16 Java NIO Files-翻译

304 阅读6分钟

Java NIO Files类提供了一系列方法来操作文件系统中的文件。这个Java NIO Files教程将会包含大部分常用的方法。Files类包含许多的方法,所以如果你需要的方法没有在这里描述,也需要查看JavaDoc。

java.nio.file.Files类跟java.nio.Paths实例一起工作。所以在与Files一起工作时你需要了解Path类。

Files.exists()方法

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

也可以创建在文件系统中不存在的Path实例。例如,如果你想要创建一个新的目录,你先会创建相应的Path实例,然后创建目录。

由于Path实例可能在文件系统中并不存在,你可以使用Files.exists()方法来检查。

下面是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.exist()来判断文件是否存。在以上的例子中,这个数组包含了LinkOption.NOFOLLOW_LINKS,这说明这Files.exists()方法不应该检查符号连接来判断文件是否存在。

Files.createDirectory()

Files.createSirectory方法通过Path实例来创建一个新的目录。下面是Java的Files.createDirecotry()的例子。

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

try {
    Path newDir = Files.createDirectory(path);
} catch(FileAlreadyExistsException e){
    // the directory already exists.
} catch (IOException e) {
    //something else went wrong
    e.printStackTrace();
}

第一行创建了一个Path实例来代表需要创建的目录。在try-catch块的内部调用了Files.createDirectory()方法,并将path作为第一个参数。如果目录创建成功,会返回一个指向新创建目录的Path实例。

如果目录已经存在,会抛出java.nio.FileAlreadyExistsException异常。如果出现其他情况,会出现IO异常。

Files.copy()

Files.copy()方法用于将文件复制到另一个文件。下面是Java NIO的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) {
    //destination file already exists
} catch (IOException e) {
    //something else went wrong
    e.printStackTrace();
}

首先这个例子创建了一个源Path和目标Path。然后,这个例子调用了Files.copy()方法,并将两个path实例作为参数。这会将源Path中指定的文件复制到目标path中。

如果目标文件已经存在,将会抛出java.nio.file.FileAlreadyExistsException异常。如果出现其它异常,会抛出IOException。例如,如果目录文件的目录不存在,就会抛出IOException。

覆盖存在文件

也可以实现在文件复制时覆盖已经存在的文件。下面是一个如何使用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) {
    //destination file already exists
} catch (IOException e) {
    //something else went wrong
    e.printStackTrace();
}

注意到Files.copy()方法的第三个参数。这个参数表明复制时覆盖已经存在的文件。

File.move()方法

Java NIO Files类包含了从一个路径移动到另一个路径的方法。移动文件与重命名文件是一致的。移动文件除了能够移动文件之外还能重命名文件。是的,在java.io.File中可以通过renameTo()方法来实现,但是现在在java.nio.file.Files也可以实现这个功能。

下面是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) {
    //moving file failed.
    e.printStackTrace();
}

首先源路径和目标路径会被创建。源路径指定的是需要移动的文件,目标路径指定的是源文件需要被移动到的位置。然后调用了Files.move()方法。结果是文件被移动了。

注意到Files.move()方法的第三个参数,这个参数告诉Files.move()方法如果目标文件存在,则覆盖。这个参数事实上是可选的。

Files.move()方法可能会抛出一个IO异常如果移动文件时出现异常。例如,如果一个文件已经在目标路径上存在,但没有指定StandardCopyOPtion.REPLACE_EXISTING或者源文件不存在等其它情况。

Files.delete()

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

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

try {
    Files.delete(path);
} catch (IOException e) {
    //deleting file failed
    e.printStackTrace();
}

首先创建了需要删除文件的Path实例。然后,调用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的实现会在不同的时候被调用。需要你不想要实现FileVsitor的全部方法,可以继承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_SIBLINGS
  • SKIP_SUBTREE

通过返回这些值调用者可以确定后续需要怎么做。

CONTINUE 意味着文件遍历按正常继续。

TERMINATE 表示文件遍历应该结束。

SKIP_SIBLINGS 表示遍历应该继续,但不继续访问相邻的文件或目录。

SKIP_SUBTREE 表示文件遍历应该继续,但不访问子目录。这个值只会在preVisitDirectory中返回,如果在其它函数中返回,会被解析成CONTINUE。

查找文件

下面是一个继承SimpleFileVisitor来查找README.txt文件的walkFileTree()方法的使用例子:

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()方法仅会删除一个空目录。通过遍历所有的目录并删除每个目录下的文件,然后删除目录自己。下面是一个递归删除目录的例子:

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();
}

Files类中的其他方法

java.nio.file.Files类下包含其他的许多方法,如创建符号链接,检测文件的大小,设置文件权限等等。请从java.nio.file.Files的JavaDoc查看这些方法的明细。