NIO:New IO
Java新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包:主要包含提供字符集服务的相关类
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对象:
static XxxBuffer allocate(int capacity):创建一个容量为capacity的XxxBuffer对象
使用较多的是ByteBuffer和CharBuffer。其中ByteBuffer类还有一个子类:MappedByteBuffer,它用于表示Channel将磁盘文件的部分或全部内容映射到内存中后得到的结果,通常MappedByteBuffer对象由Channel的map()方法返回
Buffer三个重要概念:容量(capacity)、界限(limit)、位置(position)
容量(capacity):缓冲区的容量(capacity)表示该Buffer的最大数据容量,即最多可以存储多少数据。缓冲区的容量不可能为负值,创建后不能改变
界限(limit)界限(limit):第一个不应该被读写或者写入的缓冲区位置索引。也就是说,位于limit后的数据既不可被读,也不可被写
位置(position):用于指明下一个可以被读出或者写入的缓冲区位置索引(类似于IO流中的记录指针)。当使用Buffer从Channel中读取数据时,position的值恰好等于已经读到了多少数据。当刚刚新建一个Buffer对象时,其position为0;如果从Channel中读取了2个数据到该Buffer中,则position为2,指向Buffer中的第三个(第1个位置的索引为0)位置
标记(mark):Buffer里还支持一个可选的标记(mark,类似于传统IO流中的mark),Buffer允许直接将position定位到该mark处。
0 <= mark <= position <= limit <= capacity
Buffer的主要作用就是装入数据,然后输出数据,开始时Buffer的position为0,limit为capacity,程序可通过put()方法向Buffer中放入一些数据(或从channel获取数据),每放入一些数据,position向后移动一些位置
当Buffer装入数据结束后,调用filp()方法,该方法将limit设置为position所在位置,将position设置为0。这样使得从Buffer中读取数据总是从0开始。读完所有装入的数据即结束,也就是说,Buffer调用filp后,Buffer为输出数据做好了准备
当Buffer输出数据结束后,调用clear方法。将position置为0,将limit置为capacity,这样为再次向Buffer中装载数据做好准备
Buffer抽象类常用方法:
int capacity():返回Buffer的capacity大小
boolean hasRemaining():判断当前位置(position)和界限(limit)之间是否有元素可供处理
int limit():返回Buffer的界限(limit)的位置
Buffer limit(int newLimit):重新设置界限(limit)的值,并返回一个具有新的limit的缓冲区对象
int position():返回Buffer的position值
Buffer position(new position):设置Buffer的position值,并返回position被修改后的Buffer对象
int remaining():返回当前位置和界限(limit)之间的元素个数
Buffer reset():将位置(position)转到mark所在的位置
Buffer rewind():将位置(position)设置为0,取消设置的mark
put()和get()方法,用于向Buffer中放入数据和从Buffer中取出数据。当使用put()和get()。Buffer既支持对单个数据的访问,也支持对批量数据的访问(以数组作为参数)
当使用put()和get()来访问Buffer中的数据时,分为相对和绝对两种:
相对(Relative):从Buffer当前位置读取或写入数据,然后将位置(position)的值按处理元素个数增加
绝对(Absolute):直接根据索引来向Buffer中读取或写入数据,使用绝对方式来访问Buffer里的数据,并不会影响position的值
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());
}
}
运行结果:
capacity: 8
limit: 8
position: 0
加入三个元素后,position = 3
执行flip()后,limit = 3
position = 0
第一个元素(position=0):a
取出一个元素后,position = 1
执行clear()后,limit = 8
执行clear()后,position = 0
执行clear()后,缓冲区内容并没有被清除:第三个元素为:c
执行绝对读取后,position = 0
代码①:新分配的CharBuffer对象:
代码②:向Buffer中放入3个对象后
代码③:执行Buffer的flip()方法后
代码⑤:执行clear()后的Buffer
使用Channel
Channel类似于传统的流对象,但与传统的流对象有两个主要区别:
Channel可以直接将指定文件的部分或全部直接映射成Buffer
程序不能直接访问Channel中的数据,包括读、写入都不行,Channel只能与Buffer进行交互。也就是说,如果要从Channel中取得数据,必须先用Buffer从Channel中取出一些数据,然后让程序从Buffer中取出这些数据;如果要将程序中的数据写入Channel,一样先让程序将谁放入Buffer中,程序再将Buffer里的数据写入Channel中
所有的Channel都不应该通过构造器来直接创建,而是通过传统的节点InputStream、OutputStream的getChannel()方法来返回对应的Channel,不同的节点流获得的Channle不一样
Channel中最常用的三类方法是map()、read()和write()
map()方法用于将Channel对应的部分或全部数据映射成ByteBuffer;而read()或write()方法都有一系列重载形式,这些方法用于从Buffer中读取数据或向Buffer里写入数据
map方法的方法签名为:MappedByteBuffer map(FileChannel.MapMode mode, long position, long size),第一个参数执行映射时的模式,分别有只读,读写模式,而第二个,第三个参数用于控制将Channel的哪些数据映射成ByteBuffer
以下是直接将FileChannel的全部数据映射成ByteBuffer的效果的代码。使用FileInputStream、FileOutputStream来获取FileChannel。代码①直接将指定Channel中的全部数据映射成ByteBuffer,代码②直接将整个ByteBuffer的全部数据写入一个输出FileChannel中,完成文件复制。使用Charset类和CharsetDecoder类将ByteBuffer转换成CharBuffer
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("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();
}
}
}
RandomAccessFile中也包含getChannel()方法,返回的FileChannel()读写类型取决于RandomAccessFile打开文件的模式。以下代码将对a.txt文件的内容进行复制,追加到该文件后面:
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
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);
}
}
}
randomChannel.position(f.length()); 代码将Channel的记录指针移动到该Channel的最后,从而可以让程序将指定ByteBuffer的数据追加到该Channel后面。每次运行上面程序,都会把a.txt文件的内容复制一份,并将全部内容追加到该文件的后面
使用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(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();
}
}
}
}
Buffer提供了flip()和clear()两个方法,每次读取数后调用flip()方法将没有数据的区域“封印”起来,避免程序从Buffer中取出null值;数据取出后立即调用clear()方法将Buffer的position设0,为下一次读取数据做准备
字符集和Charset
编码Encode:把明文的字符序列换成计算机理解的二进制序列
解码Decode:把二进制序列转换成明文字符串
Java默认使用Unicode字符集,但很多操作系统并不使用Unicode字符集,那么当从系统中读取数据到Java程序中时,就可能出现乱码等问题
JDK1.4提供了Charset来处理字节序列和字符序列(字符串)之间的转换关系,该类包含了用于创建编码和解码的的方法,还提供了获取所有Charset所支持的字符集的方法,Charset类是不可变的
availableCharset()的静态方法用于获取当前JDK所支持的所有字符集
import java.nio.charset.*;
import java.util.*;
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));
}
}
}
常用字符串别名:
GBK:简体中文字符串
BIG5:繁体中文字符串
ISO-8859-1:ISO拉丁字母表No.1
UTF-8:8位UCS转换格式
UTF-16BE:16位的UCS转换格式,Big-endian(最低地址存放高位字节)字节顺序
UTF-16LE:16位的UCS转换格式,Little-endian(最高地址存放低位字节)字节顺序
UTF-16:16位的UCS转换格式,字节顺序由可选的字节顺序标记来标识
调用Charset的forName()方法创建字符串别名对应的Charset对象,forName()方法的参数就是相应字符集的别名:
Charset cs = Charset.forName("ISO-8859-1");
获得Charset对象之后,通过该对象的newDecoder()、newEncoder()方法分别返回CharsetDecoder和CharsetEncoder对象,代表该Charset的解码器和编码器。调用CharsetDecoder的decode()方法可以将ByteBuffer(字节序列)转换成CharBuffer(字符序列),调用CharsetEncoder的encode()方法可以将CharBuffer或String(字符序列)转换成ByteBuffer(字节序列)
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类提供了如下三个方法:
CharBuffer decode(ByteBuffer bb):将ByteBuffer中的字节序列转换成字符序列的便捷方法
ByteBuffer encode(CharBuffer cb):将CharBuffer中的字节序列转换成字符序列的便捷方法
ByteBuffer encode(String str):将String中的字节序列转换成字符序列的便捷方法
获取Charset对象后,如果仅仅需要进行简单的编码、解码操作,实则无须创建CharsetDecoder和CharsetEncoder对象,直接调用Charset的encode()和decode()方法进行编码、解码即可
文件锁
如果多个运行的程序需要并发修改同一个文件时,程序之间需要某种机制来进行通信,使用文件锁可以有效阻止多个进程并发修改同一个文件
Java提供了FileLock类支持文件锁功能,在FileChannel中提供的lock()/tryLock()方法可以获得文件锁FileLock对象,从而锁定文件
lock()方法和trylock()方法的区别是:当lock()试图锁定某个文件时,如果无法得到文件锁,程序将一直阻塞,而tryLock()方法时尝试锁定文件,它将直接返回而不是阻塞,如果获得了文件锁,该方法则返回该文件锁,否则将返回null
lock(long position, long size, boolean shared):对文件从position开始,长度为size的内容加锁,该方法是阻塞式的
tryLock(long position, long size, boolean shared):非阻塞式的加锁方法
当shared为true时,表明该锁是一个共享锁,它将允许多个进程来读取文件,但不允许其他进程将其设为排他锁;当shared为false的时,表明这是一个排他锁,它将锁住对该文件的读写
通过调用FileLock的isShared来判断获得的锁是否为共享锁
直接使用lock()或tryLock()方法获取的文件锁是排他锁
处理完成之后,调用FileLock的release()方法释放文件锁
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();
}
}
}
Java7的NIO2
NIO的改进主要包括如下方面:
提供了全面的文件IO和文件系统访问支持
基于异步Channel的IO
Path、Paths和File核心API
Paths提供了get(String first, String... more)方法来获取Path对象,Paths会将给定的多个字符串连缀成路径,如Paths.get("D:", "java","code")将返回D:javacode路径;getNameCount()返回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("D:", "java","code");
System.out.println(path2);
}
}
运行结果:
path里包含的路径数量:1
path的根路径:null
D:\Development\eclipse\workspace\CrazyJava\.
absolutePath的根路径:D:\
absolutePath里包含的路径数量:5
CrazyJava
D:\java\code
以下代码示范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("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());
}
}
使用FileVisitor遍历文件和目录
Files类提供了两个方法来遍历文件和子目录
walkFileTree(Path start, FileVisitor<? super Path> visitor):遍历start路径下的所有文件和子目录
walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor):遍历start路径下的所有文件和子目录,最多遍历maxDepth深度的文件
FileVisitor代表一个文件访问器,walkFileTree()方法会自动遍历start路径下的所有文件和子目录,遍历文件和子目录都会触发FileVisitor中相应的方法。这四个方法在下面的代码中出现。FileVisitor中定义了如下4个方法:
FileVisitResult postVisitDirectory(T dir, IOException exc):访问子目录之后触发该方法
FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs):访问子目录之前触发该方法
FileVisitResult visitFile(T file, BasicFileAttributes attrs):访问file文件时触发该方法
FileVisitResult visitFileFailed(T file, IOException exc):访问file文件失败时触发该方法
FileVisitResult对象,它是一个枚举类,代表访问之后的后续行为,它有如下几种后续行为:
CONTINUE:代表“继续访问”的后续行为
TERMINATE:代表“终止访问”的后续行为
SKIP_SUBTREE:代表“继续访问“,但不访问该目录文件或目录的子目录树
SKIP_SIBLINGS:代表“继续访问”,但不访问该文件或目录的兄弟文件或目录
以下程序使用Files工具类的walkFileTree()方法遍历g:publishcodes15目录下的所有文件和子目录,直到找到的文件以“FileVisitorTest.java”结尾,则程序停止遍历。实现了对指定目录进行搜索,直到找到指定文件为止
import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
public class FileVisitorTest
{
public static void main(String[] args)
throws Exception
{
// 遍历D:\coding\Java路径\CrazyJava\codes\15目录下的所有文件和子目录
Files.walkFileTree(Paths.get("D:", "coding", "Java路径", "CrazyJava", "codes", "15"),
new SimpleFileVisitor<Path>()
{
// 访问文件时候触发该方法
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException
{
System.out.println("正在访问" + file + "文件");
// 找到了FileVisitorTest.java文件
if (file.endsWith("FileVisitorTest.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;
}
});
}
}
使用WatchService监控文件变化
register(WatchService watcher, WatchEvent.Kind<?> ... events):用watcher监听该path代表的目录下文件变化。events参数指定要监听哪些类型的事件
WatchService代表一个文件系统监听服务,它负责监听path代表的目录下的文件变化。一旦使用register()方法完成注册之后,可调用WatchService如下的三个方法来监听目录的文件变化事件
WatchKey poll():获取下一个WatchKey,如果没有WatchKey发生就立即返回null
WatcheKey poll(long timeout, TimeUnit unit):尝试等待timeout时间去获取下一个WatchKey
WatchKey take():获取下一个WatchKey,如果没有WatchKey发生就一直等待
如果程序需要一直监控,则应该选择使用take()方法,如果程序只需要监控指定时间,则使用poll方法
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;
}
}
}
}
访问文件属性
Java7的NIO.2在java.nio.file.attribute包下提供了大量工具类,可以简单地读取、修改文件属性。分为如下两类:
XxxAttributeView:代表某种文件属性的“视图”
XxxAttributes:代表某种文件属性的“集合”,程序一般通过XxxAttributeView对象获取XxxAttributes
FileAttributeView是其他XxxAttributeView的父接口
AclFileAttributeView: 为特定文件设置ACL(Access Control List)及文件所有者属性。getAcl()方法返回List<AclEntry>对象,该返回值代表该文件的权限集,setAcl(List)方法修改该文件的ACL
BaseFileAttributeView:获取或修改文件的基本属性。readAttributes()方法返回一个BaseFileAttributeView对象,对文件夹基本属性的修改是通过BaseFileAttributeView对象完成的
DosFileAttributeView:获取或修改文件DOS相关属性。readAttributes()方法返回一个DosFileAttributeView对象,对文件夹属性的修改通过DosFileAttributeView对象完成的
FileOwnerAttributeView:获取或修改文件的所有者。getOwner()方法返回一个UserPrincipal对象来代表文件所有者;也可调用setOwner(UserPrincipal owner)方法来改变文件的所有者
PosixFileAttributeView:获取文件或修改POSIX(Portable Operating System Interface of INIX)属性。readAttributes()方法返回一个PosixFileAttributeView对象,该对象用于获取或修改文件的所有者、组所有者、访问权限信息。仅在UNIX、Linux等系统上有用
UserDefinedFileAttributeView:开发者自定义文件属性
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。