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 的對比和總結html

Path 接口和 Paths 類

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

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

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

建立 Path 對象

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

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個方法:ide

方法 描述
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 類。post

判斷文件是否存在

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);
相關文章
相關標籤/搜索