Java NIO相对于Java IO而言的,Java NIO(no blocking)非阻塞IO,与Java IO有如下不同:
- Java IO是阻塞的IO,当BuffreReader读取输入流中的数据时,如果没有读到有效的数据,程序将在此处阻塞该线程的执行,同样使用InputStream的read()方法从流中读取数据时,如果数据源中没有数据,它也会阻塞该线程。而java NIO是非阻塞的。
- 不仅如此,传统的输入流、输出流都是通过字节的移动来处理的(即使不直接去处理字节流,但底层的实现还是依赖于字节处理),也就说,面向流的输入/输出系统一次只能处理一个字节,效率不高。但是对于NIO而言,对于数据的处理是采用块进行处理的。
Java NIO包的简介:
1、java.nio包:主要包含各种鱼buffer相关的类。
2、java.nio.channels包:主要包含与channel和Selector相关的类
3、java.nio.charaset包:主要包含与字符集相关的类
4、java.nio.channels.spi包:主要包含与Channel相关的服务提供者编程接口
5、java.nio.charset.spi包:包含与字符集相关的服务提供者的编程接口
对于Java NIO包括Channel、buffer、CharSet类(将Unicode字符串映射成字节序列已经逆映射操作)和Selector类(用于支持非阻塞式输入\输出)
Channel是对传统的输入/输出系统的模拟,在新IO系统中所有的数据都需要通过通道传输;Channel与传统的InputStream,OutputStream最大的的区别在于它提供了一个map()方法,通过该map()方法可以直接将“一块数据”映射到内存中。
buffer是一个容器,本质上而言是一个数组,发送到Channel中所有对象都必须首先放到Buffer中,而从Channel中读取的数据也必须先放到Buffer中。也允许使用Channel直接将文件的某块数据映射成Buffer。
Buffer的使用
Buffer是一个抽象类,其实现包括ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffe、DoubleBuffer.这些Buffer类都没有提供构造器,通过如下方式提供的Buffer对象:
》》》》static XxxBuffer allocate(int capacity):创建一个容量为capacity的XxxBuffer对象
ByteBuffer类还有一个子类:MappedByteBuffer,它用于表示Channel将磁盘文件的部分或全部内容映射到内存中后得到的结果,通常MappedByteBuffer对象由Channel的map()方法返回
在Buffer中有三个重要的概念:容量(capacity),界限(limit)和位置(position)
1、容量(capacity):缓冲区的容量(capacity)表示该buffer的最大数据容量,即最多可存储多少数据。缓冲区的容量不可能为负值,创建 后不能改变。
2、界限(limit):第一个不应该被读出或者写入的缓冲区位置索引。也就是说,位于limit后的数据既不可被读,也不可被写。
3、位置(position):用于指明下一个可以被读出或者写入的缓冲区位置的索引(类似于IO流中的记录指针)。当使用Buffer从Channel中读取数据时,position的值恰好等于已经读了多少数据。当刚刚新建一个Buffer对象时,其position为0;如果从Channel中读取2个数据到该Buffer中,position为2,指向Buffer中第3个(第1个位置的索引为0)位置
除此之外,Buffer里还支持一个可选的标记(mark,类似于传统IO流中的mark),Buffer允许直接将position定位到该mark处。具体的关系如下:
0<=mark<=position<=limit<=capacity
当Buffer装入数据结束后,调用Buffer的flip()方法,该方法将limit设置为position所在位置,并将position设为0
当输出数据结束后,Buffer调用Clear()方法,该方法不是清空Buffer的数据,仅仅将position置为0,将limit置为capacity,为再次向Buffer中装入数据做好准备。
Buffer及其所有的子类还提供了两个重要的方法:put()和get() 方法,用于向Buffer中放入数据和从Buffer中取出数据。
使用put()和get()来访问Buffer中的数据,分为相对和绝对两种
1、相对(Relative):从Buffer的当前position处开始读取或写入数据,然后将位置(position)的值按处理的圆度的个数增加
2、绝对(Absolute):直接根据索引向Buffer中读取或写入数据,使用绝对方式访问Buffer里的数据时,并不会影响(position)的值
1 package com.edu.ynu.java.learn.collection; 2 3 import java.nio.CharBuffer; 4 5 public class BufferTest 6 { 7 public static void main(String[] args) 8 { 9 //创建Buffer 10 CharBuffer buff = CharBuffer.allocate(8); 11 System.out.println("capacity:" + buff.capacity()); 12 System.out.println("limit" + buff.limit()); 13 System.out.println("position:" + buff.position()); 14 //放入元素 15 buff.put('a'); 16 buff.put('b'); 17 buff.put('c'); 18 System.out.println("加入三个元素后,position=" + buff.position() + " 加入三个元素之后,limit=" + buff.limit()); 19 // 调用flip()方法 20 buff.flip(); 21 System.out.println("执行flip()后,position= " + buff.position() + " 执行flip()后,limit=" + buff.limit()); 22 23 // 取出第一个元素 24 System.out.println("第一个元素(position):" + buff.get()); 25 System.out.println("取出递给元素后,position=" + buff.position()); 26 27 // 调用clear()方法 28 buff.clear(); 29 System.out.println("执行clear()后,limit= " + buff.limit()); 30 System.out.println("执行clear()后,position= " + buff.position()); 31 32 33 34 35 } 36 }
通过allocate()方法创建的Buffer对象是普通Buffer,ByteBuffer还提供了一个allocateDirect()方法来创建直接Buffer.直接Buffer的创建成本比普通的Buffer的创建成本高,但直接Buffer的读取效率更高。
使用Channel
Channel类似于传统的对象,但与传统的流对象有两个主要区别
1、Channel可以直接将指定文件的部分或全部直接映射成Buffer.
2、程序不能直接访问Channel中的数据,包括读取、写入都不行,Channel只能与Buffer进行交互。
也就说,如果要从Channel中取得数据,必须先用Buffer从Channel中取出一些数据,然后让程序从Buffer中取出这些数据;如果要将程序中的数据写入Channel,一样先让程序将数据放入Buffer中,程序再将Buffer里的数据写入Channel中。
所有的Channel都不应该通过构造器来直接创建,而是通过传统的节点InputStream、OutputStream的getChannel()方法来返回对应的Channel
Channel中最常用的三类方法是map(),read()和write(),其中map()方法用于将Channel对应的部分或全部数据映射成ByteBuffer;而read()或write()都有一系列重载形式,这些方法用于从Buffer中读取数据或向Buffer中写入数据。
map()方法的方法签名为:MappedByteBuffer map(FileChannel.MapMode mode,long position,long size),第一个参数执行映射时的模式,分别的有只读,读写的模式,而第二个,第三个参数用于控制将Channel的哪些数据映射成ByteBuffer。
1 package com.edu.ynu.java.learn.collection; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.nio.ByteBuffer; 8 import java.nio.CharBuffer; 9 import java.nio.MappedByteBuffer; 10 import java.nio.channels.FileChannel; 11 import java.nio.charset.Charset; 12 import java.nio.charset.CharsetDecoder; 13 import java.nio.charset.CharsetEncoder; 14 15 public class FileChannelTest 16 { 17 public static void main(String[] args) 18 { 19 File f = new File("D:\\development\\developSpace\\javaworkspace\\JavaBase\\java_learning\\src\\com\\edu\\ynu\\java\\learn\\collection\\FileChannelTest.java"); 20 try ( 21 // 创建FileInputStream,以该文件输入流创建FileChannel 22 FileChannel inChannel = new FileInputStream(f).getChannel(); 23 // 以文件输出流创建FileBuffer,用以控制输出 24 FileChannel outChannel = new FileOutputStream("a.txt").getChannel()) 25 { 26 // 将FileChannel里的全部数据映射成ByteBuffer 27 MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length()); // ① 28 29 30 // 使用GBK的字符集来创建解码器 31 Charset charset = Charset.forName("UTF8"); 32 // 直接将buffer里的数据全部输出 33 outChannel.write(buffer); // ② 34 // 再次调用buffer的clear()方法,复原limit、position的位置 35 buffer.clear(); 36 // 创建解码器(CharsetDecoder)对象 37 CharsetDecoder decoder = charset.newDecoder(); 38 // 使用解码器将ByteBuffer转换成CharBuffer 39 CharBuffer charBuffer = decoder.decode(buffer); 40 // CharBuffer的toString方法可以获取对应的字符串 41 // System.out.println(charBuffer); 42 43 Charset charset1 = Charset.forName("UTF8"); 44 CharsetEncoder decoder1 = charset1.newEncoder(); 45 46 ByteBuffer charBuffer1 = decoder1.encode(charBuffer); 47 48 Charset charset2 = Charset.forName("GBK"); 49 CharsetDecoder decoder2 = charset2.newDecoder(); 50 CharBuffer charBuffe = decoder2.decode(charBuffer1); 51 System.out.println(charBuffe); 52 } catch (IOException ex) 53 { 54 ex.printStackTrace(); 55 } 56 } 57 }
不仅InputStream,OutputStream包含了getChannel()方法,在RandomAccessFile中也包含了一个getChannel()方法,RandomAccessFile返回的FileChannel()是只读的还是读写的,则取决于RandomAccessFile打开文件的模式。
1 package com.edu.ynu.java.learn.collection; 2 3 import java.io.File; 4 import java.io.FileNotFoundException; 5 import java.io.IOException; 6 import java.io.RandomAccessFile; 7 import java.nio.ByteBuffer; 8 import java.nio.channels.FileChannel; 9 10 public class RandomFileChannelTest 11 { 12 public static void main(String[] args) 13 { 14 File f = new File("a.txt"); 15 try ( 16 //创建一个RandomAccessFile对象 17 RandomAccessFile raf = new RandomAccessFile(f, "rw"); 18 // RandomAccessFile对应的Channel 19 FileChannel randomChannel = raf.getChannel();) 20 { 21 ByteBuffer buffer = randomChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length()); 22 // 把Channel的记录指针移动到最后 23 randomChannel.position(f.length()); 24 // 将buffer中的所有指针数据输出 25 randomChannel.write(buffer); 26 } catch (FileNotFoundException e) 27 { 28 e.printStackTrace(); 29 } catch (IOException e) 30 { 31 e.printStackTrace(); 32 } 33 } 34 35 36 }
如果使用map()方法一次将所有的文件内容映射到内存中引起性能下降,也可以使用Channel和Buffer传统的"用竹筒多次反复取水“的方式。
1 package com.edu.ynu.java.learn.collection; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.IOException; 6 import java.nio.ByteBuffer; 7 import java.nio.CharBuffer; 8 import java.nio.channels.FileChannel; 9 import java.nio.charset.Charset; 10 import java.nio.charset.CharsetDecoder; 11 12 public class ReadFile 13 { 14 public static void main(String[] args) 15 { 16 try ( 17 //创建文件输入流 18 FileInputStream fis = new FileInputStream("D:\\development\\developSpace\\javaworkspace\\JavaBase\\java_learning\\src\\com\\edu\\ynu\\java\\learn\\collection\\ReadFile.java"); 19 // 创建一个FileChannel 20 FileChannel fcin = fis.getChannel();) 21 { 22 // 定义一个ByteBuffer对象,用于重复取水 23 ByteBuffer bbuf = ByteBuffer.allocate(fis.available()); 24 // 个人认为相对于上面的语句,下面的语句更低端一点 25 // ByteBuffer bbuf1 = ByteBuffer.allocate(256); 26 while (fcin.read(bbuf) != -1) 27 { 28 //锁定Buffer的空白区 29 bbuf.flip(); 30 //创建Charset对象 31 Charset charset = Charset.forName("utf8"); 32 // 创建解码器(CharsetDecoder)对象 33 CharsetDecoder charsetDecoder = charset.newDecoder(); 34 35 // 将ByteBuffer的内容转码 36 CharBuffer cb = charsetDecoder.decode(bbuf); 37 System.out.println(cb); 38 bbuf.clear(); 39 } 40 41 } catch (FileNotFoundException e) 42 { 43 e.printStackTrace(); 44 } catch (IOException e) 45 { 46 e.printStackTrace(); 47 } 48 } 49 }
字符集和Charset
编码和解码
编码:将明文的字符序列转换成计算机理解的二进制序列。
解码:把二进制序列转换成普通人能够看懂的文字符串。
获取JDK支持的字符编码:
1 package com.edu.ynu.java.learn.collection; 2 3 import java.nio.charset.Charset; 4 import java.util.SortedMap; 5 6 public class CharsetTest 7 { 8 public static void main(String[] args) 9 { 10 SortedMap<String, Charset> map = Charset.availableCharsets(); 11 // 下面两种写都可以 12 // map.forEach((key, value) -> System.out.println(key + "--->" + value)); 13 for (String alias : map.keySet()) 14 { 15 System.out.println(alias + "--->" + map.get(alias)); 16 } 17 System.out.println(System.getProperties().getProperty("file.encoding")); 18 } 19 }
注,可以使用System类的getProperties()方法来访问本地系统的文件编码格式,文件编码格式的属性名为file.encoding.示例代码如下:
1 System.out.println(System.getProperties().getProperty("file.encoding"));
用Charset来对字符进行编码解码步骤如下:
1、使用Charset的forName()方法来创建对应的Charset对象,forName()方法的参数就是相应的字符集的别名
2、获得Charset对象之后,可以通过该对对象的newDecoder(),newEncoder()这两个方法分别返回CharsetDecoder和CharsetEncoder对象,代表该Charset的解码器和编码器。调用CharsetDecoder的decode()方法可以将ByteBuffer(字节序列)转换成CharBuffer(字符序列),调用CharsetEncoder的encode()方法就可以将CharBuffer或String(字符序列)转换成ByteBuffer(字节序列)。
示例如下:
1 package com.edu.ynu.java.learn.collection; 2 3 import java.nio.ByteBuffer; 4 import java.nio.CharBuffer; 5 import java.nio.charset.CharacterCodingException; 6 import java.nio.charset.Charset; 7 import java.nio.charset.CharsetDecoder; 8 import java.nio.charset.CharsetEncoder; 9 10 public class CharsetTransform 11 { 12 public static void main(String[] args) 13 { 14 // 创建简体中文对应的Charset 15 Charset cn = Charset.forName("UTF8"); 16 17 CharsetEncoder cnEncoder = cn.newEncoder(); 18 CharsetDecoder cnDecoder = cn.newDecoder(); 19 20 CharBuffer charBuffer = CharBuffer.allocate(2014); 21 charBuffer.put('刘'); 22 charBuffer.put('邦'); 23 charBuffer.put('刘'); 24 charBuffer.put('彻'); 25 charBuffer.flip(); 26 // 将CharBuffer中的字符序列转换成字节序列 27 try 28 { 29 ByteBuffer bbuff = cnEncoder.encode(charBuffer); 30 System.out.println(bbuff.capacity()); 31 for (int i = 0; i < bbuff.limit(); i++) 32 { 33 System.out.print(bbuff.get(i) + " "); 34 } 35 System.out.println("\n" + cnDecoder.decode(bbuff)); 36 } catch (CharacterCodingException e) 37 { 38 e.printStackTrace(); 39 } 40 41 } 42 }
在Charset中也提供了对应的decode()方法和encode() 方法,即获取Charset对象后,无须创建CharsetEncoder和CharsetDecoder对象,直接调用Charset的encode()和decode()方法进行编码、解码。
文件锁
在NIO中,Java提供了FileLock来支持文件锁定功能,在FileChannel中提供的lock()/tryLock()方法可以获得文件锁FileLock对象,
lock()和tryLock()方法存在区别:
当lock()试图锁定某个文件时,如果无法得到文件锁,程序将一直阻塞
tryLock()是尝试锁定文件,它将直接返回而不是阻塞,如果获得了文件锁,该方法则返回该文件锁,否则将返回null.
如果FileChannel只是锁定文件的部分内容,而不是锁定全部内容,则可以使用如下的lock()或tryLock()方法。
lock(long positon,long size,boolean shared):对文件从position开始、长度为size的内容加锁。该方法是阻塞的
tryLock(long position,long size,boolean shared):非阻塞式的加锁方法。参数的作用于上一个方法类似
当参数shared为true时,表明该锁是一个共享锁,它将允许多个进程来读取该文件,但是阻止其他进程获得对该文件的排他锁。意思是该锁锁定了该文件,但允许进程对该文件进行性读取。
当share为false时,表明该锁是一个排他锁,它将锁住对该文件的读写。
可以通过调用FileLock的isShared来判断它获取的锁是否为共享锁。
处理完文件后通过FileLock的release()方法释放文件锁。
示例程序如下:
1 package com.edu.ynu.java.learn.collection; 2 3 import java.io.FileNotFoundException; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.nio.channels.FileChannel; 7 import java.nio.channels.FileLock; 8 import java.util.concurrent.TimeUnit; 9 10 public class FileLockTest 11 { 12 public static void main(String[] args) 13 { 14 try (FileChannel channel = new FileOutputStream("a.txt").getChannel();) 15 { 16 // 使用非阻塞方式对指定文件加锁 17 FileLock lock = channel.tryLock(); 18 // 程序暂停10s 19 TimeUnit.SECONDS.sleep(10000); 20 // 释放锁 21 lock.release(); 22 } catch (FileNotFoundException e) 23 { 24 e.printStackTrace(); 25 } catch (IOException e) 26 { 27 e.printStackTrace(); 28 } catch (InterruptedException e) 29 { 30 e.printStackTrace(); 31 } finally 32 { 33 } 34 35 36 } 37 }
上述程序调用TimeUnit.SECOND.sleep(10000)暂停了10秒后才释放文件锁,因此在这10秒之内,其他程序无法对a.txt文件进行修改。
文件锁虽然可以用于控制并发访问,但对于高并发访问的情形,还是推荐使用数据库来保存程序信息,而不是使用文件。
有如下几点需要指出:
1、在某些平台上,文件锁仅仅是建议性的,并不是强制性的。这意味着即使一个程序不能获得文件锁,它也可以对该文件进行读写。
2、在某些平台上,不能同步的锁定一个文件并把它映射到内存中
3、文件锁是由Java虚拟机所持有的,如果两个Java程序使用同一个Java虚拟机运行,则它们不能对同一个文件进行加锁
4、在某些平台上的关闭FileChannel时,会释放Java虚拟机在该文件上的所有锁,因此应该避免对同一个被锁定的文件打开多个FileChannel.
Java7的NIO 2
Java 7对原有的NIO进行了重大的改进,改进主要包括如下两方面的内容
1、提供了全面的文件IO和文件系统访问支持。
2、基于异步Channel的IO。
第一个改进表现为Java 7新增的java.nio.file包及各个子包,
第二个改进表现为Java 7在java.nio.channels包下增加了多个以Asychronous开头的Channel接口和类。
早期Java只提供了一个File类来访问文件系统,但File类的功能比较有限,它不能利用特定文件系统的特性,File所提供的方法的性能也不高,并且大多数方法在出错时仅返回失败,并不会提供异常信息。
NIO.2为了弥补这种不足,引入了一个Path接口,Path接口代表的一个平台无关的平台路径。
NIO.2还提供了Files,Paths两个工具类,其中Files包含了大量的静态的工具方法来操作文件;Paths则包含了两个返回Path的静态工厂方法。
Files和Paths两个工具类非常符合Java一贯的命名风格;Arrays为操作数据的工具类,Collections为操作集合的工具类。
1 package com.edu.ynu.java.learn.nio; 2 3 import java.nio.file.Path; 4 import java.nio.file.Paths; 5 6 public class PathTest 7 { 8 public static void main(String[] args) 9 { 10 // 以当前路径来创建Path对象 11 Path path = Paths.get("."); 12 System.out.println("path里包含的路径数量:" + path.getNameCount()); 13 System.out.println("path的根路径:" + path.getRoot()); 14 // 获取path对应的绝对路径 15 Path absolutePath = path.toAbsolutePath(); 16 System.out.println(absolutePath); 17 // 获取绝对路径的根路径 18 System.out.println("absolutePath的根路径:" + absolutePath.getRoot()); 19 // 获取绝对路径所包含的路径的数量 20 System.out.println("absolutePath里包含的路径数量:" + absolutePath.getNameCount()); 21 System.out.println(absolutePath.getName(3)); 22 // 以多个String来构建Path对象 23 Path path2 = Paths.get("d:", "test", "test1"); 24 System.out.println(path2); 25 } 26 }
getNameCount()方法的返回Path路径所包含的路径名的数量,例如g:\publish\codes调用该方法会返回3。
Files类的使用范例:
1 package com.edu.ynu.java.learn.nio; 2 3 import java.io.FileOutputStream; 4 import java.io.IOException; 5 import java.nio.charset.Charset; 6 import java.nio.file.FileStore; 7 import java.nio.file.Files; 8 import java.nio.file.Paths; 9 import java.util.ArrayList; 10 import java.util.List; 11 12 public class FileTest 13 { 14 public static void main(String[] args) 15 { 16 try 17 { 18 // 复制文件 19 Files.copy(Paths.get("src//com//edu//ynu//java//learn//nio//FileTest.java"), new FileOutputStream("c.txt")); 20 // 判断FilesTest.java文件是否为隐藏文件 21 System.out.println("FilesTest.java是否为隐藏文件: " + Files.isHidden(Paths.get("src//com//edu//ynu//java//learn//nio//FileTest.java"))); 22 // 一次性读取FilesTest.java文件的所有行 23 List<String> lines = Files.readAllLines(Paths.get("src//com//edu//ynu//java//learn//nio//FileTest.java"), Charset.forName("utf8")); 24 System.out.println(lines); 25 // 判断指定文件的大小 26 System.out.println("FileTest.java 的大小: " + Files.size(Paths.get("src//com//edu//ynu//java//learn//nio//FileTest.java"))); 27 List<String> poem = new ArrayList<>(); 28 poem.add("举杯邀明月"); 29 poem.add("对影成三人"); 30 // 直接将多个字符串内容写入指定的文件中 31 Files.write(Paths.get("src//com//edu//ynu//java//learn//nio//pome.txt"), poem, Charset.forName("UTF8")); 32 // 使用Java 8新增的Stream API列出当前目录下所有文件和子目录 33 Files.list(Paths.get(".")).forEach(path -> System.out.println(path)); 34 // 使用Java 8新增的Stream API读取文件内容 35 Files.lines(Paths.get("src//com//edu//ynu//java//learn//nio//FileTest.java"), Charset.forName("UTF8")).forEach(line -> System.out.println(line)); 36 37 FileStore fileStore = Files.getFileStore(Paths.get("C:")); 38 //判断C盘的总内容、可用空间 39 System.out.println("C:共有空间:" + fileStore.getTotalSpace()); 40 System.out.println("C:可用空间:" + fileStore.getUsableSpace()); 41 // System.out.println("C:未用空间" + fileStore.getUnallocatedSpace()); 42 } catch (IOException e) 43 { 44 e.printStackTrace(); 45 } 46 } 47 }
上面程序中的粗体字简单示范了Files工具类的用法,从上面的程序可以看出,Files类是一个高度封装的工具类,提供了大量的工具方法来完成文件复制,读取文件内容,写入文件内容等功能
Java 8进一步增强了Files工具类的功能,允许开发者使用Stream API来操作文件目录和文件内容
Files工具类提供了如下方法来遍历文件和子目录:
walkFileTree(Path start,FileVisitor<? super Path> visitor) 遍历start路径下所有的文案和子目录
1 walkFileTree(Path start,Set<FileVisitOption> options,int maxDepth,FileVisitor<? super Path> visitor):与上一个方法的功能类似。该方法最多遍历maxDepth深度的文件
在walkFileTree()方法会自动遍历start路径下的所有的文件和子目录,遍历文件和子目录下都会“触发”FileVisitor中相应的方法。
1 FileVisitResult postVisitDirectory(T dir,IOException exec): 访问子目录之后触发该方法 2 FileVisitResult preVisitDirectory(T dir,IOException exec): 访问子目录之前触发该方法 3 FileVisitResult visitFile(T file,BasicFileAttributes attrs): 访问file文件时触发该方法 4 FileVisitResult visitFileFailed(T file,IOException exc): 访问file文件失败时触发该方法
上面4个方法都返回一个FileVisitResult对象,它是一个枚举类,代表了访问之后的后续行为
FileVIsitResult定义了后续行为:
1、CONTINUE:代表"继续访问"的后续行为
2、SKIP_SIBLINGS:代表"继续访问"的后续行为,但不访问该文件或目录的兄弟文件或目录
3、SKIP_SUBTREE:代表"继续访问"的后续行为,但不访问该文件或目录的子目录树
4、TERMINATE:代表“中止访问”的后续行为
在实际的编程中可以通过SimpleFileVisitor(FileVisitor的实现类)来实现自己的“文件访器”,可以根据需要、选择性的重写指定的方法
1 package com.edu.ynu.java.learn.nio; 2 3 import java.io.IOException; 4 import java.nio.file.*; 5 import java.nio.file.attribute.BasicFileAttributes; 6 7 public class FileVisitorTest 8 { 9 public static void main(String[] args) 10 { 11 try 12 { 13 Files.walkFileTree(Paths.get("D:", "development", "developData", "ali-repository"), new SimpleFileVisitor<Path>() 14 { 15 // 访问文件时触发该方法 16 @Override 17 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException 18 { 19 System.out.println("正在访问:" + file + "文件"); 20 // 找到了antlr-2.7.2.jar文件 21 if (file.endsWith("antlr-2.7.2.jar")) 22 { 23 System.out.println("--已经找到目标文件--"); 24 return FileVisitResult.TERMINATE; 25 } 26 return FileVisitResult.CONTINUE; 27 } 28 29 // 开始访问目录时触发该方法 30 @Override 31 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException 32 { 33 System.out.println("正在方问:"+dir+"路径"); 34 return FileVisitResult.CONTINUE; 35 } 36 }); 37 } catch (IOException e) 38 { 39 e.printStackTrace(); 40 } finally 41 { 42 } 43 } 44 }
使用WatchService监控文件变化
通常通过程序监听文件的变化,思路是启动一条后台程序,然后用后台程序每隔一段时间去“遍历”一次指定的目录文件,如果
NIO.2的Path类提供了如下方法来来监听文件系统系统的变化
1 register(WatchService warcher,WatcherEvent.Kind<?>...events):用watcher监听该path代表的目录下的文件变化。events参数指定要监听哪些类型的事件
WatchService代表了一个文件系统监听服务,它负责监听path代表的目录下的文件变化。
一旦使用register()方法完成注册之后,接下来就可调用WatchService的如下方法来获取被监听目录的文件变化事件
1 WatchKey poll():获取下一个WatchKey,如果没有WatchKey发生就立即返回null
2 WatchKey poll(long timeout,TimeUnit unit):尝试等待timeout时间获取下一个WatchKey
3 WatchKey take():获取下一个WatchKey,如果没有WatchKey发生就一直等待。
如果程序需要一直监控,则应该选择使用take()方法;如果程序只需要监控指定时间,则可考虑是一个poll()方法
1 package com.edu.ynu.java.learn.nio; 2 3 import java.io.IOException; 4 import java.nio.file.*; 5 6 public class WatchServiceTest 7 { 8 public static void main(String[] args) 9 { 10 try 11 { 12 WatchService watchService = FileSystems.getDefault().newWatchService(); 13 // 为C:盘根路径注册监听 14 Paths.get("C:/").register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); 15 while (true) 16 { 17 WatchKey key = watchService.take(); 18 for (WatchEvent<?> event : key.pollEvents()) 19 { 20 System.out.println(event.context() + " 文件发生了 " + event.kind() + " 事件"); 21 } 22 // 重设WatchKey 23 boolean valid = key.reset(); 24 if (!valid) 25 { 26 break; 27 } 28 } 29 } catch (IOException e) 30 { 31 e.printStackTrace(); 32 } catch (InterruptedException e) 33 { 34 e.printStackTrace(); 35 } finally 36 { 37 } 38 39 } 40 }
访问文件属性
Java 7的NIO.2在java.nio.fileattribute包下提供了大量的工具类,这些工具类猪獒分为如下两类:
1、XxxAttributeView:代表某种文件属性的“视图”
2、XxxAttributes:代表某种文件属性的“集合”,程序一般通过XxxAttributeView对象来获取XxxAttributes.
FileAttributeView是其他XxxAttributeView的父接口
AclFileAttributeView:通过AclFileAttributeView,开发者可以为特定的文件设置ACL(Access Control List)及文件所有者属性。
BasicFileAttributeView:它可以获取或修改文件的基本属性
DosFileAttributeView:它主要用于获取或修改文件DOS相关属性
FileOwnerAttributeView:它主要用于获取或修改的文件的所有者
PosixFileAttributeView:它主要用于获取或修改POSIX(Protable Operating System Interface of UNIX)属性,它的readAttributes()方法返回一个PosixFileAttributes对象,该对象可用于获取或修改文件的所有者,组所有者,访问权限信息。这个View只在UNIX,Linux等系统上有用。
UserDefinedFileAttributeView:它可以让开发者为文件设置一些自定义属性。
1 package com.edu.ynu.java.learn.nio; 2 3 import java.io.IOException; 4 import java.nio.ByteBuffer; 5 import java.nio.charset.Charset; 6 import java.nio.file.FileSystems; 7 import java.nio.file.Files; 8 import java.nio.file.Path; 9 import java.nio.file.Paths; 10 import java.nio.file.attribute.*; 11 import java.util.Date; 12 import java.util.List; 13 14 public class AttributeViewTest 15 { 16 public static void main(String[] args) 17 { 18 try 19 { 20 // 获取将要操作的文件 21 Path testPath = Paths.get("src\\com\\edu\\ynu\\java\\learn\\AttributeViewTest.java"); 22 // 获取访问基本属性的BasicFileAttributeView 23 BasicFileAttributeView basicFileAttributeView = Files.getFileAttributeView(testPath, BasicFileAttributeView.class); 24 25 BasicFileAttributes basicFileAttributes = basicFileAttributeView.readAttributes(); 26 27 System.out.println("创建时间:" + new Date(basicFileAttributes.creationTime().toMillis())); 28 System.out.println("最后访问时间:" + new Date(basicFileAttributes.lastAccessTime().toMillis())); 29 System.out.println("文件大小:" + basicFileAttributes.size()); 30 31 // 获取访问文件属主信息的FileOwerAttributeView 32 FileOwnerAttributeView fileOwnerAttributeView = Files.getFileAttributeView(testPath, FileOwnerAttributeView.class); 33 // 获取该文件所属的用户 34 System.out.println(fileOwnerAttributeView.getOwner()); 35 // 获取系统中的guest对应的用户 36 UserPrincipal user = FileSystems.getDefault().getUserPrincipalLookupService().lookupPrincipalByName("guest"); 37 // 修改用户 38 fileOwnerAttributeView.setOwner(user); 39 UserDefinedFileAttributeView userDefinedFileAttributeView = Files.getFileAttributeView(testPath, UserDefinedFileAttributeView.class); 40 List<String> attrNames = userDefinedFileAttributeView.list(); 41 for (String name : attrNames) 42 { 43 ByteBuffer buf = ByteBuffer.allocate(userDefinedFileAttributeView.size(name)); 44 userDefinedFileAttributeView.read(name, buf); 45 buf.flip(); 46 String value = Charset.defaultCharset().decode(buf).toString(); 47 System.out.println(name + "--->" + value); 48 } 49 // 添加一个自定义属性 50 userDefinedFileAttributeView.write("发行者", Charset.defaultCharset().encode("疯狂Java联盟")); 51 // 获取访问DOS属性的DosFileAttributeView 52 DosFileAttributeView dosFileAttributeView = Files.getFileAttributeView(testPath, DosFileAttributeView.class); 53 // 将文件设置为隐藏、只读 54 dosFileAttributeView.setHidden(true); 55 dosFileAttributeView.setReadOnly(true); 56 } catch (IOException e) 57 { 58 e.printStackTrace(); 59 } finally 60 { 61 62 } 63 64 } 65 }