Java NIO详解

Java NIO相对于Java IO而言的,Java NIO(no blocking)非阻塞IO,与Java IO有如下不同:

  1. Java  IO是阻塞的IO,当BuffreReader读取输入流中的数据时,如果没有读到有效的数据,程序将在此处阻塞该线程的执行,同样使用InputStream的read()方法从流中读取数据时,如果数据源中没有数据,它也会阻塞该线程。而java NIO是非阻塞的。
  2. 不仅如此,传统的输入流、输出流都是通过字节的移动来处理的(即使不直接去处理字节流,但底层的实现还是依赖于字节处理),也就说,面向流的输入/输出系统一次只能处理一个字节,效率不高。但是对于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 }

 

转载于:https://www.cnblogs.com/david-jack-steven/p/8416431.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值