Java NIO 教程(十五) Java NIO Files

原文地址

目录

Java NIO Files类(java.nio.file.Files)提供了几种操作文件系统中的文件的方法。这个Java NIO Files教程将介绍最常用的这些方法。Files类包含许多方法,所以如果您需要一个在这里没有描述的方法,那么请检查JavaDoc。Files类可能还会有一个方法来实现它。

java.nio.file.Files类与java.nio.file.Path实例一起工作,因此在处理Files类之前,您需要了解Path类。

Files.exists()

Files.exists()方法检查给定的Path在文件系统中是否存在。

可以创建在文件系统中不存在的Path实例。例如,如果您计划创建一个新目录,您首先要创建相应的Path实例,然后创建目录。

由于Path实例可能指向,也可能没有指向文件系统中存在的路径,你可以使用Files.exists()方法来确定它们是否存在(如果需要检查的话)。

这里是一个Java Files.exists()的例子:

Path path = Paths.get("data/logging.properties");

boolean pathExists =
        Files.exists(path,
            new LinkOption[]{ LinkOption.NOFOLLOW_LINKS});

这个例子首先创建一个Path实例指向一个路径,我们想要检查这个路径是否存在。然后,这个例子调用Files.exists()方法,然后将Path实例作为第一个参数。

注意Files.exists()方法的第二个参数。这个参数是一个选项数组,它影响Files.exists()如何确定路径是否存在。在上面的例子中的数组包含LinkOption.NOFOLLOW_LINKS,这意味着Files.exists()方法不应该在文件系统中跟踪符号链接,以确定文件是否存在。

Files.createDirectory()

Files.createDirectory()方法,用于根据Path实例创建一个新目录,下面是一个Files.createDirectory()例子:

Path path = Paths.get("data/subdir");

try {
    Path newDir = Files.createDirectory(path);
} catch(FileAlreadyExistsException e){
    // 目录已经存在
} catch (IOException e) {
    // 其他发生的异常
    e.printStackTrace();
}

第一行创建表示要创建的目录的Path实例。在try-catch块中,用路径作为参数调用Files.createDirectory()方法。如果创建目录成功,将返回一个Path实例,该实例指向新创建的路径。

如果该目录已经存在,则是抛出一个java.nio.file.FileAlreadyExistsException。如果出现其他错误,可能会抛出IOException。例如,如果想要的新目录的父目录不存在,则可能会抛出IOException。父目录是您想要创建新目录的目录。因此,它表示新目录的父目录。

Files.copy()

Files.copy()方法从一个路径拷贝一个文件到另外一个目录,这里是一个Java Files.copy()例子:

Path sourcePath      = Paths.get("data/logging.properties");
Path destinationPath = Paths.get("data/logging-copy.properties");

try {
    Files.copy(sourcePath, destinationPath);
} catch(FileAlreadyExistsException e) {
    // 目录已经存在
} catch (IOException e) {
    // 其他发生的异常
    e.printStackTrace();
}

首先,该示例创建一个源和目标Path实例。然后,这个例子调用Files.copy(),将两个Path实例作为参数传递。这可以让源路径引用的文件被复制到目标路径引用的文件中。

如果目标文件已经存在,则抛出一个java.nio.file.FileAlreadyExistsException异常。如果有其他错误,则会抛出一个IOException。例如,如果将该文件复制到不存在的目录,则会抛出IOException

重写已存在的文件

可以强制Files.copy()覆盖现有的文件。这里有一个示例,演示如何使用Files.copy()覆盖现有文件。

Path sourcePath      = Paths.get("data/logging.properties");
Path destinationPath = Paths.get("data/logging-copy.properties");

try {
    Files.copy(sourcePath, destinationPath,
            StandardCopyOption.REPLACE_EXISTING);
} catch(FileAlreadyExistsException e) {
    // 目标文件已存在
} catch (IOException e) {
    // 其他发生的异常
    e.printStackTrace();
}

请注意Files.copy()方法的第三个参数。如果目标文件已经存在,这个参数指示copy()方法覆盖现有的文件。

Files.move()

Java NIO Files还包含一个函数,用于将文件从一个路径移动到另一个路径。移动文件与重命名相同,但是移动文件既可以移动到不同的目录,也可以在相同的操作中更改它的名称。是的,java.io.File类也可以使用它的renameTo()方法来完成这个操作,但是现在已经在java.nio.file.Files中有了文件移动功能。

这里有一个Java Files.move()例子:

Path sourcePath      = Paths.get("data/logging-copy.properties");
Path destinationPath = Paths.get("data/subdir/logging-moved.properties");

try {
    Files.move(sourcePath, destinationPath,
            StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
    //移动文件失败
    e.printStackTrace();
}

首先创建源路径和目标路径。源路径指向要移动的文件,而目标路径指向文件应该移动到的位置。然后调用Files.move()方法。这会导致文件被移动。

请注意传递给Files.move()的第三个参数。这个参数告诉Files.move()方法来覆盖目标路径上的任何现有文件。这个参数实际上是可选的。

如果移动文件失败,Files.move()方法可能抛出一个IOException。例如,如果一个文件已经存在于目标路径中,并且您已经排除了StandardCopyOption.REPLACE_EXISTING选项,或者被移动的文件不存在等等。

Files.delete()

Files.delete()方法可以删除一个文件或者目录。下面是一个Java Files.delete()例子:

Path path = Paths.get("data/subdir/logging-moved.properties");

try {
    Files.delete(path);
} catch (IOException e) {
    // 删除文件失败
    e.printStackTrace();
}

首先,创建指向要删除的文件的Path。然后调用Files.delete()方法。如果Files.delete()由于某种原因不能删除文件(例如,文件或目录不存在),会抛出一个IOException

Files.walkFileTree()

Files.walkFileTree()方法包含递归遍历目录树的功能。walkFileTree()方法将Path实例和FileVisitor作为参数。Path实例指向您想要遍历的目录。FileVisitor在遍历期间被调用。

在我解释遍历是如何工作之前,这里我们先了解FileVisitor接口:

public interface FileVisitor {
    public FileVisitResult preVisitDirectory(
        Path dir, BasicFileAttributes attrs) throws IOException;

    public FileVisitResult visitFile(
        Path file, BasicFileAttributes attrs) throws IOException;

    public FileVisitResult visitFileFailed(
        Path file, IOException exc) throws IOException;

    public FileVisitResult postVisitDirectory(
        Path dir, IOException exc) throws IOException {
}

您必须自己实现FileVisitor接口,并将实现的实例传递给walkFileTree()方法。在目录遍历过程中,您的FileVisitor实现的每个方法都将被调用。如果不需要实现所有这些方法,那么可以扩展SimpleFileVisitor类,它包含FileVisitor接口中所有方法的默认实现。

这里是一个walkFileTree()的例子:

Files.walkFileTree(path, new FileVisitor<Path>() {
  @Override
  public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
    System.out.println("pre visit dir:" + dir);
    return FileVisitResult.CONTINUE;
  }

  @Override
  public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
    System.out.println("visit file: " + file);
    return FileVisitResult.CONTINUE;
  }

  @Override
  public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
    System.out.println("visit file failed: " + file);
    return FileVisitResult.CONTINUE;
  }

  @Override
  public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
    System.out.println("post visit directory: " + dir);
    return FileVisitResult.CONTINUE;
  }
});

FileVisitor实现中的每个方法在遍历过程中的不同时间都被调用:

在访问任何目录之前调用preVisitDirectory()方法。在访问一个目录之后调用postVisitDirectory()方法。

调用visitFile()在文件遍历过程中访问的每一个文件。它不会访问目录-只会访问文件。在访问文件失败时调用visitFileFailed()方法。例如,如果您没有正确的权限,或者其他什么地方出错了。

这四个方法中的每个都返回一个FileVisitResult枚举实例。FileVisitResult枚举包含以下四个选项:

  • CONTINUE 继续
  • TERMINATE 终止
  • SKIP_SIBLING 跳过同级
  • SKIP_SUBTREE 跳过子级

通过返回其中一个值,调用方法可以决定如何继续执行文件。

CONTINUE继续意味着文件的执行应该像正常一样继续。

TERMINATE终止意味着文件遍历现在应该终止。

SKIP_SIBLINGS跳过同级意味着文件遍历应该继续,但不需要访问该文件或目录的任何同级。

SKIP_SUBTREE跳过子级意味着文件遍历应该继续,但是不需要访问这个目录中的子目录。这个值只有从preVisitDirectory()返回时才是一个函数。如果从任何其他方法返回,它将被解释为一个CONTINUE继续。

文件搜索

这里是一个用于扩展SimpleFileVisitorwalkFileTree(),以查找一个名为README.txt的文件:

Path rootPath = Paths.get("data");
String fileToFind = File.separator + "README.txt";

try {
  Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
    
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
      String fileString = file.toAbsolutePath().toString();
      //System.out.println("pathString = " + fileString);

      if(fileString.endsWith(fileToFind)){
        System.out.println("file found at path: " + file.toAbsolutePath());
        return FileVisitResult.TERMINATE;
      }
      return FileVisitResult.CONTINUE;
    }
  });
} catch(IOException e){
    e.printStackTrace();
}

递归删除目录

Files.walkFileTree()也可以用来删除包含所有文件和子目录的目录。Files.delete()方法只会删除一个目录,如果它是空的。通过遍历所有目录并删除每个目录中的所有文件(在visitFile())中,然后删除目录本身(在postVisitDirectory()中),您可以删除包含所有子目录和文件的目录。下面是一个递归目录删除示例:

Path rootPath = Paths.get("data/to-delete");

try {
  Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
      System.out.println("delete file: " + file.toString());
      Files.delete(file);
      return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
      Files.delete(dir);
      System.out.println("delete dir: " + dir.toString());
      return FileVisitResult.CONTINUE;
    }
  });
} catch(IOException e){
  e.printStackTrace();
}

文件类中的其他方法

java.nio.file.Files类包含许多其他有用的函数,比如用于创建符号链接的函数、确定文件大小、设置文件权限等等。有关这些方法的更多信息,请查看java.nio.file.Files类的JavaDoc。

推荐阅读更多精彩内容