Java中的IO流四

Java的IO相关内容。

NIO2

文件系统访问

java.nio.file.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
public static void main(String[] args) throws IOException {
Path path = Paths.get("/Users/Mark/Project", "Base.java");
System.out.println(path); // /Users/Mark/Project/Base.java
System.out.println(path.toAbsolutePath()); // /Users/Mark/Project/Base.java
System.out.println(path.toRealPath()); // /Users/Mark/Project/Base.java
System.out.println(path.getFileName()); // Base.java
System.out.println(path.getNameCount()); // 4
System.out.println(path.getRoot()); // /
System.out.println(path.getParent()); // /Users/Mark/Project
for (int i = 0; i < path.getNameCount(); i++) {
System.out.println(path.getName(i)); // Users, Mark, Project, Base.java
}
//==================================
path = Paths.get("src/main/java/", "com/mark/nio");
System.out.println(path); // src/main/java/com/mark/nio
System.out.println(path.subpath(0, 3)); // src/main/java

System.out.println(path.resolve("test")); // src/main/java/com/mark/nio/test
System.out.println(path.resolveSibling("io")); // src/main/java/com/mark/io

Path other = Paths.get("src/main/resources");
System.out.println(other.toAbsolutePath()); // /Users/Mark/Project/idea/javademo/src/main/resources
path = path.relativize(other);
System.out.println(path); // ../../../../resources
System.out.println(path.toAbsolutePath()); // /Users/Mark/Project/idea/javademo/../../../../resources
System.out.println(path.toAbsolutePath().normalize()); // /Users/resources
}

列出目录的子目录和文件可以使用File.list和File.listFiles方法,NIO2中可以使用java.nio.file.DirectoryStream来进行遍历目录。DirectoryStream接口实现了Closeable和Iterable接口。通过Files.newDirectoryStream方法获得DirectoryStream实例,可以通过DirectoryStream.Filter来过滤。

1
2
3
4
5
Path path = Paths.get("");
DirectoryStream<Path> ds = Files.newDirectoryStream(path, "*.java"); // 只会列出当前目录下的文件和子目录,不会递归列出子目录下的文件和目录
for (Path d : ds) {
System.out.println(d);
}

如果想递归遍历整个目录,可以使用Files.walkFileTree方法和java.nio.file.FileVisitor接口。FileVisitor接口有四个方法

  • visitFile 正在访问某个文件
  • visitFileFailed 访问某个文件出现错误
  • preVisitDirectory 在访问一个目录中的子目录和文件之前调用
  • postVisitDirectory 在访问一个目录中的子目录和文件之后调用

这四个方法都返回FileVisitResult,这是一个枚举类型,有四个值CONTINUE、TERMINATE、SKIP_SUBTREE 和 SKIP_SIBLINGS。
该接口有一个默认实现SimpleFileVisitor。我们可以继承这个类来实现我们自己的逻辑。

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
// 列出当前目录下的所有Java文件
Path start = Paths.get("");
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.getFileName().toString().endsWith(".java")) {
System.out.println(file.toAbsolutePath());
}
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
// 跳过 .git 和 .idea 目录
if (".git".equals(dir.getFileName().toString()) || ".idea".equals(dir.getFileName().toString())) {
return FileVisitResult.SKIP_SUBTREE;
}
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
return super.visitFileFailed(file, exc);
}

@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
return super.postVisitDirectory(dir, exc);
}
});

文件的属性信息由java.nio.file.attribute.BasicFileAttributes、PosixFileAttributes、DosFileAttributes这几个接口定义。可以通过java.nio.file.attribute.BasicFileAttributeView、DosFileAttributeView、PosixFileAttributeView的readAttributes方法来得到。Files类也提供了一系列静态方法来获得文件的属性信息。不同类型的FileAttributes会有不同的方法。

1
2
3
4
5
6
7
8
9
10
11
Path path = Paths.get("pom.xml");
PosixFileAttributeView fav = Files.getFileAttributeView(path, PosixFileAttributeView.class);
PosixFileAttributes attributes = fav.readAttributes();
System.out.println(attributes.group());
System.out.println(attributes.owner());
System.out.println(attributes.permissions());
System.out.println(attributes.creationTime());
System.out.println(attributes.isRegularFile());
System.out.println(attributes.lastAccessTime());
System.out.println(attributes.lastModifiedTime());
System.out.println(attributes.size());

对一个目录进行监视可以通过java.nio.file.WatchService接口来实现,通过将要监视的目录和感兴趣的事件注册到WatchService,事件类StandardWatchEventKinds中定义了一些事件,在事件发生的时候就可以通过WatchKey来获得事件列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Path this_dir = Paths.get("").toAbsolutePath();
System.out.println("Now watching the current directory " + this_dir.toString());
try {
WatchService watcher = this_dir.getFileSystem().newWatchService();
this_dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE);

while (true) {
WatchKey watchKey = watcher.take();
List<WatchEvent<?>> events = watchKey.pollEvents();
for (WatchEvent event : events) {
Path context = (Path) event.context(); // 返回的是相对于监视目录的相对路径
context = this_dir.resolve(context); // 转成绝对路径
System.out.println("Someone just created the file " + context.toString());
System.out.println(Files.size(context));
}
watchKey.reset(); // 不重置的话,之后相同的事件就无法被监视到
}
} catch (IOException | InterruptedException e) {
System.out.println("Error: " + e.toString());
}

zip/jar文件系统

在Java中可以通过FileSystem和FileSystemProvider这两个抽象类来实现自定义的文件系统。

Java类库中包含两种文件系统,基于操作系统的文件系统和zip/jar文件系统。

例子:将一个文件添加到zip压缩包中

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
public static void main(String[] args) throws IOException {
File testZip = new File("归档.zip");
File fileToAdd = new File("demo.txt");
if (testZip.exists() && fileToAdd.exists()) {
addFile(testZip, fileToAdd);
}
}

/*
原先的方法:
先创建一个tmp文件,然后将原压缩文件中的数据写入tmp文件,再把要加入的文件数据写入tmp文件
最后删了原压缩文件,并把tmp文件改名为原压缩文件
*/

private static void addFile(File zip, File file) throws IOException {
File tmp = File.createTempFile(zip.getName(), null);
int len = -1;
try (ZipInputStream input = new ZipInputStream(new FileInputStream(zip));
ZipOutputStream output = new ZipOutputStream(new FileOutputStream(tmp))) {
ZipEntry entry = input.getNextEntry();
byte[] buf = new byte[10240];
while (entry != null) {
String name = entry.getName();
if (!file.getName().equals(name)) {
output.putNextEntry(new ZipEntry(name));
while ((len = input.read(buf)) != -1) {
output.write(buf, 0, len);
}
output.closeEntry();
}
entry = input.getNextEntry();
}
try (FileInputStream newFileInput = new FileInputStream(file)) {
output.putNextEntry(new ZipEntry(file.getName()));
System.out.println(newFileInput.available());
while ((len = newFileInput.read(buf)) != -1) {
output.write(buf, 0, len);
}
output.closeEntry();
}
zip.delete();
tmp.renameTo(zip);
}
}

// 使用文件系统的方法
private static void addFileToZip(File zip, File file) throws IOException {
Map<String, String> env = new HashMap<>();
env.put("create", "true");
// 这里的URI.create的参数是固定的 jar:+文件的URI
// jar是jar文件系统的Scheme
// 在操作压缩文件时,需要通过Paths.get(URI)方法来得到压缩文件
try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + zip.toURI()), env)) {
Path path = file.toPath();
Path pathInZip = fs.getPath("/test/" + file.getName());// 指定压缩文件中的位置,这里是压缩文件中的test文件夹下
Files.copy(path, pathInZip, StandardCopyOption.REPLACE_EXISTING);
}
}

异步I/O通道

例子:

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
private static void server() throws IOException {
AsynchronousChannelGroup group = AsynchronousChannelGroup.withFixedThreadPool(10, Executors.defaultThreadFactory());
AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel.open(group).bind(new InetSocketAddress(8680));
channel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
@Override
public void completed(AsynchronousSocketChannel result, Void attachment) {
channel.accept(null, this);
try {
System.out.println(result.getRemoteAddress());
result.write(ByteBuffer.wrap("hi there!".getBytes(StandardCharsets.UTF_8)));
result.close();
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void failed(Throwable exc, Void attachment) {
throw new RuntimeException(exc);
}

});
}

private static void writeToFile() throws IOException, InterruptedException, ExecutionException {
AsynchronousFileChannel afc = AsynchronousFileChannel.open(Paths.get("demo.txt"), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
Future<Integer> future = afc.write(ByteBuffer.wrap("hello world".getBytes()), 0);
System.out.println(future.get());
}

AsynchronousServerSocketChannel实现了NetworkChannel接口,该接口是是与网络套接字相关的通道,提供了绑定和配置相关的方法。