Java NIO files

pedro7發表於2021-08-07

Java NIO files

java.nio.file.Files類提供了許多操作檔案的方法,它們往往和Path類合作使用。

1.Files.exits()

Files#exits()方法檢查一個Path是否存在於當前的檔案系統中。假如我們直接使用Path例項的相關方法,那麼一個不存在的檔案可能會被建立,如果我們想避免這種情況的發生,那麼可以先使用Files#extis()來進行一下檢查。

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

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

顯然,exits()方法有兩個引數,第一個是要檢查的path,第二個是exits()方法的選項陣列。如LinkOption.NOFOLLOW_LINKS代表不允許跟隨檔案系統中的符號連結來確定路徑是否存在。

2.Files.createDirectory()

Files#createDirectory()方法利用Path建立一個新的目錄。

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

通過上面的示例一眼就可以看出這個方法是幹嘛用的,另外就是注意一下通過丟擲異常來對不同的情況進行處理。

注:假如父目錄不存在,可能丟擲IOException異常。

3.Files.copy()

Files#copy()方法將檔案從一個path複製到另一個。

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

功能太明顯了。如果目標檔案已存在,會丟擲FileAlreadyExistsException,如果試圖將檔案複製到不存在的目錄,會丟擲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();
}

在copy方法的引數列表中加上這個StandardCopyOption.REPLACE_EXISTING,可以在目標檔案存在時強制覆蓋。

4.FIles.move()

Files#move()方法將檔案從一個path移動到另一個path。同時可以設定目標的檔名,也就是說不僅可以實現移動功能,也可以實現重新命名或移動+重新命名。

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

功能顯而易見,StandardCopyOption.REPLACE_EXISTING引數的意義和copy方法中的一樣。

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

6.Files.walkFileTree()

Files.walkFileTree()方法可以遞迴遍歷目錄樹。它使用一個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 {
}

Files.walkFileTree()方法需要一個FileVisitor的實現類作為引數,實現FileVisitor介面就需要實現上述方法。如果不想做特殊實現或者只想實現一部分,可以繼承SimpleFileVisitor類,它其中有對FileVisitor的方法的預設實現。

下面是示例:

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

這些方法在遍歷的不同時間被呼叫

  • preVisitDirectory()方法在訪問任何目錄前被呼叫。
  • postVisitDirectory()方法在訪問任何目錄後被呼叫。
  • visitFile()方法在訪問任何檔案時被呼叫。
  • visitFileFailed()在訪問任何檔案失敗時被呼叫。(比如沒許可權)

每個方法返回;一個FileVisitResult列舉,這些返回指決定了遍歷如何進行。包括

  • CONTINUE。表示遍歷將繼續正常進行。
  • TERMINATE。表示檔案遍歷將終止。
  • SKIP_SIBLINGS。表示檔案遍歷將繼續,但不在訪問此檔案/目錄的同級檔案/目錄。
  • SKIP_SUBTREE。表示檔案遍歷將繼續,但不再訪問此目錄內的檔案。

下面是一個通過walkFileTree()方法尋找名字為README.txt的檔案的示例。注意這裡的FileVisitor是繼承SimpleFileVisitor的,不過重寫了visitFile方法。

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

下面是一個通過walkFileTree()方法刪除名字為README.txt的檔案的示例。注意這裡的FileVisitor是繼承SimpleFileVisitor的,不過重寫了visitFile方法和postVisitDirectory方法。

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

7.其他方法

Files類中還有許多其他方法,可以自己去看API。