java.nio.*

新 I/O 使用 NIO(同步非阻塞)的方式重写了老的 I/O 了,因此它获得了 NIO 的种种优点。

速度的提升来自于使用了更接近操作系统 I/O 执行方式的结构:Channel(通道) 和 Buffer(缓冲区)。我们可以想象一个煤矿:通道就是连接矿层(数据)的矿井,缓冲区是运送煤矿的小车。通过小车装煤,再从车里取矿。换句话说,我们不能直接和 Channel 交互; 我们需要与 Buffer 交互并将 Buffer 中的数据发送到 Channel 中;Channel 需要从 Buffer 中提取或放入数据。

其实一般情况下使用普通IO就可以了,只有在遇到性能瓶颈(例如内存映射文件)或创建自己的 I/O 库时,我们才需要去理解 NIO。

java.nio.file

删除目录树

Files 工具类包含大部分我们需要的目录操作和文件操作方法。出于某种原因,它们没有包含删除目录树相关的方法,on Java8中的实现如下:

  1. import java.nio.file.*;
  2. import java.nio.file.attribute.BasicFileAttributes;
  3. import java.io.IOException;
  4. public class RmDir {
  5. public static void rmdir(Path dir) throws IOException {
  6. Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
  7. @Override
  8. public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
  9. Files.delete(file);
  10. return FileVisitResult.CONTINUE;
  11. }
  12. @Override
  13. public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
  14. Files.delete(dir);
  15. return FileVisitResult.CONTINUE;
  16. }
  17. });
  18. }
  19. }

删除目录树的方法实现依赖于 Files.walkFileTree(),”walking” 目录树意味着遍历每个子目录和文件。Visitor 设计模式提供了一种标准机制来访问集合中的每个对象,然后你需要提供在每个对象上执行的操作。 此操作的定义取决于实现的 **FileVisitor 的四个抽象方法,包括:

  1. preVisitDirectory():在访问目录中条目之前在目录上运行。
  2. visitFile():运行目录中的每一个文件。
  3. visitFileFailed():调用无法访问的文件。
  4. postVisitDirectory():在访问目录中条目之后在目录上运行,包括所有的子目录。

为了简化,java.nio.file.SimpleFileVisitor 提供了所有方法的默认实现。这样,在我们的匿名内部类中,我们只需要重写非标准行为的方法:visitFile() 和 postVisitDirectory() 实现删除文件和删除目录。两者都应该返回标志位决定是否继续访问(这样就可以继续访问,直到找到所需要的)。

文件查找

FileSystem 对象上调用 getPathMatcher() 获得一个 PathMatcher,然后传入您感兴趣的模式。模式有两个选项:glob 和 regex。glob 比较简单,实际上功能非常强大,因此您可以使用 glob 解决许多问题。如果您的问题更复杂,可以使用 regex。

  • 示例:使用 glob 查找以 .tmp 或 .txt 结尾的所有 Path

    1. import java.nio.file.*;
    2. public class Find {
    3. public static void main(String[] args) throws Exception {
    4. Path test = Paths.get("test");
    5. Directories.refreshTestDir();
    6. Directories.populateTestDir();
    7. // Creating a *directory*, not a file:
    8. Files.createDirectory(test.resolve("dir.tmp"));
    9. PathMatcher matcher = FileSystems.getDefault()
    10. .getPathMatcher("glob:**/*.{tmp,txt}");
    11. Files.walk(test)
    12. .filter(matcher::matches)
    13. .forEach(System.out::println);
    14. System.out.println("***************");
    15. PathMatcher matcher2 = FileSystems.getDefault()
    16. .getPathMatcher("glob:*.tmp");
    17. Files.walk(test)
    18. .map(Path::getFileName)
    19. .filter(matcher2::matches)
    20. .forEach(System.out::println);
    21. System.out.println("***************");
    22. Files.walk(test) // Only look for files
    23. .filter(Files::isRegularFile)
    24. .map(Path::getFileName)
    25. .filter(matcher2::matches)
    26. .forEach(System.out::println);
    27. }
    28. }

    在 matcher 中,glob 表达式开头的 */ 表示“当前目录及所有子目录”,这在当你不仅仅要匹配当前目录下特定结尾的 Path 时非常有用。单 表示“任何东西”,然后是一个点,然后大括号表示一系列的可能性—-我们正在寻找以 .tmp 或 .txt 结尾的东西。您可以在 getPathMatcher() 文档中找到更多详细信息。
    matcher2 只使用 *.tmp,通常不匹配任何内容,但是添加 map() 操作会将完整路径减少到末尾的名称。
    注意,在这两种情况下,输出中都会出现 dir.tmp,即使它是一个目录而不是一个文件。要只查找文件,必须像在最后 files.walk() 中那样对其进行筛选。

文件读写

如果一个文件很“小”,那么 java.nio.file.Files 类中的实用程序将帮助你轻松读写文本和二进制文件。
Files.readAllLines() 一次读取整个文件(因此,“小”文件很有必要),产生一个List

  • ```java import java.util.; import java.nio.file.;

public class ListOfLines { public static void main(String[] args) throws Exception { Files.readAllLines( Paths.get(“../streams/Cheese.dat”)) .stream() .filter(line -> !line.startsWith(“//“)) .map(line -> line.substring(0, line.length()/2)) .forEach(System.out::println); } } ```

ByteBuffer

有且仅有 ByteBuffer(字节缓冲区,保存原始字节的缓冲区)这一类型可直接与通道交互。查看 java.nio.ByteBuffer 的 JDK 文档,你会发现它是相当基础的:通过初始化某个大小的存储空间,再使用一些方法以原始字节形式或原始数据类型来放置和获取数据。但是我们无法直接存放对象,即使是最基本的 String 类型数据。这是一个相当底层的操作,也正因如此,使得它与大多数操作系统的映射更加高效。

旧式 I/O 中的三个类分别被更新成 FileChannel(文件通道),分别是:FileInputStream、FileOutputStream,以及用于读写的 RandomAccessFile 类。

注意,这些都是符合底层 NIO 特性的字节操作流。 另外,还有 Reader 和 Writer 字符模式的类是不产生通道的。但 java.nio.channels.Channels 类具有从通道中生成 Reader 和 Writer 的实用方法。