天天看点

Java:前程似锦的 NIO 2.0-1

Java 之所以能够霸占编程语言的榜首,其强大、丰富的类库功不可没,几乎所有的编程问题都能在其中找到解决方案。但在早期的版本当中,输入输出(I/O)流并不那么令开发者感到愉快:

1)JDK 1.4 之前的 I/O 没有缓冲区的概念、不支持正则表达式、支持的字符集编码有限等等;

2)JDK 1.4 的时候引入了非阻塞 I/O,也就是 NIO 1.0,但遍历目录很困难,不支持文件系统的非阻塞操作等等。

为了突破这些限制,JDK 1.7 的时候引入了新的 NIO,也就是本篇文章的主角——NIO 2.0。

01、基石:Path

Path 既可以表示一个目录,也可以表示一个文件,就像 File 那样——当然了,Path 就是用来取代 File 的。

1)可以通过 Paths.get() 创建一个 Path 对象,此时 Path 并没有真正在物理磁盘上创建;参数既可以是一个文件名,也可以是一个目录名;绝对路径或者相对路径均可。

2)可以通过 Files.notExists() 确认 Path(目录或者文件) 是否已经存在。

3)可以通过 Files.createDirectory() 创建目录,此时目录已经在物理磁盘上创建成功,可通过资源管理器查看到。

4)可以通过 Files.createFile() 创建文件,此时文件已经在物理磁盘上创建成功,可通过资源管理器查看到。

5)可以通过 toAbsolutePath() 查看 Path 的绝对路径。

6)可以通过 resolve() 将 Path 连接起来,参数可以是一个新的 Path 对象,也可以是对应的字符串。

具体的代码如下:

public class Wanger {

    public static void main(String[] args) {
        // 相对路径
        Path dir = Paths.get("chenmo");

        // 输出 dir 的绝对路径
        System.out.println(dir.toAbsolutePath()); // 输出:D:\program\java.git\java_demo\chenmo

        if (Files.notExists(dir)) {
            try {
                // 如果目录不存在,则创建目录
                Files.createDirectory(dir);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
        
        // 这时候 chenmo.txt 文件并未创建
        // 通过 resolve 方法把 dir 和 chenmo.txt 链接起来
        Path file = dir.resolve("chenmo.txt");

        // 输出 file 的绝对路径
        System.out.println(file.toAbsolutePath()); // 输出:D:\program\java.git\java_demo\chenmo\chenmo.txt

        if (Files.notExists(file)) {
            try {
                // 如果文件不存在,则创建文件
                Files.createFile(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

}

      

如果要将 File 转换为 Path,可以通过 File 类的 toPath() 方法完成。代码示例如下:

File file = new File("沉默王二.txt");

Path path = file.toPath();

如果要将 Path 转换为 File,可以通过 Path 类的 toFile() 方法完成。代码示例如下:

Path path = Paths.get("沉默王二.txt");

File file = path.toFile();

02、处理目录

NIO 2.0 新增的 java.nio.file.DirectoryStream<T> 接口可以非常方便地查找目录中的(符合某种规则的)文件,比如说我们要查找 chenmo 目录下的 txt 后缀的文件,代码示例如下:

// 相对路径
Path dir = Paths.get("chenmo");
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.txt")) {
    for (Path entry : stream) {
  System.out.println(entry.getFileName());
    }
} catch (IOException e) {
    e.printStackTrace();
}      

1)Files.newDirectoryStream(Path dir, String glob) 会返回一个过滤后的 DirectoryStream( 目录流,),第一个参数为目录,第二个参数为 glob 表达式,比如 *.txt 表示所有 txt 后缀的文件。

2)由于 DirectoryStream 继承了 Closeable 接口,所以它可以配合 try-with-resources 语法写出更安全的代码,目录流会自动调用 close 方法关闭流,释放与流相关的资源,不需要再通过 finally 进行主动关闭。

3)DirectoryStream 被称为目录流,允许方便地使用 for-each 结构来遍历目录。

03、处理目录树

目录树意味着一个目录里既有文件也有子目录,也可能都没有,也可能有其一。NIO 2.0 可以很方便地遍历一颗目录树,并操作符合条件的文件;这其中关键的一个方法就是 Files 类的 walkFileTree,其定义如下:

public static Path walkFileTree(Path start, FileVisitor<? super Path> visitor)
        throws IOException
    {
        return walkFileTree(start,
                            EnumSet.noneOf(FileVisitOption.class),
                            Integer.MAX_VALUE,
                            visitor);
    }      

第二个参数 FileVisitor 被称为文件访问器接口,它实现起来非常复杂,要实现 5 个方法呢,但幸好 JDK 的设计者提供了一个默认的实现类 SimpleFileVisitor,如果我们只想从目录树中找到 txt 后缀的文件,可以这样做:

// 相对路径
Path dir = Paths.get("chenmo");
try {
    Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
  @Override
  public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
    if (file.toString().endsWith(".txt")) {
    System.out.println(file.getFileName());
    }
    return FileVisitResult.CONTINUE;
  }
    });
} catch (IOException e) {
    e.printStackTrace();
}      

通过创建匿名内部类来重写 SimpleFileVisitor 的 visitFile 方法,如果后缀名为 txt 就打印出来。