NIO 01 (转)
本文转自:http://www.cnblogs.com/littlehann/p/3720396.html
目录
1. NIO、NIO.2简介 2. NIO中的关键技术
1. NIO、NIO.2简介
Java中的输入流、输出流都是阻塞式的输入、输出。不仅如此,传统的输入流、输出流都是通过字节的移动来处理的(即使是字符流,在底层也是通过字节流来进行处理的),也就是说,面向流的输入/输出系统一次只能处理一个字节,因此面向流的输入/输出系统通常效率不高。
从JDK1.4开始,java提供了一系列改进的输入/输出处理的新功能,这些功能被统称为新IO(New IO,简称NIO),新增了许多用于处理输入/输出的类,这些类都被放在java.nio包以及子包下,并且对原java.io包中的许多类都以NIO为基础进行了改写。
NIO和传统的IO有相同的目的,都是用于进行输入/输出,但新IO采用内存映射文件的方式来处理输入/输出,新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了
(模拟了操作系统上的虚拟内存的概念),通过这种方式来进行输入/输出比传统的输入/输出要快得多 http://baike.baidu.com/view/394293.htm
java.nio的包层次结构如下
1. Buffer java.nio.Buffer: Buffer是一个对象,它包含一些要写入或者刚读出的数据。在NIO中加入Buffer对象,体现了NIO库与传统I/O的一个重要区别。在面向流的I/O中,我们将数据直接写入或者
将数据直接读到Stream对象中。而在NIO库中,所有数据都是用缓冲区处理的。发送到Channel中的所有对象都必须首先放到Buffer中,而从Channel中读取的数据也必须先放到Buffer中。任何
时候访问NIO中的数据,我们都是将它放到缓冲区中。缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。这些Buffer类都没有提供构造器,而是通过具体子类: XxxBuffer.allocate(int capaticy); 来创建一个容量为capacity的XxxBuffer对象 1.1) ByteBuffer: 字节数组 1.1.1) MappedByteBuffer: 用于表示Channel将磁盘文件的部分或者全部内容映射到内存后得到的结果 1.2) ByteOrder: 字节枚举数组 1.3) CharBuffer: 字符数组 1.4) DoubleBuffer: Double数组 1.5) FloatBuffer: Float数组 1.6) IntBuffer: Int数组 1.7) LongBuffer: Long数组 1.8) ShortBuffer: Short数组 2. Channel Channel类似于传统的流对象,但与传统的流对象有两个主要区别:
1) Channel可以直接将指定文件的部分或者全部直接映射成Buffer。2) 程序不能像访问流那样直接访问Channel中的数据(包括读取、写入),Channel只能和Buffer进行交互。即,如果要从Channel中取得数据,必须先使用Buffer从Channel中取出一些数据,然后让程序再从Buffer中取得这些数据。写入也一样。 和Buffer一样,所有的Channel都不应该通过构造器来直接创建吗,而是通过传统的节点InputStream、OutputStream的getChannel()方法来返回对应的Channel,不同的节点流获得的Channel是不一样的。从这个角度理解,Channel也可以理解为对节点流的一种包装流,这也是Java中著名的装饰器设计模式的一种体现。 我们按照Channel的功能对Channel的类层次进行分类 1) 文件操作 1.1) FileChannel: 文件读写的通道 1.1.1) FileInputStream调用getChannel返回的是只能读的FileChannel 1.1.2) FileOutputStream调用getChannel返回的是只能写的FileChannel 1.2) AsynchronousFileChannel: NIO.2新增了异步文件操作方式 1.3) FileLock: 文件锁、互斥读写 2) 非阻塞式输入输出 2.1) SelectableChannel 2.2) Selector 2.3) SelectionKey 3) java.nio.channels.Pipe: 线程通信 3.1) Pipe.SinkChannel: PipedInputStream调用getChanel()返回的是Pipe.SinkChannel 3.2) Pipe.SourceChannel: PipedOutputStream调用getChanel()返回的是Pipe.SourceChannel 4) NetworkChannel: 支持TCP网络通信的Channel 4.1) ServerSocketChannel 4.2) AsynchronousServerSocketChannel 4.3) SocketChannel 4.4) AsynchronousSocketChannel 5) DatagramChannel: 支持UDP网络通信的Channel 3. Charset 计算机底层是没有文本文件的、图片之分的,它只是记录每个文件的二进制数据。当需要保存文本文件时,程序必须先把文件中的每个"字符"翻译成二进制序列,当需要读取文本文件时,程序必须把二进制序列转换成一个个的"字符"。java默认使用Unicode字符集,为了解决可能出现的乱码等问题。JDK 1.4提供了Charset来处理字节序列和字符序列(字符串)之间的转换关系
(单字节、双字节),该类包含了用于创建解码器、编码器的方法。 1) Charset 一旦知道了字符集的别名之后,程序就可以调用Charset的forName()方法来创建对应的Charset对象,forName()方法的参数就是相应字符集的别名 2) CharsetEncoder 获得了Charset对象之后,就可以通过该对象的newEncoder()方法来返回对应的编码器,调用CharsetEncoder的encode()方法可以完成字符序列(CharBuffer、String)到字节序列
(ByteBuffer)的转换 3) CharsetDecoder 获得了Charset对象之后,就可以通过该对象的newDecoder()方法来返回对应的解码器,调用CharsetDecoder的decode()方法可以完成字节序列(ByteBuffer)到字符序列(CharBuffer、String)的转换最常用的字符集对应的Charset对象 4) StandardCharsets java7新增了一个StandardCharsets类,该类里包含了ISO-8859-1、UTF-8、UTF-16等静态Field,这些静态Field代表了 4. java.nio.channels.spi 主要包含与Channel相关的服务提供者编程接口 5. java.nio.charset.spi包 包含与字符集相关的服务提供者编程接口 6. java.nio.file包 早期的Java只提供了一个File类来访问文件系统,但File类的功能比较有限,它不能利用特定文件系统的特性,File所提供的方法的性能也不高。而且,其大多数方法在出错时仅返回失败,并不会提供异常信息 1) Path: 代表了一个平台无关的路径 2) Paths: 包含了2个返回Path的静态工厂方法 2.1) Path get(String first, String... more): 将给定的多个字符串进行拼接 2.2) get(URI uri) 3) Files: 提供了大量的高性能方法来操作文件 4) FileVisitor 使用FileVisitor来遍历文件和目录,在编程时可以通过继承SimpleFileVisitor来实现自己的"文件访问器",这样就可以根据需要,选择性地重写指定方法 5) java.nio.file.attribute 如果程序希望获取关于文件、目录的更多的和特定操作系统、磁盘个性有关的属性,可以使用NIO.2提供的java.nio.file.attribute工具类来实现 5.1) FileAttribute 代表某种文件属性的"集合",程序一般通过XxxAttributeView对象来获取XxxAttribute 5.1.1) BasicFileAttribute 5.1.2) DosFileAttribute 5.1.3) PosixFileAttribute 5.2) FileAttributeView 代表某种文件属性的"视图" 5.2.1) AclFileAttributeView 为特定的文件设置ACL(Access Control List)、及"文件所有者属性" 5.2.2) BasicFileAttributeView 获取或修改文件的基本属性,包括: 1) 最后修改时间 2) 最后访问时间 3) 创建时间 4) 大小 5) 是否为目录 6) 是否为符号链接等 5.2.3) DosFileAttributeView 获取或修改文件DOS相关信息,比如: 1) 是否只读 2) 是否隐藏 3) 是否为系统文件 4) 是否为存档文件等 5.2.4) FileOwnerAttributeView 获取或修改文件的所有者 5.2.5) FileStoreAttributeView 5.2.6) PosixFileAttributeView 获取后修改POSIX(Portable Operation System Interface of INIX)属性,用于修改: 1) 文件的所有者 2) 组所有者 3) 访问权限信息(即UNIX的chmod命令负责干的事情) 这个View只在UNIX、Linux等系统上有用 5.2.7) UserDefinedFileAttributeView 可以让开发者为文件设置一些自定义属性
2. NIO中的关键技术
0x1: Buffer
Buffer中有几个重要的概念:
1. 容量(capacity): 缓冲区的容量(capacity)表示该Buffer的最大数据容量,即最多可以存储多少数据。缓冲区的容量不可能为负值,创建后不能改变
2. 界限(limit): 第一个不应该被读出、或者写入的缓冲区位置索引。即位于limit后的数据既不可被读、也不可被写
3. 位置(position): 用于指明下一个可以被读出、或者写入的缓冲区位置索引(类似IO流中的记录指针)。当使用Buffer从Channel中读取数据时,position的值等于已经读取了多少数据。
4. Buffer里还支持一个可选的标记(mark,类似于传统IO流中的mark),Buffer允许直接将position定位到该mark处
0 <= mark <= position <= limit <= capacity
Buffer类主要作用就是装入数据、然后输出数据,类中包含几个重要的方法:
1. 开始时Buffer的position为0,limit为capacity,程序可以通过put()方法向Buffer中放入一些数据,每放入一些数据,Buffer的position相应地向后移动一些位置(记住,position
是一个记录指针) 2. put() 1) 相对(Relative)写入: 从Buffer的当前position处开始写入数据,然后将位置(position)的值按处理元素的个数增加 2) 绝对(Absotute)写入: 直接根据索引向Buffer中指定位置开始写入数据,但并不影响位置(position)的值 3. get() 1) 相对(Relative)读取: 从Buffer的当前position处开始读取数据,然后将位置(position)的值按处理元素的个数增加 2) 绝对(Absotute)读取: 直接根据索引向Buffer中指定位置开始读取数据,但并不影响位置(position)的值 4. flip()
当Buffer装入数据结束后,调用Buffer的flip()方法,该方法将limit设置为position所在的位置,并将position设置为0(这里要重点注意,相当于将整个Buffer收缩了)。也就是说,
Buffer调用flip()方法之后,Buffer就为输出数据做好了准备 5. clear()
当Buffer输出数据结束后,Buffer调用clear()方法,clear()方法不是清空Buffer的数据,它仅仅将position设置为0,将limit设置为capacity,这样就为再次向Buffer中装入数据
做好了准备(注意,此时Buffer中的数据被檫除了吗?没有) 我们可以类比思考: Buffer的这种数据读取机制是一种典型的"基于数据结构的数据操作方式",和操作系统的栈内存操作方式从原理上是一致的
code:
import java.nio.*; 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()); } }
0x2: Channel
Channel中最常用的方法是:
1. map()
用于将Channel对应的部分、或全部数据映射成Buffer
MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)
1) FileChannel.MapMode: 执行映射时的模式
1.1) PRIVATE: private (copy-on-write,写时复制) mapping
1.2) READ_ONLY: Mode for a read-only mapping
1.3) READ_WRITE: Mode for a read/write mapping
2) position、long size决定哪些数据被映射成Buffer
2. read()
用于从Buffer中读取数据
3. write()
用于向Buffer中写入数据
code:
import java.io.*; import java.nio.*; import java.nio.channels.*; import java.nio.charset.*; 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("out.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(); } } }
有时候,如果遇到Channel对应的文件太大,无法一次性全部Map到内存中、或者使用map()方法一次将所有的文件内容映射到内存中会引起性能下降,也可以使用Channel的Buffer的"分批逐段读取"的方法
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
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(64);
// 将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();
}
}
}
}
可以看到,代码每次读取数据后调用flip()方法将没有数据的区域"封印"起来,避免程序从Buffer中读取null值
0x3: Charset
import java.nio.*; import java.nio.charset.*; 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)); } }
实际上,Charset类也提供了如下3个方法,无须创建相应的解码、编码器就可以直接进行编码转换,是否创建编码、解码器的差别我觉得和在进行和正则有关的编程的时候是否创建正则表达式的编译后对象是一个原理,复用性、性能的原因
1. CharBuffer decode(ByteBuffer bb)
将ByteBuffer中的字节序列转换成字符序列的快捷方法
2. ByteBuffer encode(CharBuffer cb)
将ByteBuffer中的字节序列转换成字符序列的快捷方法
3. ByteBuffer encode(String str)
将CharBuffer中的字符序列转换成字节序列的快捷方法
0x4: FileLock
文件锁在操作系统中是很平常的事情,如果多个程序需要并发修改同一个文件时,程序之间需要通过锁机制来进行并发控制。从JDK 1.4的NIO开始,java开始提供文件锁的支持。在NIO中,FileChannel调用lock()、tryLock()方法返回的FileLock可以支持文件锁功能,这两种锁的特性如下
1. lock() FileLock lock(long position, long size, boolean shared) 1) position、size表明对文件的一部分进行加锁,这是一种细粒度的锁机制 2) 参数shared 2.1) true: 表明是一个"共享锁",它将允许多个进行来"读取(注意: 只是读取)",但阻止其他进程获得对该文件的排它锁 2.2) false(默认值): 表明是一个"排它锁",它将锁住对该文件的读写 程序可以通过FileLock的isShared来判断它获得的锁是否为共享锁 当调用lock()试图锁定某个文件时,如果无法获得文件锁,该方法将一直阻塞 2. tryLock() tryLock()是"尝试"锁定文件,它将立即返回而不是阻塞,如果获得了文件锁,该方法返回文件锁,否则返回null
code:
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
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();
}
}
}
从以上代码可以看到,在这10秒内,其他程序无法对a.txt进行修改
0x5: Path、Paths、Files
path
import java.io.*; import java.net.*; import java.nio.file.*; 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); } }
Files
import java.nio.file.*; import java.nio.charset.*; import java.io.*; import java.util.*; public class FilesTest { public static void main(String[] args) throws Exception { // 复制文件 Files.copy(Paths.get("FilesTest.java") ,new FileOutputStream("out.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")); FileStore cStore = Files.getFileStore(Paths.get("C:")); // 判断C盘的总空间,可用空间 System.out.println("C:共有空间:" + cStore.getTotalSpace()); System.out.println("C:可用空间:" + cStore.getUsableSpace()); } }
从代码可以看出,Files类是一个高度封装的工具类,它提供了大量的工具方法来完成文件复制、读取文件内容、写入文件内容等功能,简化原本的文件IO代码量
0x6: FileVisitor
在传统的java.io.File文件类的模式下,如果我们想要遍历指定目录下的所有文件和目录,则只能使用递归进行遍历,有了NIO的Files类的帮助,程序员可以采用更优雅的方式来遍历文件和子目录,Files类提供了如下两个方法来遍历文件和子目录
1. walkFileTree(Path start, FileVisitor<? super Path> visitor); 遍历start路径下的所有文件和子目录 2. walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor); 作用和1一样,增加了控制遍历深度的maxDepth参数
这两个方法都需要FileVisitor参数,FileVisitor代表一个"文件访问器",walkFileTree()方法会自动遍历start路径下的所有文件和子目录,遍历文件和子目录都会"触发"(回调的思想)FileVisitor中相应的方法
1. FileVisitResult postVisitDirectory(T dir, IOException exc) 访问子目录之后触发该方法 2. FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) 访问子目录前触发该方法 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 代表"终止访问"的后续行为
code:
import java.io.*; import java.nio.file.*; import java.nio.file.attribute.*; public class FileVisitorTest { public static void main(String[] args) throws Exception { // 遍历g:\publish\codes\15目录下的所有文件和子目录 Files.walkFileTree(Paths.get("D:", "学习资料", "视频学习资料", "JAVA学习", "疯狂java", "code", "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; } }); } }
0x7: 使用WatchService来监控磁盘文件变动
NIO.2的Path类提供了如下的方法来监听文件系统的变动
1. WatchKey register(WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers) 1) watcher: 代表一个文件系统监听服务,它监听该path代表的目录下的文件变化 2) events参数指定要监听哪些类型的事件 2.1) ENTRY_CREATE 2.2) ENTRY_DELETE 2.3) ENTRY_MODIFY 2.4) OVERFLOW
一旦使用register()方法完成注册之后,接下来就可以调用WatchService的方法来获取被监听目录的文件变化事件
1. WatchKey poll() 获取下一个WatchKey,如果没有WatchKey发生就立即返回null 2. WatchKey poll(long timeout, TimeUnit unit) 尝试等待timeout时间(最大延时)去获取下一个WatchKey 3. WatchKey take() 获取下一个WatchKey,如果没有WatchKey发生就一直阻塞等待
code:
import java.io.*; import java.nio.file.*; import java.nio.file.attribute.*; 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; } } } }
0x8: 深度、高效、全面地访问文件属性
import java.io.*; import java.util.*; import java.nio.file.*; import java.nio.*; import java.nio.charset.*; import java.nio.file.attribute.*; public class AttributeViewTest { public static void main(String[] args) throws Exception { // 获取将要操作的文件 Path testPath = Paths.get("AttributeViewTest.java"); // 获取访问基本属性的BasicFileAttributeView BasicFileAttributeView basicView = Files.getFileAttributeView(testPath , BasicFileAttributeView.class); // 获取访问基本属性的BasicFileAttributes BasicFileAttributes basicAttribs = basicView.readAttributes(); // 访问文件的基本属性 System.out.println("创建时间:" + new Date(basicAttribs.creationTime().toMillis())); System.out.println("最后访问时间:" + new Date(basicAttribs.lastAccessTime().toMillis())); System.out.println("最后修改时间:" + new Date(basicAttribs.lastModifiedTime().toMillis())); System.out.println("文件大小:" + basicAttribs.size()); // 获取访问文件属主信息的FileOwnerAttributeView FileOwnerAttributeView ownerView = Files.getFileAttributeView(testPath, FileOwnerAttributeView.class); // 获取该文件所属的用户 System.out.println(ownerView.getOwner()); // 获取系统中guest对应的用户 UserPrincipal user = FileSystems.getDefault().getUserPrincipalLookupService().lookupPrincipalByName("guest"); // 修改用户 ownerView.setOwner(user); // 获取访问自定义属性的FileOwnerAttributeView UserDefinedFileAttributeView userView = Files.getFileAttributeView(testPath, UserDefinedFileAttributeView.class); List<String> attrNames = userView.list(); // 遍历所有的自定义属性 for (String name : attrNames) { ByteBuffer buf = ByteBuffer.allocate(userView.size(name)); userView.read(name, buf); buf.flip(); String value = Charset.defaultCharset().decode(buf).toString(); System.out.println(name + "--->" + value) ; } // 添加一个自定义属性 userView.write("发行者", Charset.defaultCharset().encode("疯狂Java联盟")); // 获取访问DOS属性的DosFileAttributeView DosFileAttributeView dosView = Files.getFileAttributeView(testPath, DosFileAttributeView.class); // 将文件设置隐藏、只读 dosView.setHidden(true); dosView.setReadOnly(true); } }