1. 程式人生 > >NIO 2 Paths 與 Files 使用

NIO 2 Paths 與 Files 使用

Paths 類與 Files 類屬於 NIO 2 的一部分,在 JDK 7 中引入,並在 JDK 8 中增加了一些適應新增特性(Stream等)的方法。這兩個類一般配合起來使用,其中封裝了許多操作檔案的方法,相比之前的 File 類,更為簡潔方便,也包含更細粒度的控制功能。

Path

Path 可以理解為定義的一個路徑,對應一個檔案或目錄。該檔案或目錄如果已經存在,則可以通過此Path 進行檔案的修改、刪除、複製等操作,否則需要先建立該檔案再進行後續操作。

建立 Path 的三種方式:

  • 通過 Paths 的 get() 方法,該方法的引數可以是 String 或 URI

Path path = Paths.get(“d:/test.txt”); // 如果 String 過長,可以將其分成多個 String 表示 Path lPath = Paths.get(“d:”, “test.txt”);

  • 通過 FileSystem 的 getPath() 方法,FileSystems.getDefault() 方法返回預設的檔案系統供 JVM 訪問

Path path = FileSystems.getDefault().getPath(“d:”, “test.txt”);

  • 通過 File 轉化

File file = new File(“d:”, “test.txt”); Path path = file.toPath();

檔案操作(Files)

  • 建立與刪除檔案/目錄
//建立檔案
Path path = Paths.get("d:/test.txt");
Files.
createFile(path); //建立目錄 Path directory = Paths.get("d:", "pictures"); Files.createDirectory(directory);

刪除有兩種方法:

public static void delete(Path path) throws IOException public static boolean deleteIfExists(Path path) throws IOException

兩者的區別在於當檔案不存在時,前者會丟擲 NoSuchFileException 異常,後者會返回 false。

注意:當刪除目錄時,無論使用哪種刪除方法,都只能刪除空目錄,否則會丟擲 DirectoryNotEmptyException 異常。

  • 檔案複製

public static Path copy(Path source, Path target, CopyOption… options)

示例如下:

Path source = Paths.get("d:/test.txt");
Path target = Paths.get("e:/");
Files.copy(source, target.resolve(source.getFileName()), StandardCopyOption.REPLACE_EXISTING);

resolve() 方法用來將兩個路徑合在一起組成新的路徑,以上例來說,它的作用是將 d 盤下的 test.txt檔案複製到 e 盤下,檔名保持一致。

StandardCopyOption 有如下型別:

  • REPLACE_EXISTING:替換已存在的檔案
  • COPY_ATTRIBUTES:將原始檔的檔案屬性資訊複製到目標檔案中
  • ATOMIC_MOVE:原子性複製,即不存在因失敗導致檔案複製不完整的情況出現

注意:使用 copy 方法複製目錄時只會複製空目錄,目錄中的檔案不會複製。複製目錄需要通過檔案遍歷來實現。

  • 檔案移動/重新命名

// 檔案移動與重新命名用到的是同一個命令 public static Path move(Path source, Path target, CopyOption… options)

示例如下:

Path source = Paths.get("d:/test.txt");
Path target = Paths.get("e:/");//注意要有"/",否則會使用當前工作目錄

//移動到其它目錄
Files.move(source, target.resolve(source.getFileName()), StandardCopyOption.REPLACE_EXISTING);

//重新命名檔案
Files.createFile(source);
Files.move(source, source.resolveSibling("test2.txt"), StandardCopyOption.REPLACE_EXISTING);

resolveSibling() 方法的作用與 resolve() 方法類似,但它是以 source 的父目錄與方法中的引數進行合併,例如 source.resolveSibling(“test2.txt”) 代表的路徑就是 “d:/test2.txt”。

注意:當移動目錄時,只能移動空目錄,否則會丟擲 DirectoryNotEmptyException 異常;給目錄重新命名時沒有此限制。

  • 檔案讀寫
在之前,如果要使用 BufferedWriter 與 BufferedReader 實現這一操作,需要寫一連串冗長的程式碼;現在,Files類提供了實用的方法封裝了這些操作。
Path path = Paths.get("d:/test.txt");
Files.createFile(path);
BufferedWriter bw = Files.newBufferedWriter(path, StandardCharsets.UTF_8, StandardOpenOption.WRITE);
bw.write("Hello");
bw.newLine();
bw.write("World!");
bw.flush();
bw.close();

在檔案讀取時,有以下兩種方法:

//讀檔案方式1(jdk1.7)
BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8);
String str;
while ((str = reader.readLine()) != null) {
    System.out.println(str);
}
reader.close();

//讀檔案方式2(jdk1.8)
List<String> list = Files.readAllLines(path);
System.out.println(list);
  • 檔案遍歷
檔案遍歷有兩種方式:非遞迴遍歷與遞迴遍歷。
  1. 非遞迴遍歷的兩種方法
Path dir = Paths.get("E:/Pictures");
//非遞迴遍歷目錄方式1(jdk1.7)
DirectoryStream<Path> directoryStream = Files.newDirectoryStream(dir);
for (Path item : directoryStream)
System.out.println(item.toAbsolutePath());
directoryStream.close();

//非遞迴遍歷目錄方式2(jdk1.8)
Stream<Path> stream = Files.list(dir);
stream.forEach(System.out::println);
stream.close();
  1. 遞迴遍歷(深度優先遍歷)的兩種方法
//深度優先遍歷方式1(jdk1.7)
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
    //訪問檔案時呼叫
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attr) throws IOException {
        System.out.println(file.toAbsolutePath());
        return FileVisitResult.CONTINUE;
    }
    //訪問目錄前呼叫
    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attr) throws IOException {
        System.out.println(dir.toAbsolutePath());
        return FileVisitResult.CONTINUE;
    }
});

//深度優先遍歷方式2(jdk1.8)
Stream<Path> deepStream = Files.walk(dir);
deepStream.forEach(System.out::println);
stream.close();

可以看到,藉助於 JDK 8 中引入的 Stream 特性,語法更為簡潔易懂。

  • 獲取與修改檔案屬性

public static Map<String,Object> readAttributes(Path path, String attributes, LinkOption… options)

示例如下:

Path path = Paths.get("d:/test.txt");
System.out.println(Files.readAttributes(path, "*"));

輸出結果:

{lastAccessTime=2017-11-15T12:57:07.681984Z, lastModifiedTime=2017-11-15T12:57:07.681984Z, 
size=0, creationTime=2017-11-15T12:57:07.681984Z, isSymbolicLink=false, 
isRegularFile=true, fileKey=null, isOther=false, isDirectory=false}

也可以通過 setAttributes() 方法對檔案屬性進行修改:

public static Path setAttribute(Path path, String attribute, Object value, LinkOption… options)

示例如下:

Path path = Paths.get("d:/test.txt");

//更改檔案屬性方式1(Windows)
DosFileAttributeView view = Files.getFileAttributeView(path, DosFileAttributeView.class);
view.setArchive(false);

//更改檔案屬性方式2(Windows)
Files.setAttribute(path, "dos:archive", true);

方式1的好處是不需要記檔案屬性名,方式2的好處是簡潔易懂,但前提是需要知道準確的檔案屬性名。

注意:由於各個作業系統檔案屬性並不相同,Windows 下使用的是 DosFileAttributeView;如果是 Linux 作業系統,使用的是 PosixFileAttributeView。

  • 尋找檔案

public static Stream find(Path start, int maxDepth, BiPredicate<Path, BasicFileAttributes> matcher, FileVisitOption… options)

以尋找字尾為 “docx” 的檔案為例:

Path dir = Paths.get("E:/Pictures");
Stream<Path> result = Files.find(dir, 2, (p, basicFileAttributes) -> String.valueOf(p).endsWith(".docx"));
result.map(Path::getFileName).forEach(System.out::println);

也可以在 walk() 方法返回的 Stream 上呼叫過濾器 filter 來實現此功能。但 find() 方法可能更有效,因為避免了重複檢索 BasicFileAttributes。

  • 檔案監視 WatchService

在 NIO 2 中也提供了檔案監視機制,可以對檔案的建立、修改和刪除事件進行監視。監視服務的具體實現依賴於作業系統,在可用的情況下會利用作業系統本地的檔案監視功能。 但是,當作業系統不支援檔案監視機制時,監視服務將採用輪詢機制。

使用檔案監視的步驟如下:

  • 建立 WatchService 例項 watch
  • 將需要監控的每個目錄註冊到觀察器,返回 WatchKey 例項 key。在註冊目錄時,指定要通知的事件的型別
  • 通過一個無限迴圈等待傳入的事件。當事件發生時,key 發出訊號,加入到 watch 的 queue
  • 遍歷 key 的事件並根據事件型別進行處理
  • 重置 key,重新等待事件
  • 關閉監視服務
示例如下:
public class WatchServiceDemo {

    public static void main(String[] args) throws IOException, InterruptedException {
        String path = "d:/watch";
        watch(path);
    }

    public static void watch(String directory) throws IOException, InterruptedException {
        Path path = Paths.get(directory);
        WatchService watch = FileSystems.getDefault().newWatchService();
        path.register(watch, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        while (true) {
            WatchKey key = watch.take();
            for (WatchEvent event : key.pollEvents()) {
                if (event.kind() == ENTRY_CREATE) {
                    System.out.println("檔案建立:" + event.context());
                }
                if (event.kind() == ENTRY_MODIFY) {
                    System.out.println("檔案修改:" + event.context());
                }
                if (event.kind() == ENTRY_DELETE) {
                    System.out.println("檔案刪除:" + event.context());
                }
            }
            boolean reset = key.reset();
            if (!reset) {
                System.out.println("監視服務終止!");
                break;
            }
        }
        watch.close();
   }
}

注意:在建立、重新命名檔案以及修改檔案內容時會監測到兩個重複的事件。此外,上述程式碼只能監測到該目錄下的檔案事件,而無法監測到子目錄中的檔案事件(但能監測到子目錄是否發生了變化)。

參考連結: