java NIO

Java NIO

之前我们讲过JavaIO,我们曾把JavaIO比喻为从一个竹筒中取水,一滴滴地输入或者输出。新的IO方式使用了不同的方式来处理输入与输出,新IO采用内存映射文件的方式来处理输入和输出,新IO将文件或者文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了。

Channel(通道)和Buffer(缓冲)是新IO中两个核心对象。新IO系统中所有数据都需要通过通道来传输;你可能会说,都是用通道的话,两者究竟有啥不同?

答案是Channel提供了一个map方法,通过该方法可以直接将一块数据映射到内存中。

使用Buffer

Buffer是一个抽象类,从内部结构上来看,Buffer有点像数组,一类数据的容器。Buffer常用的子类是ByteBuffer,当然也有其他对应基础数据类型的子类。例如CharBuffer、IntBuffer等。

Buffer中有三个重要概念:

  1. 容量(capacity):缓冲区的容积表示该Buffer最多能容纳多少数据。
  2. 界限(limit):第一个不能被读出或写入的缓冲区索引,也就是说处于界限之后的数据既不可被读取也不能写入。
  3. 位置(position):这个很好理解,指明下个能被读出或写入的缓冲区位置索引,类似于IO流中的记录指针,或者RandomAccessFile类的文件指针。(关于前面IO流和RandomAccessFile类我在之前的博客中(将JavaIO那一块都有涉及)

还有一个概念-mark,Buffer允许直接把position直接定位到mark的位置。

Buffer中三大概念示意图

常用方法

方法 描述
static XxxBuffer allocate(int capacity) Buffer没有提供构造器,通过使用静态方法可以创建Buffer容器,Xxx代表着基础数据类型。比如Char,Short等。
int capacity() 返回Buffer容积大小
boolean hasRemaining() 判断当前位置(position)和界限(limit)之间是否还有元素可供处理
int limit() 返回Buffer的界限
Buffer limit(int newLt) 重新设置界限的值,并返回一个具有新界限的缓冲区对象
Buffer mark() 设置Buffer的mark位置,它只能在0和position之间
int position(int newPs) 返回buffer的位置
Buffer position() 设置Buffer的position,并返回新对象
int remaining() 返回当前位置和界限之间的元素个数
Buffer reset() 将位置转到mark所在的位置
Buffer rewind() 将位置设置成0,取消设置的mark
put(Object) 放入数据
Buffer put(Object) 读取数据
Buffer clean() 将position设置为0,将limit设置为capacity,也就是说做好了写入数据的准备,当然也可以作为输入数据的准备。
Buffer flip() 将limit设置为position所在位置,并将position设为0。这就使的Buffer的读写指针回到了开始位置,也就是说做好了输出数据的准备。

示例程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.nio.Buffer;
import java.nio.CharBuffer;

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("加入3个元素后,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());

//调用clean方法
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());
}
}

通过 allocate() 方法创建的Buffer 对象是普通 Buffer, ByteBuffer 还提供了一个 allocateDirect() 方法来创建直接Buffer。 直接 Buffer 的创建成本比普通 Buffer 的创建成本高, 但直接 Buffer 的读取效率更高。如果只是使用一次就丢弃的话,不建议直接创建Buffer,而且只有ByteBuffer才提供了直接创建Buffer的方法

使用Channel

Channel类似于传统的流对象,不过与传统的流对象主要有两个区别

  1. Channel可以直接将指定文件的部分或全部直接映射成Buffer

  2. 程序不能直接访问Channel的数据,包括读写。Channel只能与Buffer进行交互

所有的Channel都不应该通过构造器来创建,而是通过传统的节点的getChannel()方法来创建,所以一般节点流都有与之对应的CHannel。例如FileInputStream()就是返回FileChannel。

方法的话,read和write用法与传统流差不多。提供了一个特殊的map方法。

MappedByteBuffer map(FileChannel.MapMode mode,long position,long size)

第一个参数执行映射的模式(只读、读写等模式);第二个、第三个参数用于控制将Channel的哪些数据映射为ByteBuffer

下面程序示范了直接将FileChannel的全部数据映射成ByteBuffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;

public class FileChannelTest {
public static void main(String[] args) {
File f = new File("FileCHannelTest.java");
try(
//创建FIleInputStream,以该文件输入流创建FileChannel
FileChannel inChannel = new FileInputStream(f).getChannel();

//以文件输出流创建FileChannel,用以控制输出
FileChannel outChannel = new FileOutputStream("a.txt").getChannel();
) {

//将FileChannel里的全部数据映射城ByteBuffer
MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());

//使用UTF-8的字符集来创建编码器,这里使用GBK有可能会出问题
Charset charset = Charset.forName("UTF-8");

//直接将buffer里的数据全部输出
outChannel.write(buffer);

//再次调用buff的clean方法,复原limit、position的位置
buffer.clear();

//创建解码器对象
CharsetDecoder decoder = charset.newDecoder();

//使用解码器将ByteBuffer转换成CharBuffer
CharBuffer charBuffer = decoder.decode(buffer);

//CharBuffer的toString可以获得对应的字符串
System.out.println(charBuffer);

} catch (Exception e) {
e.printStackTrace();
}
}
}

注意,虽然FileChannel虽然可以读写,但是FileInputStream获取的FileChannel只能读,而另一个只能写。RandomAccessFile也包含了一个getChannel()的方法,这个方法创建的可读性取决于RandomAccessFile打开文件的模式。

下面是示范程序,将会对a.txt文件的内容进行复制,追加到文件后面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class RandomFileChannelTest {
public static void main(String[] args) {
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);

} catch (Exception e) {
e.printStackTrace();
}
}
}

字符集和Charset

计算机中的所有文件只是一种表象,所有文件在底层都是二进制文件。对于文本文件来说,我们能看到字符,完全是因为使用了特定的字符集来转换。也就是所谓的编码和解码。

编码与解码

Charset 类提供了一个 availableCharsets() 静态方法来获取当前 JDK 所支持的所有字符集。所以程序可以使用如下程序来获取该 JDK 所支持的全部字符集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.nio.charset.Charset;
import java.util.SortedMap;

public class CharsetTest {
public static void main(String[] args) {

//获取Java支持的全部字符集
SortedMap<String, Charset> map = Charset.availableCharsets();
for(String alias : map.keySet()) {

//输出字符集的别名和对应的对象
System.out.println(alias+"----->"+map.get(alias));
}
}
}

一旦我们知道了字符集的别名之后,就可以调用Charset.forName()方法来创建对应的Charset对象,然后通过该对象的newDecoder()newEncoder()这两个方法分别返回CharsetDecoder和CharsetEncoder对象,分别代表着Charset的解码器和编码器。

下面程序完成了ByteBuffer和CharBuffer之间的转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;

public class CharsetTransform {
public static void main(String[] args) throws CharacterCodingException {

//创建简体中文对应的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.println(bbuff.get(i)+" ");
}

//将ByteBuffer的数据解码成字符系列
System.out.println("\n"+cnDecoder.decode(bbuff));
}
}

如果仅仅需要进行简单的编码、解码操作,直接调用Charset的encode()和decode()方法进行编码、解码即可。

文件锁

NIO中,Java提供了FileLock来支持文件锁定功能,防止在多个运行程序需要并发地修改同一个文件。

lock()上锁,tryLock()可以获得文件锁对象,从而锁定对象。前者会直接上锁,但假如无法得到文件锁,程序将一直阻塞,后者是尝试锁定,如果获得文件锁就返回,没有就返回null。如果想锁定文件地部分内容,则可以使用他们的重载方法。

  • lock( long position, long size, boolean shared): 对文件从 position 开始, 长度为 size 的内容 加锁,该方法是阻塞式的。
  • tryLock( long position, long size, boolean shared): 非阻塞式的加锁方法。 参数的作用与上一个 方法类似。

下面程序示范了FileLock类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

public class FileLockTest {
public static void main(String[] args) {
try(
//使用FileOutputStream获取FileChannel
FileChannel channel = new FileOutputStream("a.txt").getChannel();
)
{
//使用非阻塞式方法对指定文件加锁
FileLock lock = channel.tryLock();

//程序暂停10s
Thread.sleep(10000);

//释放锁
lock.release();

} catch (Exception e) {
e.printStackTrace();
}
}
}

关于文件锁的要点:

  1. 某些平台上,文件锁仅仅是建议,并不是强制的。这意味着就算上锁还是可以读写。
  2. 某些平台上,不能同步地锁定一个文件并把它映射到内存中
  3. 文件锁是Java虚拟机所持有,两个Java程序使用同一个虚拟机,不能对同一个文件上锁。
  4. 在某些平台上关闭FileChannel时,会释放Java虚拟机在该文件上地所有锁,因此应该避免对同一个被锁定地文件打开多个FileChannel。

NIO.2的功能

Java7对原有的NIO进行了重大改进。提供了全面的文件IO和文件系统访问支持,基于异步Channel的IO

Path、Paths和Files核心API

Path代表了一种与平台无关的平台路径,下面是示例程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathTest {
public static void main(String[] args) {

//以当前路径来创建Path对象
Path path = Paths.get(".");

System.out.println("path里包含的路径数量:"+path.getNameCount());

//获取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(0));

//以多个String来构建Path对象
Path path2 = Paths.get("d:","publish","codes");
System.out.println(path2);
}
}

Files是一个操作文件的工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

public class FilesTest {
public static void main(String[] args) throws FileNotFoundException, IOException {

Path path = Paths.get("FilesTest.java");
Charset forName = Charset.forName("UTF-8");
//复制文件
Files.copy(path, new FileOutputStream("a.txt"));

//判断FilesTest.java文件是否为隐藏文件
System.out.println("FileTest.java是否为隐藏文件"+Files.isHidden(path));

//一次性读取FilesTest.java文件的所有行
List<String> lines = Files.readAllLines(path,forName);
System.out.println(lines);

//判断文件的大小
System.out.println("FilesTest.java的大小为:"+
Files.size(path));

List<String> poem = new ArrayList<>();
poem.add("水晶谭底银鱼跃");
poem.add("清徐风中碧杆横");

//直接将多个字符串写入指定文件中
Files.write(Paths.get("poem.txt"), poem, forName);

//使用java8新增的流列出当前目录下所有文件和子目录
Files.list(Paths.get(".")).forEach(path1 ->System.out.println(path1));

//使用Java8新增的StreamAPI读取文件内容
Files.lines(path,forName).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遍历文件和目录

方法 描述
walkFileTree( Path start, FileVisitor<? super Path> visitor) 遍历 start 路径下的所有文件和子目录。
walkFileTree( Path start, Set< FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor) 与上一个方法类似,该方法最多遍历maxDepth深度的文件
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: 代表“ 继续访问” 的后续行为。
  • SKIP_ SIBLINGS: 代表“ 继续访问” 的后续行为,但不访问该文件或目录的兄弟文件或目录。
  • SKIP_ SUBTREE: 代表“ 继续访问” 的后续行为,但不访问该文件或目录的子目录树
  • TERMINATE: 代表“ 中止访问” 的后续行为。

实际使用的时候没必要4个方法都实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

public class FileVisitorTest {
public static void main(String[] args) throws IOException {

//遍历D:\java_project\test 目录下的所有文件和子目录
Files.walkFileTree(Paths.get("D:","java_project","test"), new SimpleFileVisitor<Path>() {

//访问文件时触发该方法
@Override
public FileVisitResult visitFile(Path file,BasicFileAttributes attrs) {
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) {
System.out.println("正在访问:"+dir+"路径");
return FileVisitResult.CONTINUE;
}
});
}
}

使用WatchService监控文件变化

Path类提供了register方法来监听文件系统的变化

register( WatchService watcher, WatchEvent. Kind<?>... events)

用 watcher 监听该 path 代表的目录下的文件变化。 events 参数指定要监听哪些类型的事件。

通过注册以后就可以通过WatchSrvice代表一个文件系统监听服务。

  • WatchKey poll(): 获取 下一个 WatchKey, 如果 没有 WatchKey 发生 就 立即 返回 null。
  • WatchKey poll( long timeout, TimeUnit unit): 尝试等待 timeout 时间去获取下一个 WatchKey。
  • WatchKey take(): 获取下一个 WatchKey, 如果没有 WatchKey 发生就一直等待。

如果程序需要一直监控,则应该选择使用 take() 方法; 如果程序只需要监控指定时间,则可考虑使用 poll() 方法。

如下程序监听了C盘创建文件,修改文件,删除文件的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;

public class WatchServiceTest {
public static void main(String[] args) throws IOException, InterruptedException {

//获取文件系统的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。

下面是一些方法的官方记录:

方法 描述
AclFileAttributeView 通过 AclFileAttributeView,开发者可以为特定文件设置ACL( Access Control List)及文件所有者属性。它的 getAcl() 方法返回 List< AclEntry> 对象,该返回值代表了该文件的权限集。通过 setAcl( List)方法可以修改该文件的 ACL。
BasicFileAttributeView 它可以获取或修改文件的基本属性,包括文件的最后修改时间、最后访问时间、创建时间、大小、是否为目录、是否为符号链接等。它的 readAttributes() 方法返回一个 BasicFileAttributes 对象,对文件夹基本属性的修改是通过 BasicFileAttributes 对象完成的。
DosFileAttributeView 它主要用于获取或修改文件 DOS 相关属性,比如文件是否只读、是否隐藏、是否为系统文件、是否是存档文件等。 它的 readAttributes() 方法返回一个 DosFileAttributes 对象,对这些属性的修改其实是由 DosFileAttributes 对象来完成的。
FileOwnerAttributeView 它主要 用于获取或修改文件的所有者。 它的 getOwner() 方法返回一个 UserPrincipal 对象来 代表文件所有者;也可调用 setOwner( UserPrincipal owner)方法来改变文件的所有者。
PosixFileAttributeView 它主要用于获取或修改 POSIX( Portable Operating System Interface of INIX)属性,它的 readAttributes() 方法返回一个PosixFileAttributes 对象,该对象可用于获取或修改文件的所有者、组所有者、访问权限信息( 就是 UNIX 的 chmod 命令负责干的事情)。这个 View 只在 UNIX、 Linux 等系统上有用。

示例程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributeView;
import java.nio.file.attribute.FileOwnerAttributeView;
import java.nio.file.attribute.UserDefinedFileAttributeView;
import java.nio.file.attribute.UserPrincipal;
import java.util.Date;
import java.util.List;

public class AttributeViewTest {
public static void main(String[] args) throws IOException {

//获取将要操作的文件
Path testPath = Paths.get("AttributeViewTest.java");

//获取访问基本属性的BasicFileAttributeView
BasicFileAttributeView basicView = Files.getFileAttributeView(testPath, BasicFileAttributeView.class);

//获取访问基本属性的BasicFileAttribs
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 ownerView = Files.getFileAttributeView(testPath, FileOwnerAttributeView.class);

//获取该文件所属的用户
System.out.println(ownerView.getOwner());

//获取系统中guest对应的用户
UserPrincipal user = FileSystems.getDefault().getUserPrincipalLookupService().lookupPrincipalByName("guest");

//修改用户
//ownerView.setOwner(user);

//获取访问自定义属性的
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);

}
}

NIO很强很强,猛得一批。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!