【Java實戰?】從IO到NIO:Java高并發編程的飛躍

目錄

  • 一、NIO 與 IO 的深度剖析
    • 1.1 IO 的局限性
    • 1.2 NIO 核心特性
    • 1.3 NIO 核心組件
    • 1.4 NIO 適用場景
  • 二、NIO 核心組件實戰
    • 2.1 Buffer 緩沖區
    • 2.2 Channel 通道
    • 2.3 Selector 選擇器
    • 2.4 NIO 文件操作案例
  • 三、NIO2.0 實戰
    • 3.1 Path 類
    • 3.2 Files 類
    • 3.3 Files 類高級操作
    • 3.4 NIO2.0 實戰案例


一、NIO 與 IO 的深度剖析

在 Java 編程領域,輸入輸出(I/O)操作是與外部資源交互的基礎,如文件、網絡連接等。傳統的 I/O 模型在處理簡單場景時表現出色,但隨著應用程序對性能和并發處理能力要求的不斷提高,其局限性逐漸顯現。Java NIO(New I/O)的出現,為開發者提供了一種更高效、更靈活的 I/O 處理方式,尤其在高并發和大數據傳輸場景中展現出顯著優勢。接下來,我們將深入探討 NIO 與傳統 IO 的區別,以及 NIO 的核心特性、組件和適用場景。

1.1 IO 的局限性

傳統的 Java IO 是基于流(Stream)的操作,其主要特點是面向字節或字符序列,數據以順序的方式從數據源讀取或寫入到目的地。這種方式在處理簡單的 I/O 任務時非常直觀和方便,但在高并發和大數據量處理場景下,暴露出了一些明顯的局限性。

  • 阻塞問題:Java IO 是阻塞式的,當一個線程調用 read () 或 write () 方法時,該線程會被阻塞,直到有數據可讀或數據完全寫入。這意味著在 I/O 操作進行期間,線程無法執行其他任務,嚴重浪費了 CPU 資源。例如,在一個服務器應用中,如果同時有大量客戶端連接,每個連接都需要一個獨立的線程來處理 I/O 操作,那么隨著連接數的增加,線程數量也會急劇增加,導致線程上下文切換開銷增大,系統性能急劇下降。
  • 面向流的局限性:IO 是面向流的,流是單向的,要么是輸入流,要么是輸出流。這就限制了數據處理的靈活性,對于一些需要同時進行讀寫操作的場景,需要分別創建輸入流和輸出流,增加了代碼的復雜性。此外,流操作是順序的,無法隨機訪問數據,對于一些需要隨機讀寫的場景,如文件的部分內容更新,處理起來比較困難。
  • 單線程處理能力有限:由于每個 I/O 操作都需要一個線程來處理,當并發連接數較多時,線程資源會被大量消耗,系統的可擴展性受到限制。而且,線程的創建和銷毀也會帶來一定的開銷,進一步降低了系統的性能。

1.2 NIO 核心特性

Java NIO 旨在解決傳統 IO 的局限性,提供了一系列新的特性,使其在高并發和大數據處理場景中表現出色。

  • 非阻塞特性:NIO 支持非阻塞 I/O 操作,當一個線程從通道請求數據時,如果沒有數據可用,該線程不會被阻塞,而是立即返回。這使得一個線程可以管理多個通道,大大提高了系統的并發處理能力。例如,在一個網絡服務器中,可以使用一個線程來監聽多個客戶端連接的事件,當有事件發生時,才對相應的連接進行處理,避免了線程的阻塞等待,提高了資源利用率。
  • 面向緩沖區:NIO 基于緩沖區(Buffer)進行數據操作,所有數據都要先寫入緩沖區,然后再從緩沖區讀取。緩沖區是一塊連續的內存區域,提供了更靈活的數據處理方式,可以隨機訪問數據,并且支持數據的讀寫、標記、重置等操作。與面向流的 IO 相比,面向緩沖區的 NIO 在處理大數據量時效率更高,因為它減少了數據的拷貝次數。
  • 選擇器:選擇器(Selector)是 NIO 的一個重要組件,它可以用于同時監控多個通道的讀寫事件,并在有事件發生時立即做出響應。通過選擇器,可以實現單線程監聽多個通道的效果,從而提高系統吞吐量和運行效率。例如,在一個網絡服務器中,可以將所有的客戶端連接通道注冊到一個選擇器上,選擇器不斷輪詢這些通道,當有通道準備好進行 I/O 操作時,選擇器就會通知相應的線程進行處理,避免了每個連接都需要一個線程來處理的情況,節省了系統資源。

1.3 NIO 核心組件

NIO 的核心組件包括通道(Channel)、緩沖區(Buffer)和選擇器(Selector),它們協同工作,實現了高效的 I/O 操作。

  • Channel(通道):通道是連接數據源或目的地的雙向通道,可以進行讀、寫或同時進行讀寫操作。與傳統的流不同,通道支持異步操作,并且可以與多個緩沖區進行交互。常見的通道類型有 FileChannel(用于文件的讀寫操作)、SocketChannel(用于通過 TCP 協議進行網絡通信)、ServerSocketChannel(用于監聽客戶端的連接請求)和 DatagramChannel(用于通過 UDP 協議進行網絡通信)。例如,通過 FileChannel 可以實現對文件的高效讀寫,支持隨機訪問和內存映射等高級操作;通過 SocketChannel 可以實現非阻塞的網絡通信,提高網絡應用的并發性能。
  • Buffer(緩沖區):緩沖區是 NIO 中數據的容器,用于存儲數據。所有數據的讀寫都要通過緩沖區進行,緩沖區提供了對數據的結構化訪問以及維護讀寫位置等信息。常用的緩沖區類型有 ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer 和 DoubleBuffer 等,分別用于存儲不同類型的數據。在使用緩沖區時,通常需要經歷分配 Buffer、寫入數據到 Buffer、切換 Buffer 為讀模式和從 Buffer 中讀取數據等步驟。例如,通過 ByteBuffer 的 allocate () 方法可以分配一個指定容量的緩沖區,然后使用 put () 方法將數據寫入緩沖區,再通過 flip () 方法將緩沖區從寫模式切換為讀模式,最后使用 get () 方法從緩沖區中讀取數據。
  • Selector(選擇器):選擇器用于監聽多個通道的事件,如連接打開、數據到達等。一個選擇器可以注冊多個通道,當其中的某些通道上有感興趣的事件發生時,這些通道就會變為可用狀態,可以在選擇器的選擇操作中被選中。通過選擇器,一個線程可以管理多個通道,實現非阻塞的 I/O 操作,提高系統的并發處理能力。使用選擇器的基本流程包括創建 Selector、將通道注冊到 Selector 上并指定感興趣的事件類型,以及不斷循環地調用 Selector 的 select () 方法來檢查是否有通道已經準備好進行 I/O 操作,最后處理準備就緒的通道。

1.4 NIO 適用場景

NIO 的特性使其在以下場景中具有明顯的優勢:

  • 高并發網絡應用:在開發高性能的網絡服務器或客戶端時,NIO 可以處理大量并發連接,通過非阻塞 I/O 和選擇器機制,提高系統的并發處理能力和資源利用率。例如,常見的網絡通信框架如 Netty、Mina 等,都基于 NIO 實現,能夠支持海量的并發連接,廣泛應用于互聯網、游戲、金融等領域。
  • 大數據傳輸:NIO 提供的通道和緩沖區概念,可以高效地進行大規模數據的傳輸。在處理大文件讀寫時,通過 FileChannel 和 ByteBuffer 的配合,可以減少 I/O 操作的次數,提高數據傳輸的效率。例如,在進行文件的拷貝、備份等操作時,使用 NIO 可以大大縮短操作時間。
  • 需要高效利用系統資源的場景:由于 NIO 可以使用較少的線程來處理大量的 I/O 操作,減少了線程上下文切換的開銷,因此在對系統資源利用率要求較高的場景中,NIO 是更好的選擇。例如,在一些嵌入式系統或資源受限的環境中,使用 NIO 可以在有限的資源條件下實現高效的 I/O 處理。

二、NIO 核心組件實戰

了解了 NIO 的基本概念和原理后,接下來我們通過實際的代碼示例來深入學習 NIO 的核心組件 —— 緩沖區(Buffer)、通道(Channel)和選擇器(Selector)的使用。

2.1 Buffer 緩沖區

在 NIO 中,Buffer 是一個用于存儲數據的容器,所有數據的讀寫都要通過緩沖區進行。常見的緩沖區類型有 ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer 和 DoubleBuffer 等,分別用于存儲不同類型的數據。下面以 ByteBuffer 為例,介紹緩沖區的創建、讀寫操作以及 flip 和 clear 方法的使用。

創建緩沖區
可以使用靜態方法 allocate 來創建一個指定容量的緩沖區。例如,創建一個容量為 1024 字節的 ByteBuffer:

ByteBuffer buffer = ByteBuffer.allocate(1024);

也可以通過 wrap 方法將一個現有的數組包裝成緩沖區:

byte[] array = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(array);

寫入數據

使用 put 方法將數據寫入緩沖區。例如,將一個字符串寫入 ByteBuffer:

String message = "Hello, NIO!";
buffer.put(message.getBytes());

也可以從通道中讀取數據到緩沖區,假設我們有一個 FileChannel:

FileInputStream fis = new FileInputStream("example.txt");
FileChannel channel = fis.getChannel();
channel.read(buffer);

讀取數據

在讀取數據之前,需要先調用 flip 方法將緩沖區從寫模式切換為讀模式。flip 方法會將 position 設置為 0,并將 limit 設置為當前 position 的值,這樣就可以從緩沖區的開頭開始讀取數據了。例如:

buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String result = new String(data);
System.out.println(result);

flip 與 clear 方法

  • flip 方法:如上述所說,用于將緩沖區從寫模式切換為讀模式。
  • clear 方法:用于清空緩沖區,將 position 設置為 0,limit 設置為容量大小。但需要注意的是,clear 方法并不會真正刪除緩沖區中的數據,只是重置了緩沖區的狀態,以便重新寫入數據。例如:
buffer.clear();

通過以下完整代碼示例,可以更清晰地看到緩沖區的工作原理:

import java.nio.ByteBuffer;public class BufferExample {public static void main(String[] args) {// 創建緩沖區ByteBuffer buffer = ByteBuffer.allocate(1024);// 寫入數據String message = "Hello, NIO!";buffer.put(message.getBytes());// 切換為讀模式buffer.flip();// 讀取數據byte[] data = new byte[buffer.remaining()];buffer.get(data);String result = new String(data);System.out.println(result);// 清空緩沖區buffer.clear();}
}

2.2 Channel 通道

Channel 是 NIO 中用于與數據源或目的地進行數據傳輸的通道,它可以進行讀、寫或同時進行讀寫操作。常見的通道類型有 FileChannel(用于文件的讀寫操作)、SocketChannel(用于通過 TCP 協議進行網絡通信)、ServerSocketChannel(用于監聽客戶端的連接請求)和 DatagramChannel(用于通過 UDP 協議進行網絡通信)。下面分別介紹 FileChannel 和 SocketChannel 的使用。

FileChannel 文件操作

FileChannel 主要用于文件的讀寫操作,它可以實現對文件的高效讀寫,支持隨機訪問和內存映射等高級操作。以下是使用 FileChannel 讀取和寫入文件的示例:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class FileChannelExample {public static void main(String[] args) {try (FileInputStream fis = new FileInputStream("input.txt");FileOutputStream fos = new FileOutputStream("output.txt");FileChannel inChannel = fis.getChannel();FileChannel outChannel = fos.getChannel()) {// 創建緩沖區ByteBuffer buffer = ByteBuffer.allocate(1024);// 從輸入通道讀取數據到緩沖區while (inChannel.read(buffer) != -1) {// 切換緩沖區為讀模式buffer.flip();// 從緩沖區寫入數據到輸出通道outChannel.write(buffer);// 清空緩沖區,為下一次讀取做準備buffer.clear();}System.out.println("文件復制完成!");} catch (Exception e) {e.printStackTrace();}}
}

SocketChannel 網絡操作

SocketChannel 用于通過 TCP 協議進行網絡通信,可以實現非阻塞的網絡通信,提高網絡應用的并發性能。以下是一個簡單的 SocketChannel 客戶端和服務器端示例:

服務器端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;public class NioServer {public static void main(String[] args) {try {// 創建選擇器Selector selector = Selector.open();// 創建服務器套接字通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);serverSocketChannel.socket().bind(new InetSocketAddress(8080));// 將服務器通道注冊到選擇器上,監聽連接事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服務器啟動,監聽端口8080...");while (true) {// 阻塞直到有事件發生selector.select();Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();keyIterator.remove();if (key.isAcceptable()) {// 處理新連接ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();SocketChannel clientChannel = serverChannel.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ);System.out.println("新客戶端連接: " + clientChannel.getRemoteAddress());} else if (key.isReadable()) {// 處理讀事件SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String message = new String(data);System.out.println("收到客戶端消息: " + message);// 回顯消息給客戶端ByteBuffer outBuffer = ByteBuffer.wrap(("服務器已收到消息: " + message).getBytes());clientChannel.write(outBuffer);} else if (bytesRead == -1) {// 客戶端關閉連接clientChannel.close();System.out.println("客戶端斷開連接: " + clientChannel.getRemoteAddress());}}}}} catch (IOException e) {e.printStackTrace();}}
}

客戶端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;public class NioClient {public static void main(String[] args) {try {// 創建SocketChannelSocketChannel clientChannel = SocketChannel.open();clientChannel.configureBlocking(false);// 連接服務器clientChannel.connect(new InetSocketAddress("localhost", 8080));// 等待連接完成while (!clientChannel.finishConnect()) {// 可以做點別的事,或者稍微等等}System.out.println("已連接到服務器");// 發送消息String message = "你好,服務器!";ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());clientChannel.write(buffer);// 接收服務器回顯消息buffer.clear();int bytesRead = clientChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String response = new String(data);System.out.println("收到服務器回顯: " + response);}// 關閉通道clientChannel.close();} catch (IOException e) {e.printStackTrace();}}
}

2.3 Selector 選擇器

Selector 是 NIO 中的一個重要組件,它可以用于同時監控多個通道的讀寫事件,并在有事件發生時立即做出響應。通過選擇器,可以實現單線程監聽多個通道的效果,從而提高系統吞吐量和運行效率。以下是使用 Selector 的基本步驟和代碼示例:

注冊通道

首先需要創建一個 Selector,然后將通道注冊到 Selector 上,并指定感興趣的事件類型。例如,將 ServerSocketChannel 注冊到 Selector 上,監聽連接事件:

Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

監聽事件
使用 selector 的 select 方法來監聽注冊通道上的事件。select 方法會阻塞,直到有感興趣的事件發生。例如:

int readyChannels = selector.select();
if (readyChannels > 0) {// 處理就緒事件
}

處理就緒事件

通過 selectedKeys 方法獲取已就緒的 SelectionKey 集合,然后遍歷該集合,根據不同的事件類型處理相應的通道。例如:

Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();keyIterator.remove();if (key.isAcceptable()) {// 處理新連接ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();SocketChannel clientChannel = serverChannel.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ);System.out.println("新客戶端連接: " + clientChannel.getRemoteAddress());} else if (key.isReadable()) {// 處理讀事件SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String message = new String(data);System.out.println("收到客戶端消息: " + message);// 回顯消息給客戶端ByteBuffer outBuffer = ByteBuffer.wrap(("服務器已收到消息: " + message).getBytes());clientChannel.write(outBuffer);} else if (bytesRead == -1) {// 客戶端關閉連接clientChannel.close();System.out.println("客戶端斷開連接: " + clientChannel.getRemoteAddress());}}
}

完整的服務器端代碼示例(包含 Selector 的使用):

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;public class SelectorExample {public static void main(String[] args) {try {// 創建選擇器Selector selector = Selector.open();// 創建服務器套接字通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);serverSocketChannel.socket().bind(new InetSocketAddress(8080));// 將服務器通道注冊到選擇器上,監聽連接事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服務器啟動,監聽端口8080...");while (true) {// 阻塞直到有事件發生selector.select();Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();keyIterator.remove();if (key.isAcceptable()) {// 處理新連接ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();SocketChannel clientChannel = serverChannel.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ);System.out.println("新客戶端連接: " + clientChannel.getRemoteAddress());} else if (key.isReadable()) {// 處理讀事件SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String message = new String(data);System.out.println("收到客戶端消息: " + message);// 回顯消息給客戶端ByteBuffer outBuffer = ByteBuffer.wrap(("服務器已收到消息: " + message).getBytes());clientChannel.write(outBuffer);} else if (bytesRead == -1) {// 客戶端關閉連接clientChannel.close();System.out.println("客戶端斷開連接: " + clientChannel.getRemoteAddress());}}}}} catch (IOException e) {e.printStackTrace();}}
}

2.4 NIO 文件操作案例

為了更直觀地展示 NIO 在文件操作中的優勢,我們通過一個大文件高效讀寫的案例來進行說明。假設我們有一個大小為 1GB 的大文件,需要將其讀取并復制到另一個文件中,對比傳統 IO 和 NIO 的實現方式和性能表現。

傳統 IO 實現

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class TraditionalIoExample {public static void main(String[] args) {long startTime = System.currentTimeMillis();try (FileInputStream fis = new FileInputStream("largeFile.txt");FileOutputStream fos = new FileOutputStream("copy_largeFile.txt")) {byte[] buffer = new byte[1024];int length;while ((length = fis.read(buffer)) != -1) {fos.write(buffer, 0, length);}} catch (IOException e) {e.printStackTrace();}long endTime = System.currentTimeMillis();System.out.println("傳統IO復制文件耗時: " + (endTime - startTime) + " 毫秒");}
}

NIO 實現

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;public class NioFileExample {public static void main(String[] args) {long startTime = System.currentTimeMillis();try (FileChannel inChannel = FileChannel.open(Paths.get("largeFile.txt"), StandardOpenOption.READ);FileChannel outChannel = FileChannel.open(Paths.get("copy_largeFile.txt"), StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024); // 1MB緩沖區while (inChannel.read(buffer) != -1) {buffer.flip();outChannel.write(buffer);buffer.clear();}} catch (IOException e) {e.printStackTrace();}long endTime = System.currentTimeMillis();System.out.println("NIO復制文件耗時: " + (endTime - startTime) + " 毫秒");}
}

通過實際測試可以發現,NIO 在處理大文件讀寫時,由于其采用了緩沖區和通道的機制,減少了系統調用次數和數據拷貝次數,相比傳統 IO 具有更高的效率。在上述案例中,NIO 復制文件的耗時通常會明顯低于傳統 IO,尤其是在處理超大文件時,這種優勢更加顯著。這是因為 NIO 的緩沖區可以一次性讀取和寫入大量數據,減少了 I/O 操作的頻率,從而提高了文件讀寫的性能。

三、NIO2.0 實戰

Java NIO2.0 是 Java 7 引入的一組增強的 I/O API,它在 NIO 的基礎上提供了更強大、更便捷的文件和目錄操作功能。NIO2.0 引入了新的類和接口,如 Path、Files 和 WatchService 等,使得文件系統的操作更加靈活和高效。接下來,我們將深入探討 NIO2.0 中 Path 類和 Files 類的使用,并通過實戰案例展示其強大功能。

3.1 Path 類

在 Java NIO2.0 中,Path 類用于表示文件系統中的路徑,它是一個平臺無關的抽象路徑。Path 接口提供了一系列方法來操作路徑,包括獲取路徑的各個部分、解析路徑、規范化路徑等。

路徑表示

可以使用 Paths 類的 get 方法來創建 Path 對象。例如,創建一個表示文件路徑的 Path 對象:

import java.nio.file.Path;
import java.nio.file.Paths;public class PathExample {public static void main(String[] args) {// 創建Path對象Path path = Paths.get("C:/Users/Username/Documents/example.txt");// 獲取文件名System.out.println("文件名: " + path.getFileName());// 獲取父路徑System.out.println("父路徑: " + path.getParent());// 獲取根路徑System.out.println("根路徑: " + path.getRoot());}
}

路徑操作

Path 接口提供了豐富的方法來操作路徑,如拼接路徑、規范化路徑等。

  • 拼接路徑:使用 resolve 方法可以將兩個路徑拼接在一起。例如:
Path path1 = Paths.get("C:/Users/Username");
Path path2 = path1.resolve("Documents/example.txt");
System.out.println("拼接后的路徑: " + path2);
  • 規范化路徑:使用 normalize 方法可以消除路徑中的冗余部分,如 “./” 和 “…/”。例如:
Path unnormalizedPath = Paths.get("C:/Users/Username/../Documents/./example.txt");
Path normalizedPath = unnormalizedPath.normalize();
System.out.println("規范化后的路徑: " + normalizedPath);

3.2 Files 類

Files 類是 NIO2.0 中用于文件操作的核心類,它提供了大量的靜態方法來執行文件的創建、刪除、復制、移動、讀取和寫入等操作。

文件創建

使用 createFile 方法可以創建一個新文件。例如:

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;public class FilesExample {public static void main(String[] args) {Path filePath = Paths.get("C:/Users/Username/newfile.txt");try {Files.createFile(filePath);System.out.println("文件創建成功");} catch (IOException e) {e.printStackTrace();}}
}

文件刪除

使用 delete 方法可以刪除文件或空目錄。如果要刪除的文件或目錄不存在,會拋出 NoSuchFileException 異常;如果要刪除的目錄非空,會拋出 DirectoryNotEmptyException 異常。例如:

Path filePath = Paths.get("C:/Users/Username/newfile.txt");
try {Files.delete(filePath);System.out.println("文件刪除成功");
} catch (IOException e) {e.printStackTrace();
}

文件復制

使用 copy 方法可以復制文件。如果目標文件已存在,會拋出 FileAlreadyExistsException 異常。可以通過 StandardCopyOption.REPLACE_EXISTING 選項來覆蓋已存在的文件。例如:

Path sourcePath = Paths.get("C:/Users/Username/source.txt");
Path targetPath = Paths.get("C:/Users/Username/target.txt");
try {Files.copy(sourcePath, targetPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);System.out.println("文件復制成功");
} catch (IOException e) {e.printStackTrace();
}

文件讀取

使用 readAllBytes 方法可以讀取文件的所有字節內容。例如:

Path filePath = Paths.get("C:/Users/Username/data.txt");
try {byte[] content = Files.readAllBytes(filePath);String data = new String(content);System.out.println("文件內容: " + data);
} catch (IOException e) {e.printStackTrace();
}

3.3 Files 類高級操作

除了基本的文件操作,Files 類還提供了一些高級操作方法,如獲取文件屬性和監聽文件變化。

文件屬性獲取

可以使用 Files 類的 getAttribute 方法獲取文件的各種屬性,如文件大小、修改時間、創建時間等。例如,獲取文件的大小和修改時間:

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.io.IOException;public class FileAttributesExample {public static void main(String[] args) {Path filePath = Paths.get("C:/Users/Username/data.txt");try {BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);System.out.println("文件大小: " + attrs.size() + " 字節");System.out.println("修改時間: " + attrs.lastModifiedTime());System.out.println("創建時間: " + attrs.creationTime());} catch (IOException e) {e.printStackTrace();}}
}

文件監聽

NIO2.0 引入了 WatchService 來監聽文件系統的變化,如文件的創建、修改和刪除等。以下是一個簡單的示例,展示如何監聽指定目錄下的文件變化:

import java.nio.file.*;
import java.io.IOException;public class FileWatcherExample {public static void main(String[] args) {try {// 創建WatchServiceWatchService watchService = FileSystems.getDefault().newWatchService();// 注冊要監聽的目錄Path directory = Paths.get("C:/Users/Username/Documents");directory.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);System.out.println("開始監聽目錄: " + directory);while (true) {// 等待事件發生WatchKey key = watchService.take();for (WatchEvent<?> event : key.pollEvents()) {WatchEvent.Kind<?> kind = event.kind();@SuppressWarnings("unchecked")WatchEvent<Path> ev = (WatchEvent<Path>) event;Path fileName = ev.context();System.out.println("事件類型: " + kind + ", 文件: " + fileName);}// 重置WatchKeyboolean valid = key.reset();if (!valid) {break;}}} catch (IOException | InterruptedException e) {e.printStackTrace();}}
}

3.4 NIO2.0 實戰案例

為了更好地展示 NIO2.0 的強大功能,我們通過一個目錄遍歷和文件篩選的案例來進行實踐。假設我們需要遍歷指定目錄及其子目錄,篩選出所有的 Java 源文件,并打印出它們的路徑。

import java.nio.file.*;
import java.io.IOException;public class DirectoryTraversalExample {public static void main(String[] args) {Path directory = Paths.get("C:/Users/Username/Projects");try {Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {if (file.getFileName().toString().endsWith(".java")) {System.out.println("找到Java源文件: " + file);}return FileVisitResult.CONTINUE;}});} catch (IOException e) {e.printStackTrace();}}
}

在上述代碼中,我們使用 Files 類的 walkFileTree 方法來遍歷指定目錄及其子目錄。walkFileTree 方法接受兩個參數,一個是要遍歷的起始目錄,另一個是實現了 FileVisitor 接口的對象。我們通過繼承 SimpleFileVisitor 類(它是 FileVisitor 接口的一個適配器類,提供了默認的實現),并重寫 visitFile 方法來實現文件篩選邏輯。在 visitFile 方法中,我們檢查文件的擴展名是否為 “.java”,如果是,則打印出文件的路徑。通過這個案例,我們可以看到 NIO2.0 提供的文件和目錄操作功能非常強大和靈活,能夠輕松應對各種復雜的文件處理需求。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/98010.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/98010.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/98010.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

OpenCV 實戰:圖像模板匹配與旋轉處理實現教程

目錄 一、功能概述&#xff1a;代碼能做什么&#xff1f; 二、環境準備&#xff1a;先搭好運行基礎 1. 安裝 Python 2. 安裝 OpenCV 庫 3. 準備圖像文件 三、代碼逐段解析&#xff1a;從基礎到核心 1. 導入 OpenCV 庫 2. 讀取圖像文件 3. 模板圖像旋轉&#xff1a;處理…

一、cadence的安裝及入門教學(反相器的設計與仿真)

一、Cadence的安裝 1、安裝VMware虛擬機 2、安裝帶有cadence軟件的Linux系統 注&#xff1a;網盤鏈接 分享鏈接&#xff1a;https://disk.ningsuan.com.cn/#s/8XaVdtRQ 訪問密碼&#xff1a;11111 所有文件壓縮包及文檔密碼&#xff1a; Cadence_ic 3、安裝tsmc18工藝庫…

用ai寫了個UE5插件

文章目錄實際需求1.頭文件2.源文件3.用法小結實際需求 這個需求來源于之前的一個項目&#xff0c;當時用了一個第三方插件&#xff0c;里邊有一些繪制線段的代碼&#xff0c;c層用的是drawdebugline&#xff0c;當時看底層&#xff0c;覺得應該沒問題&#xff0c;不應該在rele…

機器學習從入門到精通 - 強化學習初探:Q-Learning到Deep Q-Network實戰

機器學習從入門到精通 - 強化學習初探&#xff1a;從 Q-Learning 到 Deep Q-Network 實戰 一、開場白&#xff1a;推開強化學習這扇門 不知道你有沒有過這種感覺 —— 盯著一個復雜的系統&#xff0c;既想讓它達到某個目標&#xff0c;又苦于無法用傳統規則去精確描述每一步該怎…

【OpenHarmony文件管理子系統】文件訪問接口解析

OpenHarmony文件訪問接口&#xff08;filemanagement_file_api&#xff09; 概述 OpenHarmony文件訪問接口&#xff08;filemanagement_file_api&#xff09;是開源鴻蒙操作系統中的核心文件系統接口&#xff0c;為應用程序提供了完整的文件IO操作能力。該項目基于Node-API&…

云手機運行是否消耗自身流量?

云手機運行是否消耗自身流量&#xff0c;取決于具體的使用場景和設置&#xff1a;若用戶在連接云手機時&#xff0c;使用的是家中Wi-Fi、辦公室局域網等非移動數據網絡&#xff0c;那么在云手機運行過程中&#xff0c;基本不會消耗用戶自身的移動數據流量&#xff0c;在家中連接…

JavaSe之多線程

一、多線程基本了解 1、多線程基本知識 1.進程:進入到內存中執行的應用程序 2.線程:內存和CPU之間開通的通道->進程中的一個執行單元 3.線程作用:負責當前進程中程序的運行.一個進程中至少有一個線程,一個進程還可以有多個線程,這樣的應用程序就稱之為多線程程序 4.簡單理解…

產品月報|睿本云8月產品功能迭代

睿本云8月更新已陸續上線&#xff01; 睿本云8月產品月報&#xff0c;點擊查收&#x1f447;小程序支付成功彈窗廣告、企業會員增加卡券銷售和卡券退貨模塊、工廠端可批量新增多門店訂貨單、門店端和工廠端新增“極速訂貨”、商品調撥業務支持自定義多種流程配置等功能迭代更新…

融云:當我們談論 AI 重構業務時,我們到底在談論什么

所有業務都值得用 AI 重新做一次。 這句話正在從一句鼓舞人心的口號&#xff0c;演變為一場無人可避的商業現實。AI 帶來的結構性機會&#xff0c;意味著企業有機會從根本上重構成本、效率與體驗的曲線。但這一切最終都要回到一個無比務實的問題上&#xff1a; AI 究竟如何在我…

org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 1異常

org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length 1異常問題解決一、問題背景二、錯誤現象三、原因分析核心問題&#xff1a;字符集不匹配四、解決過程試錯路徑記錄五、最終方案1.創建launch.json文件&#xff0c;修改VSCode…

【C語言】深入理解指針(5)

目錄 sizeof和strlen 1.sizeof 2.strlen 3. sizeof 和 strlen 的對比 sizeof和strlen 1.sizeof sizeo正名&#xff1a;sizeof是操作符&#xff0c;不是函數&#xff0c;sizeof是操作符&#xff0c;括號內如果有計算不會進行計算sizeof 是操作符&#xff0c;用于計算變量所…

動態代理設計模式

JDK動態代理實現 動態代理利用了JDK API,動態地在內存中構建代理對象,從而實現對目標對象的代理功能.動態代理又被稱為JDK代理或接口代理. 靜態代理與動態代理的區別: 靜態代理在編譯時就已經實現了,編譯完成后代理類是一個實際的class文 動態代理是在運行時動態生成的,即編譯…

《Html泛型魔法學院:用霍格沃茨風格網頁教授集合框架》

一、項目概述 這個創意教學網頁&#xff0c;將Java泛型與集合框架知識融入霍格沃茨魔法世界主題。通過沉浸式UI設計和交互式代碼練習&#xff0c;讓抽象的技術概念變得生動有趣。主要技術棧包括&#xff1a; HTML5語義化結構Tailwind CSS框架Font Awesome圖標庫純JavaScript交…

學習PaddlePaddle--環境配置-PyCharm + Conda?

第一階段&#xff1a;安裝與配置 Python 和 Conda?? 雖然 PyCharm 可以管理環境&#xff0c;但我們先獨立準備好 Conda 環境&#xff0c;這樣更清晰可靠。 ??1. 安裝 Miniconda (Python 環境管理)?? 1. ??下載??&#xff1a; ? 訪問 Miniconda 官網。 ? 選擇 ??M…

【數據庫】Sql Server數據庫中isnull、iif、case when三種方式的使用和空值判斷

大家好&#xff0c;我是全棧小5&#xff0c;歡迎來到《小5講堂》。 這是《Sql Server》系列文章&#xff0c;每篇文章將以博主理解的角度展開講解。 溫馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不對之處望指正&#xff01; 目錄前言ISNULL用法c…

【藍橋杯選拔賽真題64】C++最大空白區 第十四屆藍橋杯青少年創意編程大賽 算法思維 C++編程選拔賽真題解

C++最大空白區 第十四屆藍橋杯青少年創意編程大賽C++選拔賽真題 博主推薦 所有考級比賽學習相關資料合集【推薦收藏】 1、C++專欄 電子學會C++一級歷年真題解析 電子學會C++二級歷年真題解析

試用Augment編寫python腳本實現智能家居3D環境交互響應

環境配置 VS Code中直接安裝Augment擴展&#xff0c;然后郵箱登錄就能獲得7天的試用。 從如下位置安裝3D建模軟件Blender&#xff1a; https://www.blendercn.org/downloadme#xiazai Blender 是一款免費開源的 3D 創作套件。它支持整個三維流程&#xff1a;建模、綁定、動畫…

【架構師干貨】系統架構設計

1. 軟件架構概述 從需求分析到軟件設計之間的過渡過程稱為軟件架構。只要軟件架構設計好了&#xff0c;整個軟件就不會出現坍塌性的錯誤&#xff0c;即不會崩潰。 架構設計就是需求分配&#xff0c;將滿足需求的職責分配到組件上。 軟件架構為軟件系統提供了一個結構、行為和屬…

Java設計模式之結構型—享元模式

Java中最常用的設計模式-CSDN博客 把“不可變且可復用”的細粒度對象緩存起來&#xff0c;用“共享”代替“新建”&#xff0c;從而節省內存。 經典場景 字符串常量池、Integer.valueOf(-128~127)、Android Message.obtain() 游戲粒子、編輯器字形、地圖瓦片、線程池中的任務…

cursor+python輕松實現電腦監控

小伙伴們&#xff0c;今天我們利用cursor不寫一行代碼開發一個電腦的系統狀態監控小應用&#xff01;下載安裝cursor&#xff1a;網址&#xff1a;https://www.cursor.com/cn下載后雙擊安裝輸入提示詞&#xff1a; 制作一個winswos應用&#xff0c;實現顯示時間精確到秒&…