【14-输入/输出】
输入/输出 文件
•什么是文件? –文件可认为是相关记录或放在一起的数据的集合 •文件一般存储在哪里?
•JAVA程序一般通过什么去访问文件属性? • JAVA API :java.io.File 类 File类的用法
•File类可以使用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径,也可以是相 对路径,默认情况下,系统总是依据用户的工作路径来解释相对路径,这个路径由系统属 性“user.dir”指定,通常也就是运行Java虚拟机时所在的路径。
•File不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
File
import java.io.File;
……
File file = new File(args[0]);
System.out.println("文件或目录是否存在:" + file.exists());
System.out.println("是文件吗:" + file.isFile());
System.out.println("是目录吗:" + file.isDirectory());
System.out.println("名称:" + file .getName());
System.out.println("路径: " + file.getPath());
System.out.println("绝对路径: " + file.getAbsolutePath());
System.out.println("最后修改时间:" + file.lastModified());
System.out.println(“文件大小:” + file.length()+ “ 字节”);
……
文件过滤器
•在File的list方法中可以接受一个FilenameFilter参数,通过该参数可以只列出符合条件的文件。
FilenameFilter接口里包含了一个accept(File dir, String name)方法,该方法将依次对指定File 的所有子目录、子文件夹进行迭代,如果该方法返回true则list方法会列出该子目录或者子文件。
流
•流是指一连串流动的字符,是以先进先出方式发送信息的通道
IO流
•Java的IO流是实现输入/输出的基础,它可以方便地实现数据的输入/输出操作,Java中把不同的 输入/输出源(键盘、文件、网络连接等)抽象表述为“流”(stream),通过流的方式允许Java程序 使用相同的方式来访问不同的输入/输出源。stream是从起源(source)到接收(sink)的有序数 据。
•Java把所有传统的个流类型(类或抽象类)都放在java.io包中,用以实现输入/输出功能。
IO流分类
•按照流的流向来分:可以分为输入流和输出流。 –输入流:只能从中读取数据,而不能向其写出数据。 –输出流:只能向其写出数据,而不能从中读取数据。 •字节流和字符流 •按照流的角色分,可以分为节点流和处理流。
IO流的四个基类
•Java把所有设备里的有序数据抽象成流模型简化了输入/输出的处理。 •Java的IO流共涉及到40多个类,这些类看上去芜杂而凌乱,但实际上是非常规则、而且彼此之间存在非常紧密的联系。Java的IO流的40多个类都是从4个抽象基类派生出来的: –InputStream/Reader:所有输入流的基类,前者是输入字节流,后者是输入字符流。 –OutputStream/Writer:所有输出流的基类,前者是输出字节流,后者是输出字符流。
输入流
•InputStream和Reader是所有输入流的基类,它们都是两个抽象类,本身并不能创建实例来执行输入,但它们将所谓所有输入流的模板,所以它们的方法是所有输入流都可使用的方法。它们包含如下三个方法: –int read():从输入流中读取单个字节(相当于从图15.5所示水管中取出一滴水),返回所读取的字节数据(字节数据可直接转换为int类型)。 –int read(byte[]/char[] b):从输入流中读取最多b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数。 –int read(byte[]/char[] b, int off, int len):从输入流中读取最多len字节的数据,并将其存储在数组 b 中,放入b数组中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字节数。
输出流
•OutputStream和Writer也非常相似,它们采用如图15.6所示的模型来执行输出,两个流都提供了如下三个方法: –void write(int c):将指定的字节/字符输出到输出流中,其中c既可以代表字节,也可以代表字符。 –void write(byte[]/char[] buf):将字节数组/字符数组中的数据输出到指定输出流中。 –void write(char[] cbuf, int off, int len):将字节数组/字符数组中从off位置开始,长度为len的字节/字符输出到输出流中。
处理流的用法
•使用处理流来包装节点流,程序通过处理流来执行输入/输出功能,让节点流与底层的I/O设备、文件交互。 •实际上我们要识别处理流非常简单,只要流的构造器参数不是一个物理节点,而是已经已经存在的流,那么这种流就一定是处理流;而所有节点流都是直接以物理IO节点作为构造器参数的。 •程序使用处理流非常简单,通常只需要在创建处理流时传入一个节点流作为构造器参数即可,这样创建的处理流就是包装了该节点流的处理流。 转换流
•输入/输出流体系里还提供了2个转换流,这两个转换流用于实现将字节流转换成字符流,其中 InputStreamReader将字节输入流转换成字符输入流,OutputStreamWriter将字节输出流转换 成字符输出流。
推回输入流
•在输入/输出流体系中,有两个特殊的流与众不同,就是PushbackInputStream和PushbackReader,它们都提供了如下三个方法: –void unread(byte[]/char[] buf):将一个字节/字符数组内容的推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。 –void unread(byte[]/char[] b, int off, int len):将一个字节/字符数组里从off开始,长度为len字节/字符的内容推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。 –void unread(int b) :将一个字节/字符的推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。
重定向标准输入/输出
•System类里提供了三个重定向标准输入/输出的方法: –static void setErr(PrintStream err):重定向 “标准”错误输出流。 –static void setIn(InputStream in):重新分配“标准”输入流。 –static void setOut(PrintStream out):重定向 “标准”输出流。
读写其他进程的数据
•用Runtime对象的exec方法可以运行平台上的其他程序,该方法产生一个Process对象,Process对象代表由该Java程序启动的子进程。 •Process类提供了如下三个方法,用于让程序和其子进程进行通信。 –InputStream getErrorStream():获取子进程的错误流。 –InputStream getInputStream():获取子进程的输入流。 OutputStream getOutputStream():获取子进程的输出流
RandomAccessFile
•RandomAccessFile是Java输入/输出流体系中功能最丰富的文件内容访问类,它提供了众多的方 法来访问文件内容,它既可以读取文件内容,也可以向文件输出数据。与普通的输入/输入流不同的 是,RandomAccessFile支持“随机访问”的方式,程序可以直接跳转到文件的任意地方来读写数 据。
RandomAccessFile的记录指针
•RandomAccessFile对象也包含了一个记录指针,用以标识当前读写处的位置,当程序新创建一个 RandomAccessFile对象时,该对象的文件记录指针位于文件头(也就是0处),当读/写了n个字节 后,文件记录指针将会向后移动n个字节。除此之外,RandomAccessFile可以自由移动该记录指 针,既可以向前移动,也可以向后移动。RandomAccessFile包含了如下两个方法来操作文件记录指 针: –long getFilePointer():返回文件记录指针的当前位置。 –void seek(long pos):将文件记录指针定位到pos位置。
RandomAccessFile的读写模式
•RandomAccessFile类有两个构造器,其实这两个构造器基本相同,只是指定文件的形式不同而 已:一个使用String参数来指定文件名,一个使用File参数来指定文件本身。除此之外,创建 RandomAccessFile对象时还需要指定一个mode参数,该参数指定RandomAccessFile的访问模 式,该参数有如下四个值:
–"r":以只读方式打开指定文件。如果试图对该RandomAccessFile执行写入方法都将会抛出 IOException。 –"rw":以读取、写入方式打开指定文件。如果该文件尚不存在,则尝试创建该文件。 –"rws":以读取、写入方式打开指定文件。相对于对于 "rw"模式,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 –"rwd":以读取、写入方式打开指定文件。对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。
使用RandomAccessFile插入文件
•RandomAccessFile依然不能向文件的指定位置插入内容,如果直接将文件记录指针移动到中间某 位置后开始输出,则新输出的内容会覆盖文件中原有的内容。
•如果需要向指定位置插入内容,程序需要先把插入点后面内容读入缓冲区,等将需要插入的数据写入 文件后,再将缓冲区的内容追加到文件后面。
java.io包
•文本文件的读写 –用FileInputStream读文件 –用FileOutputStream写文件 –用BufferedReader读文本文件 –用BufferedWriter写文本文件 •二进制文件的读写 –DataOutputStream –DataInputStream
序列化
•序列化机制允许将实现序列化的Java对象转换为字节序列,这些字节序列可以被保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。 •对象的序列化(Serialize)指将一个Java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指从IO流中恢复该Java对象。 •如果需要让某个对象可以支持序列化机制,必须让它的类是可序列化的(serializable),为了让某个类是可序列化的,该类必须实现如下两个接口之一: –Serializable –Externalizable
使用Serializable实现序列化
•一旦某个类实现了Serializable接口,则该类的对象就是可序列化的,程序可以通过如下两个步骤来序列化该对象。 –(1)创建一个ObjectOutputStream,这个输出流是一个处理流,所以必须建立在其他节点流的基础之上。 –(2)调用ObjectOutputStream对象的writeObject方法输出可序列化对象。
反序列化的步骤
•(1)创建一个ObjectInputStream,这个输入流是一个处理流,所以必须建立在其他节点流的基础之上。 •(2)调用ObjectInputStream对象的readObject对象读取流中的对象,该方法返回一个Object类型的Java对象,如果程序知道该Java对象的类型,则可以将该对象强制类型转换成其真实的类型。 反序列化时的父类
•当程序创建子类实例时,系统会隐式地为它的所有父类都创建实例(并建立和此子类实例的关联)! 当我们反序列化某个子类的实例时,反序列化机制需要恢复其关联的父类实例,恢复这些父类实例有两 种方式: –使用反序列化机制。 –使用父类无参数的构造器。 •如果某个父类既不可序列化,即不能使用第一种机制;也没有提供无参数的构造器,则不可采用第二 种机制,则反序列化该子类实例将抛出异常。
对象引用的序列化
•如果某个类的属性类型不是基本类型或String类型,而是另一个引用类型,那么这个引用类必须是可序列化的,否则拥有该类型属性的类是不可序列的。 •Java序列化机制采用了一种特殊的序列化算法,其算法内容是: –所有保存到磁盘中的对象都有一个序列化编号。 –当程序试图序列化一个对象时,程序将先检查该对象是否已经被序列化过,只有当该对象从未(在本次虚拟机中)被序列化过,系统才会将该对象转换成字节序列并输出。 –如果某个对象是已经序列化过的,程序将直接只是输出一个序列化编号,而不是再次重新序列化该对象。
序列化机制可能引起的问题
•由于Java序列化机制使然:如果多次序列同一个Java对象时,只有第一次序列化时才会把该Java对 象转换成字节序列并输出,这样可能引起一个潜在的问题:当程序序列化一个可变对象时,程序只有在 第一次使用writeObject方法输出时才会将该对象转换成字节序列并输出,即使后面该对象的属性已 被改变,当程序再次调用writeObject方法时,程序只是输出前面的序列化编号,所以改变的的属性 值不会被输出。
•只有当第一次调用wirteObject方法来输出对象时才会将对象转换成字节序列,并写出到 ObjectOutputStream;在后面程序中如果该对象的属性发生了改变,即再次调用writeObject方 法输出该对象时,改变后的属性不会被输出。
自定义序列化
•在属性前面加上transient关键字,可以指定Java序列化时无需理会该属性值。 •在序列化和反序列化过程中需要特殊处理的类应该提供如下特殊签名的方法,这些特殊的方法用以实现自定义序列化: –private void writeObject(java.io.ObjectOutputStream out)throws IOException –private void readObject(java.io.ObjectInputStream in) – throws IOException, ClassNotFoundException; –private void readObjectNoData()throws ObjectStreamException;
实现Externalizable接口
•该接口里定义了2个方法: –void readExternal(ObjectInput in):需要序列化的类实现 readExternal方法来实现反序列 化。该方法调用DataInput(它是ObjectInput的父接口)的方法来恢复基本类型的属性值,调用 ObjectInput的readObject方法来来恢复引用类型的属性值。
–void writeExternal(ObjectOutput out):需要序列化的类实现writeExternal方法来保存对象 的状态。该方法调用DataInput(它是ObjectInput的父接口)的方法来保存基本类型的属性值,调 用 ObjectOutput的writeObject方法来保存引用类型的属性值。
•实际上采用实现Externalizable接口方式的序列化与前面介绍的自定义序列化非常像,只是 Externalizable接口强制自定义序列化。
序列化时的版本
•Java序列化机制允许为序列化类提供一个private static final的serialVersionUID属性值,该属 性值用于标识该Java类的序列化版本,也就是说如果一个类升级后只要它的serialVersionUID属性 值保持不变,序列化机制也会把它们当成同一个序列化版本。
Java新IO
•新IO和传统的IO有相同的目的,都是用于进行输入/输出功能,但新IO使用了不同的方式来处理处 理输入/输出,新IO采用内存映射文件的方式来处理输入/输出,新IO将文件或文件的一段区域映射到 内存中,这样就可以像访问内存一样来访问文件了(这种方式模拟了操作系统上的虚拟内存的概念), 通过这种方式来进行输入/输出比传统的输入/输出要快得多。 •Java中NIO相关的包如下: –java.nio包:主要提供了一些和Buffer相关的类。 –java.nio.channels包:主要包括Channel和Selector相关的类。 –java.nio.charset包:主要包含和字符集相关的类。 –java.nio.channels.spi包:主要包含提供Channel服务的类。 –java.nio.charset.spi包:主要包含提供字符集服务的相关类。
Java新IO的核心概念
•Channel(通道)和Buffer(缓冲)是新IO中的两个核心对象,Channel是对传统输入/输出系统 中里的模拟,在新IO系统中所有数据都需要通过通道传输;Channel与传统的InputStream、 OutputStream最大的区别在于它提供了一个map方法,通过该map方法可以直接将“一块数据”映 射到内存中。如果说传统的输入/输出系统是面向流的处理,而新IO则是面向块的处理。
•Buffer可以被理解成一个容器,它的本质是一个数组,发送到Channel中的所有对象都必须首先放 到Buffer中,而从Channel中读取的数据也必须先读到Buffer中。此处的Buffer有点类似于前面我 们介绍的“竹筒”,但该Buffer既可以像前面那样一次、一次去Channel中取水,也允许使用 Channel直接将文件的某块数据映射成Buffer。
•除了Channel和Buffer之外,新IO还提供了用于将UNICODE字符串映射成字节序列以及逆映射操 作的Charset类,还提供了用于支持非阻塞式输入/输出的Selector类。
使用Buffer
•从内部结构上来看,Buffer就像一个数组,它可以保存多个类型相同的数据。Buffer是一个抽象 类,其最常用的子类是ByteBuffer,它可以在底层字节数组上进行get/set操作,除了ByteBuffer 之外,其他基本数据类型(boolean除外)都有相应的Buffer类:ByteBuffer、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。
Buffer的三个概念
•容量(capacity):缓冲区的 容量(capacity) 表示该Buffer的最大数据容量,即最多可以存储 多少数据。缓冲区的容量不可能为负值,在创建后也不能改变。
•界限(limit):第一个不应该被读出或者写入的缓冲区位置索引。也就是说,位于limit后的数据既 不可被读,也不可被写。
•位置(position):用于指明下一个可以被读出的或者写入的缓冲区位置索引(类似于IO流中的记 录指针)。当使用Buffer从Channel中读取数据时,position的值恰好等于已经读到了多少数据。当 刚刚新建一个Buffer对象时,其position为0,如果从Channel中读取了2个数据到该Buffer中,则 postion为2,指向Buffer中第三个(第一个位置的索引为0)位置。
使用Channel
•Channel类似于传统的流对象,但与传统的流不同的是,Channel有两个主要的区别: –Channel可以直接将指定文件的部分或全部映射成Buffer。
–程序不能直接访问Channel中的数据,包括读取、写入都不行,Channel只能与Buffer进行交 互。也就是说,如果要从Channel中取得数据,必须先用Buffer从Channel中取出一些数据,然 后让程序从Buffer中取出这些数据;如果要将程序中的数据写入Channel,一样先让程序将数据 放入Buffer中,程序再将Buffer里的输入写入Channel中。
Channel及其实现类
•Channel是一个接口,位于java.nio.channels包下,系统为该接口提供了DatagramChannel、 FileChannel、Pipe.SinkChannel、Pipe.SourceChannel、SelectableChannel、 ServerSocketChannel, SocketChannel等实现类,本节主要介绍FileChannel的用法,根据这 些Channel的名字我们不难发现新IO里的Channel是按功能来划分的,例如Pipe.SinkChannel、 Pipe.SourceChannel用于支持线程之间通信的管道Channel,而ServerSocketChannel、 SocketChannel则是用于支持TCP网络通信的Channel。
编码集和Charset
•通常而言,把明文的字符串序列转换成计算机理解的字节序列(二进制文件,普通人看不懂)成为编 码,把字节序列转化成普通人能看懂的明文字符串称为解码。
•粗体字代码分别实现了将CharBuffer转换成ByteBuffer,将ByteBuffer转换成CharBuffer的功 能。实际上Charset里也提供了如下三个方法: –CharBuffer decode(ByteBuffer bb):将ByteBuffer中字节序列转换成字符序列的的便捷方法。 –ByteBuffer encode(CharBuffer cb):将CharBuffer中的字符序列转换成字节序列的便捷方法。 –ByteBuffer encode(String str):将String中的字符序列转换成字节序列的便捷方法。
文件锁
•文件锁在操作系统上是很平常的事情,如果多个运行的程序需要并发修改同一个文件时,需要使用某种机制进行通信。 •使用文件锁可以有效地阻止多条进程并发修改同一份文件,所以现在的大部分操作系统都提供了文件锁的功能。 •文件锁控制文件或者文件部分字节的访问,但文件锁在不同操作系统的差别较大,所以早期的JDK版本并未提供文件锁的支持。从JDK1.4的新IO开始,Java开始提供文件锁的支持。 关于文件锁的注意
•在某些平台上,文件锁仅仅是建议性的,并不是强制式的。这意味着即使一个程序不能获得文件锁,它也可以对该文件进行读写。 •在某些平台上,不能同步地锁定一个文件并把它映射到内存中。 •文件锁是由Java虚拟机所持有的,如果两个Java程序使用同一个Java虚拟机运行,则它们不能对同一个文件进行加锁。 •在某些平台上当关闭 FileChannel时,会释放Java虚拟机在该文件上的所有锁,因此应该避免对同一个被锁定的文件打开多个FileChannel。 Java 7新增的文件API
•传统的Java里,只有一个File类,即代表文件,又代表目录。 •Java 7新增了如下API来访问文件 –Path - 接口,代表一个平台无关的目录。提供了大量的方法来操作目录。 –Paths - 工具类。所有方法都是static的。 –Files - 操作文件的工具类。 •提供了大量的方法来操作文件。 •该类所包含的大量方法可能与我们日常一般的期望有些出入。
public class FilenameFilterTest { public static void main(String[] args) { File file = new File("."); // 使用Lambda表达式(目标类型为FilenameFilter)实现文件过滤器。 // 如果文件名以.java结尾,或者文件对应一个路径,返回true String[] nameList = file.list((dir, name) -> name.endsWith(".java") || new File(name).isDirectory()); for(String name : nameList) { System.out.println(name); } } } public class FileTest { public static void main(String[] args) throws IOException { // 以当前路径来创建一个File对象 File file = new File("."); // 直接获取文件名,输出一点 System.out.println(file.getName()); // 获取相对路径的父路径可能出错,下面代码输出null System.out.println(file.getParent()); // 获取绝对路径 System.out.println(file.getAbsoluteFile()); // 获取上一级路径 System.out.println(file.getAbsoluteFile().getParent()); // 在当前路径下创建一个临时文件 File tmpFile = File.createTempFile("aaa", ".txt", file); // 指定当JVM退出时删除该文件 tmpFile.deleteOnExit(); // 以系统当前时间作为新文件名来创建新文件 File newFile = new File(System.currentTimeMillis() + ""); System.out.println("newFile对象是否存在:" + newFile.exists()); // 以指定newFile对象来创建一个文件 newFile.createNewFile(); // 以newFile对象来创建一个目录,因为newFile已经存在, // 所以下面方法返回false,即无法创建该目录 newFile.mkdir(); // 使用list()方法来列出当前路径下的所有文件和路径 String[] fileList = file.list(); System.out.println("====当前路径下所有文件和路径如下===="); for (String fileName : fileList) { System.out.println(fileName); } // listRoots()静态方法列出所有的磁盘根路径。 File[] roots = File.listRoots(); System.out.println("====系统所有根路径如下===="); for (File root : roots) { System.out.println(root); } } } public class FileInputStreamTest { public static void main(String[] args) throws IOException { // 创建字节输入流 FileInputStream fis = new FileInputStream("FileInputStreamTest.java"); // 创建一个长度为1024的“竹筒” byte[] bbuf = new byte[1024]; // 用于保存实际读取的字节数 int hasRead = 0; // 使用循环来重复“取水”过程 while ((hasRead = fis.read(bbuf)) > 0) { // 取出“竹筒”中水滴(字节),将字节数组转换成字符串输入! System.out.print(new String(bbuf, 0, hasRead)); } // 关闭文件输入流,放在finally块里更安全 fis.close(); } } public class FileOutputStreamTest { public static void main(String[] args) { try ( // 创建字节输入流 FileInputStream fis = new FileInputStream("FileOutputStreamTest.java"); // 创建字节输出流 FileOutputStream fos = new FileOutputStream("newFile.txt")) { byte[] bbuf = new byte[32]; int hasRead = 0; // 循环从输入流中取出数据 while ((hasRead = fis.read(bbuf)) > 0) { // 每读取一次,即写入文件输出流,读了多少,就写多少。 fos.write(bbuf, 0, hasRead); } } catch (IOException ioe) { ioe.printStackTrace(); } } } public class FileReaderTest { public static void main(String[] args) { try ( // 创建字符输入流 FileReader fr = new FileReader("FileReaderTest.java")) { // 创建一个长度为32的“竹筒” char[] cbuf = new char[32]; // 用于保存实际读取的字符数 int hasRead = 0; // 使用循环来重复“取水”过程 while ((hasRead = fr.read(cbuf)) > 0) { // 取出“竹筒”中水滴(字符),将字符数组转换成字符串输入! System.out.print(new String(cbuf, 0, hasRead)); } } catch (IOException ex) { ex.printStackTrace(); } } } public class FileWriterTest { public static void main(String[] args) { try (FileWriter fw = new FileWriter("poem.txt")) { fw.write("锦瑟 - 李商隐\r\n"); fw.write("锦瑟无端五十弦,一弦一柱思华年。\r\n"); fw.write("庄生晓梦迷蝴蝶,望帝春心托杜鹃。\r\n"); fw.write("沧海月明珠有泪,蓝田日暖玉生烟。\r\n"); fw.write("此情可待成追忆,只是当时已惘然。\r\n"); } catch (IOException ioe) { ioe.printStackTrace(); } } } public class KeyinTest { public static void main(String[] args) { try ( // 将Sytem.in对象转换成Reader对象 InputStreamReader reader = new InputStreamReader(System.in); // 将普通Reader包装成BufferedReader BufferedReader br = new BufferedReader(reader)) { String line = null; // 采用循环方式来一行一行的读取 while ((line = br.readLine()) != null) { // 如果读取的字符串为"exit",程序退出 if (line.equals("exit")) { System.exit(1); } // 打印读取的内容 System.out.println("输入内容为:" + line); } } catch (IOException ioe) { ioe.printStackTrace(); } } } public class PrintStreamTest { public static void main(String[] args) { try (FileOutputStream fos = new FileOutputStream("test.txt"); PrintStream ps = new PrintStream(fos)) { // 使用PrintStream执行输出 ps.println("普通字符串"); // 直接使用PrintStream输出对象 ps.println(new PrintStreamTest()); } catch (IOException ioe) { ioe.printStackTrace(); } } } public class PushbackTest { public static void main(String[] args) { try ( // 创建一个PushbackReader对象,指定推回缓冲区的长度为64 PushbackReader pr = new PushbackReader(new FileReader( "PushbackTest.java"), 64)) { char[] buf = new char[32]; // 用以保存上次读取的字符串内容 String lastContent = ""; int hasRead = 0; // 循环读取文件内容 while ((hasRead = pr.read(buf)) > 0) { // 将读取的内容转换成字符串 String content = new String(buf, 0, hasRead); int targetIndex = 0; // 将上次读取的字符串和本次读取的字符串拼起来, // 查看是否包含目标字符串, 如果包含目标字符串 if ((targetIndex = (lastContent + content) .indexOf("new PushbackReader")) > 0) { // 将本次内容和上次内容一起推回缓冲区 pr.unread((lastContent + content).toCharArray()); // 重新定义一个长度为targetIndex的char数组 if (targetIndex > 32) { buf = new char[targetIndex]; } // 再次读取指定长度的内容(就是目标字符串之前的内容) pr.read(buf, 0, targetIndex); // 打印读取的内容 System.out.print(new String(buf, 0, targetIndex)); System.exit(0); } else { // 打印上次读取的内容 System.out.print(lastContent); // 将本次内容设为上次读取的内容 lastContent = content; } } } catch (IOException ioe) { ioe.printStackTrace(); } } } public class StringNodeTest { public static void main(String[] args) { String src = "从明天起,做一个幸福的人\n" + "喂马,劈柴,周游世界\n" + "从明天起,关心粮食和蔬菜\n" + "我有一所房子,面朝大海,春暖花开\n" + "从明天起,和每一个亲人通信\n" + "告诉他们我的幸福\n"; char[] buffer = new char[32]; int hasRead = 0; try (StringReader sr = new StringReader(src)) { // 采用循环读取的访问读取字符串 while ((hasRead = sr.read(buffer)) > 0) { System.out.print(new String(buffer, 0, hasRead)); } } catch (IOException ioe) { ioe.printStackTrace(); } try ( // 创建StringWriter时,实际上以一个StringBuffer作为输出节点 // 下面指定的20就是StringBuffer的初始长度 StringWriter sw = new StringWriter()) { // 调用StringWriter的方法执行输出 sw.write("有一个美丽的新世界,\n"); sw.write("她在远方等我,\n"); sw.write("哪里有天真的孩子,\n"); sw.write("还有姑娘的酒窝\n"); System.out.println("----下面是sw的字符串节点里的内容----"); // 使用toString()方法返回StringWriter的字符串节点的内容 System.out.println(sw.toString()); } catch (IOException ex) { ex.printStackTrace(); } } } public class RedirectIn { public static void main(String[] args) { try (FileInputStream fis = new FileInputStream("RedirectIn.java")) { // 将标准输入重定向到fis输入流 System.setIn(fis); // 使用System.in创建Scanner对象,用于获取标准输入 Scanner sc = new Scanner(System.in); // 增加下面一行将只把回车作为分隔符 sc.useDelimiter("\n"); // 判断是否还有下一个输入项 while (sc.hasNext()) { // 输出输入项 System.out.println("键盘输入的内容是:" + sc.next()); } } catch (IOException ex) { ex.printStackTrace(); } } } public class RedirectOut { public static void main(String[] args) { try ( // 一次性创建PrintStream输出流 PrintStream ps = new PrintStream(new FileOutputStream("out.txt"))) { // 将标准输出重定向到ps输出流 System.setOut(ps); // 向标准输出输出一个字符串 System.out.println("普通字符串"); // 向标准输出输出一个对象 System.out.println(new RedirectOut()); } catch (IOException ex) { ex.printStackTrace(); } } } public class ReadFromProcess { public static void main(String[] args) throws IOException { // 运行javac命令,返回运行该命令的子进程 Process p = Runtime.getRuntime().exec("javac"); try ( // 以p进程的错误流创建BufferedReader对象 // 这个错误流对本程序是输入流,对p进程则是输出流 BufferedReader br = new BufferedReader(new InputStreamReader( p.getErrorStream()))) { String buff = null; // 采取循环方式来读取p进程的错误输出 while ((buff = br.readLine()) != null) { System.out.println(buff); } } } } public class WriteToProcess { public static void main(String[] args) throws IOException { // 运行java ReadStandard命令,返回运行该命令的子进程 Process p = Runtime.getRuntime().exec("java ReadStandard"); try ( // 以p进程的输出流创建PrintStream对象 // 这个输出流对本程序是输出流,对p进程则是输入流 PrintStream ps = new PrintStream(p.getOutputStream())) { // 向ReadStandard程序写入内容,这些内容将被ReadStandard读取 ps.println("普通字符串"); ps.println(new WriteToProcess()); } } } // 定义一个ReadStandard类,该类可以接受标准输入, // 并将标准输入写入out.txt文件。 class ReadStandard { public static void main(String[] args) { try ( // 使用System.in创建Scanner对象,用于获取标准输入 Scanner sc = new Scanner(System.in); PrintStream ps = new PrintStream( new FileOutputStream("out.txt"))) { // 增加下面一行将只把回车作为分隔符 sc.useDelimiter("\n"); // 判断是否还有下一个输入项 while (sc.hasNext()) { // 输出输入项 ps.println("键盘输入的内容是:" + sc.next()); } } catch (IOException ioe) { ioe.printStackTrace(); } } } public class AppendContent { public static void main(String[] args) { try ( // 以读、写方式打开一个RandomAccessFile对象 RandomAccessFile raf = new RandomAccessFile("out.txt", "rw")) { // 将记录指针移动到out.txt文件的最后 raf.seek(raf.length()); raf.write("追加的内容!\r\n".getBytes()); } catch (IOException ex) { ex.printStackTrace(); } } } public class InsertContent { public static void insert(String fileName, long pos, String insertContent) throws IOException { File tmp = File.createTempFile("tmp", null); tmp.deleteOnExit(); try (RandomAccessFile raf = new RandomAccessFile(fileName, "rw"); // 使用临时文件来保存插入点后的数据 FileOutputStream tmpOut = new FileOutputStream(tmp); FileInputStream tmpIn = new FileInputStream(tmp)) { raf.seek(pos); // ------下面代码将插入点后的内容读入临时文件中保存------ byte[] bbuf = new byte[64]; // 用于保存实际读取的字节数 int hasRead = 0; // 使用循环方式读取插入点后的数据 while ((hasRead = raf.read(bbuf)) > 0) { // 将读取的数据写入临时文件 tmpOut.write(bbuf, 0, hasRead); } // ----------下面代码插入内容---------- // 把文件记录指针重新定位到pos位置 raf.seek(pos); // 追加需要插入的内容 raf.write(insertContent.getBytes()); // 追加临时文件中的内容 while ((hasRead = tmpIn.read(bbuf)) > 0) { raf.write(bbuf, 0, hasRead); } } } public static void main(String[] args) throws IOException { insert("InsertContent.java", 45, "插入的内容\r\n"); } } public class RandomAccessFileTest { public static void main(String[] args) { try (RandomAccessFile raf = new RandomAccessFile( "RandomAccessFileTest.java", "r")) { // 获取RandomAccessFile对象文件指针的位置,初始位置是0 System.out.println("RandomAccessFile的文件指针的初始位置:" + raf.getFilePointer()); // 移动raf的文件记录指针的位置 raf.seek(300); byte[] bbuf = new byte[1024]; // 用于保存实际读取的字节数 int hasRead = 0; // 使用循环来重复“取水”过程 while ((hasRead = raf.read(bbuf)) > 0) { // 取出“竹筒”中水滴(字节),将字节数组转换成字符串输入! System.out.print(new String(bbuf, 0, hasRead)); } } catch (IOException ex) { ex.printStackTrace(); } } } public class Person implements java.io.Serializable { private String name; private transient int age; // 注意此处没有提供无参数的构造器! public Person(String name, int age) { System.out.println("有参数的构造器"); this.name = name; this.age = age; } // 省略name与age的setter和getter方法 // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // age的setter和getter方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } } public class TransientTest { public static void main(String[] args) { try ( // 创建一个ObjectOutputStream输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream( "transient.txt")); // 创建一个ObjectInputStream输入流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("transient.txt"))) { Person per = new Person("孙悟空", 500); // 系统会per对象转换字节序列并输出 oos.writeObject(per); Person p = (Person) ois.readObject(); System.out.println(p.getAge()); } catch (Exception ex) { ex.printStackTrace(); } } } public class Orientation implements java.io.Serializable { public static final Orientation HORIZONTAL = new Orientation(1); public static final Orientation VERTICAL = new Orientation(2); private int value; private Orientation(int value) { this.value = value; } // 为枚举类增加readResolve()方法 private Object readResolve() throws ObjectStreamException { if (value == 1) { return HORIZONTAL; } if (value == 2) { return VERTICAL; } return null; } } public class ResolveTest { public static void main(String[] args) { try ( // 创建一个ObjectOutputStream输入流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream( "transient.txt")); // 创建一个ObjectInputStream输入流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("transient.txt"))) { oos.writeObject(Orientation.HORIZONTAL); Orientation ori = (Orientation) ois.readObject(); System.out.println(ori == Orientation.HORIZONTAL); } catch (Exception ex) { ex.printStackTrace(); } } } public class Person implements java.io.Serializable { private String name; private int age; // 注意此处没有提供无参数的构造器! public Person(String name, int age) { System.out.println("有参数的构造器"); this.name = name; this.age = age; } // 省略name与age的setter和getter方法 // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // age的setter和getter方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } // 重写writeReplace方法,程序在序列化该对象之前,先调用该方法 private Object writeReplace() throws ObjectStreamException { ArrayList<Object> list = new ArrayList<>(); list.add(name); list.add(age); return list; } } public class ReplaceTest { public static void main(String[] args) { try ( // 创建一个ObjectOutputStream输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream( "replace.txt")); // 创建一个ObjectInputStream输入流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("replace.txt"))) { Person per = new Person("孙悟空", 500); // 系统将per对象转换字节序列并输出 oos.writeObject(per); // 反序列化读取得到的是ArrayList ArrayList list = (ArrayList) ois.readObject(); System.out.println(list); } catch (Exception ex) { ex.printStackTrace(); } } } public class Person implements java.io.Externalizable { private String name; private int age; // 注意此处没有提供无参数的构造器! public Person(String name, int age) { System.out.println("有参数的构造器"); this.name = name; this.age = age; } // 省略name与age的setter和getter方法 // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // age的setter和getter方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } public void writeExternal(java.io.ObjectOutput out) throws IOException { // 将name实例变量的值反转后写入二进制流 out.writeObject(new StringBuffer(name).reverse()); out.writeInt(age); } public void readExternal(java.io.ObjectInput in) throws IOException, ClassNotFoundException { // 将读取的字符串反转后赋给name实例变量 this.name = ((StringBuffer) in.readObject()).reverse().toString(); this.age = in.readInt(); } } public class Person implements java.io.Serializable { private String name; private int age; // 注意此处没有提供无参数的构造器! public Person(String name, int age) { System.out.println("有参数的构造器"); this.name = name; this.age = age; } // 省略name与age的setter和getter方法 // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // age的setter和getter方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } private void writeObject(java.io.ObjectOutputStream out) throws IOException { // 将name实例变量的值反转后写入二进制流 out.writeObject(new StringBuffer(name).reverse()); out.writeInt(age); } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { // 将读取的字符串反转后赋给name实例变量 this.name = ((StringBuffer) in.readObject()).reverse().toString(); this.age = in.readInt(); } } public class Person implements java.io.Serializable { private String name; private int age; // 注意此处没有提供无参数的构造器! public Person(String name, int age) { System.out.println("有参数的构造器"); this.name = name; this.age = age; } // 省略name与age的setter和getter方法 // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // age的setter和getter方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } } public class ReadObject { public static void main(String[] args) { try ( // 创建一个ObjectInputStream输入流 ObjectInputStream ois = new ObjectInputStream(new FileInputStream( "object.txt"))) { // 从输入流中读取一个Java对象,并将其强制类型转换为Person类 Person p = (Person) ois.readObject(); System.out.println("名字为:" + p.getName() + "\n年龄为:" + p.getAge()); } catch (Exception ex) { ex.printStackTrace(); } } } public class ReadTeacher { public static void main(String[] args) { try ( // 创建一个ObjectInputStream输出流 ObjectInputStream ois = new ObjectInputStream(new FileInputStream( "teacher.txt"))) { // 依次读取ObjectInputStream输入流中的四个对象 Teacher t1 = (Teacher) ois.readObject(); Teacher t2 = (Teacher) ois.readObject(); Person p = (Person) ois.readObject(); Teacher t3 = (Teacher) ois.readObject(); // 输出true System.out.println("t1的student引用和p是否相同:" + (t1.getStudent() == p)); // 输出true System.out.println("t2的student引用和p是否相同:" + (t2.getStudent() == p)); // 输出true System.out.println("t2和t3是否是同一个对象:" + (t2 == t3)); } catch (Exception ex) { ex.printStackTrace(); } } } public class SerializeMutable { public static void main(String[] args) { try ( // 创建一个ObjectOutputStream输入流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream( "mutable.txt")); // 创建一个ObjectInputStream输入流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("mutable.txt"))) { Person per = new Person("孙悟空", 500); // 系统会per对象转换字节序列并输出 oos.writeObject(per); // 改变per对象的name实例变量 per.setName("猪八戒"); // 系统只是输出序列化编号,所以改变后的name不会被序列化 oos.writeObject(per); Person p1 = (Person) ois.readObject(); // ① Person p2 = (Person) ois.readObject(); // ② // 下面输出true,即反序列化后p1等于p2 System.out.println(p1 == p2); // 下面依然看到输出"孙悟空",即改变后的实例变量没有被序列化 System.out.println(p2.getName()); } catch (Exception ex) { ex.printStackTrace(); } } } public class Teacher implements java.io.Serializable { private String name; private Person student; public Teacher(String name, Person student) { this.name = name; this.student = student; } // 此处省略了name和student的setter和getter方法 // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // student的setter和getter方法 public void setStudent(Person student) { this.student = student; } public Person getStudent() { return this.student; } } public class WriteObject { public static void main(String[] args) { try ( // 创建一个ObjectOutputStream输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream( "object.txt"))) { Person per = new Person("孙悟空", 500); // 将per对象写入输出流 oos.writeObject(per); } catch (IOException ex) { ex.printStackTrace(); } } } public class WriteTeacher { public static void main(String[] args) { try ( // 创建一个ObjectOutputStream输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream( "teacher.txt"))) { Person per = new Person("孙悟空", 500); Teacher t1 = new Teacher("唐僧", per); Teacher t2 = new Teacher("菩提祖师", per); // 依次将四个对象写入输出流 oos.writeObject(t1); oos.writeObject(t2); oos.writeObject(per); oos.writeObject(t2); } catch (IOException ex) { ex.printStackTrace(); } } } public class BufferTest { public static void main(String[] args) { // 创建Buffer CharBuffer buff = CharBuffer.allocate(8); // ① System.out.println("capacity: " + buff.capacity()); System.out.println("limit: " + buff.limit()); System.out.println("position: " + buff.position()); // 放入元素 buff.put('a'); buff.put('b'); buff.put('c'); // ② System.out.println("加入三个元素后,position = " + buff.position()); // 调用flip()方法 buff.flip(); // ③ System.out.println("执行flip()后,limit = " + buff.limit()); System.out.println("position = " + buff.position()); // 取出第一个元素 System.out.println("第一个元素(position=0):" + buff.get()); // ④ System.out.println("取出一个元素后,position = " + buff.position()); // 调用clear方法 buff.clear(); // ⑤ System.out.println("执行clear()后,limit = " + buff.limit()); System.out.println("执行clear()后,position = " + buff.position()); System.out.println("执行clear()后,缓冲区内容并没有被清除:" + "第三个元素为:" + buff.get(2)); // ⑥ System.out.println("执行绝对读取后,position = " + buff.position()); } } public class CharsetTest { public static void main(String[] args) { // 获取Java支持的全部字符集 SortedMap<String, Charset> map = Charset.availableCharsets(); for (String alias : map.keySet()) { // 输出字符集的别名和对应的Charset对象 System.out.println(alias + "----->" + map.get(alias)); } } } public class CharsetTransform { public static void main(String[] args) throws Exception { // 创建简体中文对应的Charset Charset cn = Charset.forName("GBK"); // 获取cn对象对应的编码器和解码器 CharsetEncoder cnEncoder = cn.newEncoder(); CharsetDecoder cnDecoder = cn.newDecoder(); // 创建一个CharBuffer对象 CharBuffer cbuff = CharBuffer.allocate(8); cbuff.put('孙'); cbuff.put('悟'); cbuff.put('空'); cbuff.flip(); // 将CharBuffer中的字符序列转换成字节序列 ByteBuffer bbuff = cnEncoder.encode(cbuff); // 循环访问ByteBuffer中的每个字节 for (int i = 0; i < bbuff.capacity(); i++) { System.out.print(bbuff.get(i) + " "); } // 将ByteBuffer的数据解码成字符序列 System.out.println("\n" + cnDecoder.decode(bbuff)); } } public class FileChannelTest { public static void main(String[] args) { File f = new File("FileChannelTest.java"); try ( // 创建FileInputStream,以该文件输入流创建FileChannel FileChannel inChannel = new FileInputStream(f).getChannel(); // 以文件输出流创建FileBuffer,用以控制输出 FileChannel outChannel = new FileOutputStream("a.txt") .getChannel()) { // 将FileChannel里的全部数据映射成ByteBuffer MappedByteBuffer buffer = inChannel.map( FileChannel.MapMode.READ_ONLY, 0, f.length()); // ① // 使用GBK的字符集来创建解码器 Charset charset = Charset.forName("GBK"); // 直接将buffer里的数据全部输出 outChannel.write(buffer); // ② // 再次调用buffer的clear()方法,复原limit、position的位置 buffer.clear(); // 创建解码器(CharsetDecoder)对象 CharsetDecoder decoder = charset.newDecoder(); // 使用解码器将ByteBuffer转换成CharBuffer CharBuffer charBuffer = decoder.decode(buffer); // CharBuffer的toString方法可以获取对应的字符串 System.out.println(charBuffer); } catch (IOException ex) { ex.printStackTrace(); } } } public class FileLockTest { public static void main(String[] args) throws Exception { try ( // 使用FileOutputStream获取FileChannel FileChannel channel = new FileOutputStream("a.txt").getChannel()) { // 使用非阻塞式方式对指定文件加锁 FileLock lock = channel.tryLock(); // 程序暂停10s Thread.sleep(10000); // 释放锁 lock.release(); } } } public class RandomFileChannelTest { public static void main(String[] args) throws IOException { File f = new File("a.txt"); try ( // 创建一个RandomAccessFile对象 RandomAccessFile raf = new RandomAccessFile(f, "rw"); // 获取RandomAccessFile对应的Channel FileChannel randomChannel = raf.getChannel()) { // 将Channel中所有数据映射成ByteBuffer ByteBuffer buffer = randomChannel.map( FileChannel.MapMode.READ_ONLY, 0, f.length()); // 把Channel的记录指针移动到最后 randomChannel.position(f.length()); // 将buffer中所有数据输出 randomChannel.write(buffer); } } } public class ReadFile { public static void main(String[] args) throws IOException { try ( // 创建文件输入流 FileInputStream fis = new FileInputStream("ReadFile.java"); // 创建一个FileChannel FileChannel fcin = fis.getChannel()) { // 定义一个ByteBuffer对象,用于重复取水 ByteBuffer bbuff = ByteBuffer.allocate(256); // 将FileChannel中数据放入ByteBuffer中 while (fcin.read(bbuff) != -1) { // 锁定Buffer的空白区 bbuff.flip(); // 创建Charset对象 Charset charset = Charset.forName("GBK"); // 创建解码器(CharsetDecoder)对象 CharsetDecoder decoder = charset.newDecoder(); // 将ByteBuffer的内容转码 CharBuffer cbuff = decoder.decode(bbuff); System.out.print(cbuff); // 将Buffer初始化,为下一次读取数据做准备 bbuff.clear(); } } } } public class FilesTest { public static void main(String[] args) throws Exception { // 复制文件 Files.copy(Paths.get("FilesTest.java") , new FileOutputStream("a.txt")); // 判断FilesTest.java文件是否为隐藏文件 System.out.println("FilesTest.java是否为隐藏文件:" + Files.isHidden(Paths.get("FilesTest.java"))); // 一次性读取FilesTest.java文件的所有行 List<String> lines = Files.readAllLines(Paths .get("FilesTest.java"), Charset.forName("gbk")); System.out.println(lines); // 判断指定文件的大小 System.out.println("FilesTest.java的大小为:" + Files.size(Paths.get("FilesTest.java"))); List<String> poem = new ArrayList<>(); poem.add("水晶潭底银鱼跃"); poem.add("清徐风中碧竿横"); // 直接将多个字符串内容写入指定文件中 Files.write(Paths.get("pome.txt") , poem , Charset.forName("gbk")); // 使用Java 8新增的Stream API列出当前目录下所有文件和子目录 Files.list(Paths.get(".")).forEach(path -> System.out.println(path)); // 使用Java 8新增的Stream API读取文件内容 Files.lines(Paths.get("FilesTest.java") , Charset.forName("gbk")) .forEach(line -> System.out.println(line)); FileStore cStore = Files.getFileStore(Paths.get("C:")); // 判断C盘的总空间,可用空间 System.out.println("C:共有空间:" + cStore.getTotalSpace()); System.out.println("C:可用空间:" + cStore.getUsableSpace()); } } public class FileVisitorTest { public static void main(String[] args) throws Exception { // 遍历g:\publish\codes\15目录下的所有文件和子目录 Files.walkFileTree(Paths.get("g:", "publish", "codes", "15"), new SimpleFileVisitor<Path>() { // 访问文件时候触发该方法 @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { System.out.println("正在访问" + file + "文件"); // 找到了FileInputStreamTest.java文件 if (file.endsWith("FileInputStreamTest.java")) { System.out.println("--已经找到目标文件--"); return FileVisitResult.TERMINATE; } return FileVisitResult.CONTINUE; } // 开始访问目录时触发该方法 @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { System.out.println("正在访问:" + dir + " 路径"); return FileVisitResult.CONTINUE; } }); } } public class PathTest { public static void main(String[] args) throws Exception { // 以当前路径来创建Path对象 Path path = Paths.get("."); System.out.println("path里包含的路径数量:" + path.getNameCount()); System.out.println("path的根路径:" + path.getRoot()); // 获取path对应的绝对路径。 Path absolutePath = path.toAbsolutePath(); System.out.println(absolutePath); // 获取绝对路径的根路径 System.out.println("absolutePath的根路径:" + absolutePath.getRoot()); // 获取绝对路径所包含的路径数量 System.out.println("absolutePath里包含的路径数量:" + absolutePath.getNameCount()); System.out.println(absolutePath.getName(3)); // 以多个String来构建Path对象 Path path2 = Paths.get("g:", "publish", "codes"); System.out.println(path2); } } public class WatchServiceTest { public static void main(String[] args) throws Exception { // 获取文件系统的WatchService对象 WatchService watchService = FileSystems.getDefault().newWatchService(); // 为C:盘根路径注册监听 Paths.get("C:/").register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); while (true) { // 获取下一个文件改动事件 WatchKey key = watchService.take(); // ① for (WatchEvent<?> event : key.pollEvents()) { System.out.println(event.context() + " 文件发生了 " + event.kind() + "事件!"); } // 重设WatchKey boolean valid = key.reset(); // 如果重设失败,退出监听 if (!valid) { break; } } } }
|