Java NIO 學習筆記(五)----路徑、文件和管道 Path/Files/Pipe


目錄:
Java NIO 學習筆記(一)----概述,Channel/Buffer
Java NIO 學習筆記(二)----聚集和分散,通道到通道
Java NIO 學習筆記(三)----Selector
Java NIO 學習筆記(四)----文件通道和網絡通道
Java NIO 學習筆記(五)----路徑、文件和管道 Path/Files/Pipe
Java NIO 學習筆記(六)----異步文件通道 AsynchronousFileChannel
Java NIO 學習筆記(七)----NIO/IO 的對比和總結

Path 接口和 Paths 類

Path 接口是 NIO2(AIO) 的一部分,是對 NIO 的更新,Path 接口已添加到 Java 7 中,完全限定類名是 java.nio.file.Path 。

Path 實例表示文件系統中的路徑。 路徑可以指向文件或目錄,也可以是絕對的或相對的。在某些操作系統中,不要將文件系統路徑與環境變量中的 path 路徑相混淆。 java.nio.file.Path 接口與路徑環境 path 變量無關。

在許多方面,java.nio.file.Path 接口類似於 java.io.File 類,但存在一些細微差別。 但在許多情況下,可以使用 Path 接口替換 File 類的使用。

創建 Path 對象

可以使用名為 Paths.get() 的 Paths 類(java.nio.file.Paths)中的靜態方法創建 Path 實例,get()方法是 Path 實例的工廠方法,一個示例如下:

public class PathExample {
    public static void main(String[] args) {
        // 使用絕對路徑創建
        Path absolutePath = Paths.get("D:\\test\\1.txt");
        // 使用相對路徑創建
        Path relativePath = Paths.get("D:\\test", "1.txt");
        System.out.println(absolutePath.equals(relativePath)); // ture
    }
}

注意路徑分隔符在 Windows 上是“\”,在 Linux 上是 “/”。

Paths 類只有2個方法:

方法 描述
static Path get(String first, String... more) 將路徑字符串或在連接時形成路徑字符串的字符串序列轉換為路徑。
static Path (URI uri) 將給定URI轉換為路徑對象。

Path 接口部分方法:

方法 描述
boolean endsWith(Path other) 測試此路徑是否以給定路徑結束。
boolean equals(Object other) 取決於文件系統的實現。一般不區分大小寫,有時區分。 不訪問文件系統。
Path normalize() 返回一個路徑,該路徑消除了冗余的名稱元素,比如'.', '..'
Path toAbsolutePath() 返回表示該路徑的絕對路徑的路徑對象。
File toFile() 返回表示此路徑的 File 對象。
String toString() 返回的路徑字符串使用默認名稱分隔符分隔路徑中的名稱。

Files

NIO 文件類(java.nio.file.Files)為操作文件系統中的文件提供了幾種方法,File 類與 java.nio.file.Path 類一起工作,需要了解 Path 類,然后才能使用 Files 類。

判斷文件是否存在

static boolean exists(Path path, LinkOption... options)

options 參數用於指示,在文件是符號鏈接的情況下,如何處理該符號鏈接,默認是處理符號鏈接的。其中 LinkOption 對象是一個枚舉類,定義如何處理符號鏈接的選項。整個類只有一個 NOFOLLOW_LINKS; 常亮,代表不跟隨符號鏈接。

createDirectory(Path path) 創建目錄

Path output = Paths.get("D:\\test\\output");
Path newDir = Files.createDirectory(output);
// Files.createDirectories(output); // 這個方法可以一並創建不存在的父目錄
System.out.println(output == newDir); // true
System.out.println(Files.exists(output)); // true

如果創建目錄成功,則返回指向新創建的路徑的 Path 實例,此實例和參數是同一個實例。
如果該目錄已存在,則拋出 FileAlreadyExistsException 。 如果出現其他問題,可能會拋出IOException ,例如,如果所需的新目錄的父目錄不存在。

復制文件

一共有 3 個復制方法:

static long copy(Path source, OutputStream out);
static Path copy(Path source, Path target, CopyOption... options);
static long copy(InputStream in, Path target, CopyOption... options)

其中 CopyOption 選項可以選擇指定復制模式,一般是其子枚舉類 StandardCopyOption 提供選項,有 3 種模式,第二個參數是可變形參,可以多個組合一起使用:

  1. ATOMIC_MOVE :原子復制,不會被線程調度機制打斷的操作;一旦開始,就一直運行到結束;
  2. COPY_ATTRIBUTES :同時復制屬性,默認是不復制屬性的;
  3. REPLACE_EXISTING :重寫模式,會覆蓋已存在的目的文件;

一個例子如下:

Path sourcePath = Paths.get("D:\\test\\source.txt"); // 源文件必須先存在
Path desPath = Paths.get("D:\\test\\des.txt"); // 目的文件可以不存在
Files.copy(sourcePath, desPath); // 默認情況,如果目的文件已存在則拋出異常
Files.copy(sourcePath, desPath, StandardCopyOption.REPLACE_EXISTING); // 覆蓋模式

注意:復制文件夾的時候,只能復制空文件夾,如果文件夾非空,需要遞歸復制,否則只能得到一個空文件夾,而文件夾里面的文件不會被復制。

移動文件/文件夾

只有 1 個移動文件或文件夾的方法:

static Path move(Path source, Path target, CopyOption... options);

如果文件是符號鏈接,則移動符號鏈接本身,而不是符號鏈接指向的實際文件。
和移動文件一樣,也存在第三個可選參數 CopyOption ,參考上述。如果移動文件失敗,可能會拋出 IOException,例如,如果文件已存在於目標路徑中,並且遺漏了覆蓋選項,或者要移動的源文件不存在等。

和復制文件夾不一樣,如果文件夾里面有內容,復制只會復制空文件夾,而移動會把文件夾里面的所有東西一起移動過去,以下是一個移動文件夾的示例:

// 移動 s 目錄到一個不存在的新目錄
Path s = Paths.get("D:\\s");
Path d = Paths.get("D:\\test\\test");
Files.createDirectories(d.getParent());
Files.move(s, d);

和 Linux mv 命令一樣,重命名文件與移動文件方式相同,移動文件還可以將文件移動到不同的目錄並可以同時更改其名稱。 另外 java.io.File 類也可以使用它的 renameTo() 方法來實現移動文件,但現在 java.nio.file.Files 類中也有文件移動功能。

刪除文件/文件夾

static void delete(Path path);
static boolean deleteIfExists(Path path); // 如果文件被此方法刪除則返回 true

如果文件是目錄,則該目錄必須為空才能刪除。

Files.walkFileTree() 靜態方法

刪除和復制文件夾的時候,如果文件夾為空,那么會刪除失敗或者只能復制空文件夾,此時可以使用 walkFileTree() 方法進行遍歷文件樹,然后在 FileVisitor 對象的 visitFile() 方法中執行刪除或復制文件操作。
Files 類有 2 個重載的 walkFileTree() 方法,如下:

static Path walkFileTree(Path start,
                                FileVisitor<? super Path> visitor);

static Path walkFileTree(Path start,
                                Set<FileVisitOption> options,
                                int maxDepth,
                                FileVisitor<? super Path> visitor);

將 Path 實例和 FileVisitor 作為參數,walkfiletree() 方法可以遞歸遍歷目錄樹。Path 實例指向要遍歷的目錄。在遍歷期間調用 FileVisitor ,首先介紹 FileVisitor 接口:

public interface FileVisitor<T> {
    
    FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) throws IOException;

    FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException;
    
    FileVisitResult visitFileFailed(T file, IOException exc) throws IOException;
    
    FileVisitResult postVisitDirectory(T dir, IOException exc) throws IOException;
}

必須自己實現 FileVisitor 接口,並將其實現的實例傳遞給 walkFileTree() 方法。在目錄遍歷期間,將在不同的時間調用 FileVisitor 實現的 4 個方法,代表對遍歷到的文件或目錄進行什么操作。如果不需要使用到所有方法,可以擴展 SimpleFileVisitor 類,該類包含 FileVisitor 接口中所有方法的默認實現。

Files.walkFileTree(inputPath, 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 {
        return FileVisitResult.CONTINUE;
    }

    // 訪問文件失敗會調用此方法,只針對文件,不會對目錄執行
    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
        return FileVisitResult.CONTINUE;
    }

    // 訪問文件夾之后會調用此方法
    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
        return FileVisitResult.CONTINUE;
    }
});

這四個方法都返回一個 FileVisitResult 枚舉實例。FileVisitResult 枚舉包含以下四個選項:

  • CONTINUE : 繼續
  • TERMINATE : 終止
  • SKIP_SIBLINGS : 跳過兄弟節點,然后繼續
  • SKIP_SUBTREE : 跳過子樹(不訪問此目錄的條目),然后繼續,僅在 preVisitDirectory 方法返回時才有意義,除此以外和 CONTINUE 相同。

通過返回其中一個值,被調用的方法可以決定文件遍歷時接下來應該做什么。

搜索文件

walkFileTree() 方法還可以用於搜索文件,下面這個例子擴展了 SimpleFileVisitor 來查找一個名為 input.txt 的文件:

Path rootPath = Paths.get("D:\\test");
String fileToFind = File.separator + "input.txt";

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: " + fileString);
            return FileVisitResult.TERMINATE;
        }
        return FileVisitResult.CONTINUE;
    }
});

同理,刪除有內容的目錄時,可以重寫 visitFile() 方法,並在里面執行刪除文件操作,重寫 postVisitDirectory() 方法,並在里面執行刪除目錄操作即可。

Files 類中的其他方法

Files 類包含許多其他有用的函數,例如用於創建符號鏈接,確定文件大小,設置文件權限等的函數。有關java.nio.file.Files 類的詳細信息,請查看 JavaDoc

管道 Pipe

Pipe 是兩個線程之間的單向數據連接。管道有 source 通道和一個 sink 通道,將數據寫入 sink 通道,就可以從 source 通道讀取該數據。
以下是管道原理的說明:

image

使用管道進行讀取數據

先看一個完整的例子:

public class PipeExample {
    public static void main(String[] args) throws IOException {
        Pipe pipe = Pipe.open();
        Pipe.SinkChannel sinkChannel = pipe.sink(); // sink 通道寫入數據
        String data = "some string";

        ByteBuffer buffer = ByteBuffer.allocate(32);
        buffer.clear();
        buffer.put(data.getBytes());

        buffer.flip(); // 反轉緩沖區,准備被讀取
        while (buffer.hasRemaining()) {
            sinkChannel.write(buffer); // 將 Buffer 的數據寫入 sink 通道
        }

        Pipe.SourceChannel sourceChannel = pipe.source(); // 源通道讀取數據
        ByteBuffer readBuffer = ByteBuffer.allocate(32);
        int bytesRead = sourceChannel.read(readBuffer); // 返回值代表讀取了多少數據
        System.out.println("Read: " + bytesRead); // Read: 11

        System.out.println(new String(readBuffer.array())); // some string
    }
}

如上代碼,首先要創建管道,打開管道之后是使用同一個管道對象獲取對應的 sink 通道和 source 通道的,這會自動地將兩個通道連接起來,作為對比,在標准 IO 管道中是分別創建讀管道和寫管道,然后在構造器中或者使用pipe1.connect(pipe2) 方法來連接起來,如下:

PipedOutputStream output = new PipedOutputStream();

PipedInputStream input = new PipedInputStream();
input.connect(output);
// 或者使用如下1行代碼,可以代替上面2行代碼來連接2個管道
//PipedInputStream input = new PipedInputStream(output);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM