--- title: 'Java NIO 流' disqus: kyleAlien --- Java NIO 流 === ## Overview of Content 如有引用參考請詳註出處,感謝 :smile_cat: [TOC] ## NIO - 概述 從 JDK 1.4 開始,Java 有提供一個位於 `java.nio` Package 的 IO 類別庫,其目的是為了提高 IO 操作效率,其關鍵類型有四個 | NIO 相關類 | 功能概述 | | - | - | | `Buffer` | 可控制的緩衝區,存放臨時資料 | | `Charset` | 具有將 JVM 預設的 Unicode 編碼轉換為其他編碼的類(或反轉為 Unicode) | | `Channel` | 資料傳輸通道,能將多個不同 Buffer 的資料彙聚(讀取、寫入) | | `Selector` | 支援 **非同步 IO**,也就是「Not Blocking IO」,在撰寫伺服器程式時常被使用到 | :::success * `Selector` 使用的範例請看另一篇「[**Socket 通訊 - TCP、UDP 應用 / NIO Socket**](https://hackmd.io/zbVv5CAsTWK84JprGAmsjQ?view#Socket-%E9%80%9A%E8%A8%8A---TCP%E3%80%81UDP-%E6%87%89%E7%94%A8--NIO-Socket)」 ::: ## Buffer 概述 以往資料的輸出、輸入往往比較耗時,如果有配合使用緩衝區 Buffer,就可提高 I/O 的操作效率,其功能有: 1. 減少實際的物理讀寫次數 2. 緩衝區的區塊記憶體被重復使用,減少動態分配、回收的次數 :::success * 它與傳統的 `BufferedInputStream`、`BufferedOutputStream` ... 等等(Buffered 開頭的類)相比起來差在哪? 以往的緩衝類無法讓使用者直接操縱 Buffer(傳統類別綁定類、Buffer 較不利於重用),而新的 Buffer 可以讓使用者直接操縱 ::: ### Buffer 類關係 - 屬性、方法 * Buffer 類關係圖如下 ``` mermaid graph RL; Buffer; ByteBuffer-->Buffer; CharBuffer-->Buffer; DoubleBuffer-->Buffer; FloatBuffer-->Buffer; IntBuffer-->Buffer; LongBuffer-->Buffer; ShortBuffer-->Buffer; MappedByteBuffer-->ByteBuffer ``` * Buffer 類的共通屬性、方法如下 * **共通屬性** | 屬性 | 功能概述 | | - | - | | capacity | 標明該 **緩衝區的大小** | | limit | 該緩衝區的 **終點**(可以不與 `capacity` 相同),該值是可以修改變動的 | | position | 該 **緩衝區的指標**,指向下一個讀寫單元的位置 | > 三者關係:`capacity` >= `limit` >= `position` >= 0 ``` mermaid graph RL; subgraph Buffer_緩衝區 capacity limit position end 當前容量 -->|100| capacity 當前極限 -->|80| limit 當前位置 -->|20| position ``` * **共通方法** | 方法名 | 影響 position | 影響 limit | 影響 capacity | | - | - | - | - | | `clear` | position 修改為 0 | limit 改為 capacity | - | | `flip` | position 修改為 0 | limit 改為 position | - | | `rewind` | position 修改為 0 | - | - | 其中還有幾個是緩衝類都具有的方法 | 方法名 | 功能概述 | | - | - | | `get` | 可以用來相對讀取、絕對讀取 | | `put` | 可以用來相對寫入、絕對寫入 | * 而 **Buffer 並未公開建構函數**,必須 **透過指定靜態函數取得 Buffer 物件** | 靜態方法 | 特色 | | - | - | | `allocate` | 產生指定容量的 Buffer | | `directAllocate` | 同上,不過它的緩衝區屬於「**直接緩衝區**」,與作業系統有更好的耦合,可進一步提供 IO 速度 | :::warning * 但相對的 `directAllocate` 的代價是較高的,通常只有在 **緩衝區大、並長期存在、不斷重用** 的狀況下會去使用 代價如下 1. 消耗 Native 內存 2. 不受 JVM 管控 3. 銷毀、創建需要與 Native 作業系統通訊 ::: ## Channel 概述 通道是用來連接緩衝區(`Buffer`)與資料來源、資料輸出終點的通道 ``` mermaid graph LR; 資料來源 --> Channel_Input; Channel_Input --> Buffer; Buffer --> Channel_Output; Channel_Output --> 資料輸出終點 ``` ### Channel 類關係 - 方法 * `Channel` 界面指宣告兩個方法 | Channel 方法 | 說明 | | - | - | | close | 關閉通道 | | isOpen | 判斷通道是否打開 | :::warning * JDK 在實作時,通道會在建立時開啟,**一但關閉通道,就不會開啟** ::: * `Channel` 類關係如下 | Channel 拓展介面 | 功能概述 | | - | - | | ReadableByteChannel | 唯讀的方法;`read(ByteBuffer)` | | WritableByteChannel | 唯寫的方法;`write(ByteBuffer)` | | ByteChannel | 支持讀、寫的類並擴充以上介面 | | ScatteringByteChannel | 可以有多個 ByteBuffer,將資料源頭讀取到的資料依序填充到指定的 ByteBuffer;`read(ByteBuffer[])` | | GatheringByteChannel | 可以有多個 ByteBuffer,將多個 ByteBuffer 輸出到最終檔案中;`write(ByteBuffer[])` | | FileChannel | 與「檔案」產生相關聯的類,**代表一個與檔案相連的通道** | ``` mermaid graph RL; Channel; ReadableByteChannel-->Channel; WritableByteChannel-->Channel; ScatteringByteChannel-->ReadableByteChannel ByteChannel-->ReadableByteChannel; ByteChannel-->WritableByteChannel; GatheringByteChannel-->WritableByteChannel FileChannel-->ByteChannel; FileChannel-->ScatteringByteChannel; FileChannel-->GatheringByteChannel; ``` :::success * **`FileChannel` 類也不公開建構函數,所以需要透過以往 IO 類獲得** `FileInputStream`、`FileOutputStream`、`RandomAcessFile` 類別中的 `getChannel` 方法來取得實體物件 ::: ## Charset 概述 `Charset` 類別的每個實體代表「特定的字元編碼類型」,它是個抽象類,JVM 支持的字元編碼都會繼承該類 > ![](https://hackmd.io/_uploads/HJl3U00y6.png) ### Charset 轉換編碼 * 使用 `Charset` 轉換編碼的方式如下,它會透過 Charset 類返回一個轉換過得 ByteBuffer 1. 轉換 JVM 的 Unicode 為特定字元編碼 ```java= // 使用 UTF-8 編碼 ByteBuffer utf8Encode = StandardCharsets.UTF_8.encode("123"); // 使用 ASCII 編碼 CharBuffer asciiDecoded = StandardCharsets.US_ASCII.decode("123"); ``` > StandardCharsets 提供簡易的標準編碼,它們都是 Charset 的實作類 2. 將特定編碼轉為 JVM 的 Unicode 編碼 ```java= // 使用 UTF-8 編碼 ByteBuffer utf8Encode = StandardCharsets.UTF_8.encode(originalText); // 使用 UTF-8 解碼 CharBuffer utf8Decode = StandardCharsets.UTF_8.decode(utf8Encode); // 使用 ASCII 編碼 ByteBuffer asciiEncode = StandardCharsets.US_ASCII.encode(originalText); // 使用 ASCII 解碼 CharBuffer asciiDecode = StandardCharsets.UTF_8.decode(asciiEncode); ``` ## NIO 使用 ### 字元編碼轉換 Charset * 首先先來看 Buffer、Charset 編碼的關係;ByteBuffer 可以存放 Byte 資料,但它並不關心放入的數據是怎樣被編碼,所以取出數據時要注意編碼轉換 範例如下 1. 將資料以 UTF-8 編碼放入 ByteBuffer,列印會變成亂碼 ```java= static void bufferPutUtf8() { // 將資料以 UTF-8 編碼放入 ByteBuffer ByteBuffer byteBuffer = ByteBuffer.wrap("安安" .getBytes(StandardCharsets.UTF_8)); // 轉換 CharBuffer,CharBuffer 會轉換錯誤 CharBuffer charBuffer = byteBuffer.asCharBuffer(); System.out.println(charBuffer); } ``` :::warning * **正確方式** 如果 ByteBuffer 中存放的是 Unicode 字元,在透過 `asCharBuffer` 方法轉換時,就可以轉換為正確字元; > 錯誤原因: 當前範例存放的卻是 `UTF-8` 編碼,所以會出錯 ::: > ![](https://hackmd.io/_uploads/HkMIS6klp.png) 2. 使用 Unicode 編碼 由於 Java String 採用 Unicode 編碼,所以可以正確打印並顯示 ```java= static void bufferPutUnicode() { ByteBuffer byteBuffer = ByteBuffer.wrap("安安" .getBytes(StandardCharsets.UTF_16BE)); // UTF_16BE 就是 Unicode CharBuffer charBuffer = byteBuffer.asCharBuffer(); System.out.println(charBuffer); } ``` > ![](https://hackmd.io/_uploads/rJk8up1lp.png) 3. Charset 使用 decode 使用 Charset 指定方式解碼後,取得的 CharBuffer 就會是正確的 ```java= static void bufferPutUtf8UseDecode() { ByteBuffer byteBuffer = ByteBuffer.wrap("安安" .getBytes(StandardCharsets.UTF_8)); Charset charset = Charset.forName("UTF-8"); CharBuffer charBuffer = charset.decode(byteBuffer); System.out.println(charBuffer); } ``` > ![](https://hackmd.io/_uploads/rJIHFpyxp.png) * 使用 Charset 手動設定編碼(`decode`、`encode`)範例: ```java= static void useCharset() { // 取得 Big5 編碼方起是 Charset charset = Charset.forName("Big5"); // 使用 Big5 編碼 ByteBuffer byteBuffer = charset.encode("安安你好呀!"); // 轉換 Big5 編碼為 CharBuffer charBuffer = charset.decode(byteBuffer); System.out.println(charBuffer); } ``` > ![](https://hackmd.io/_uploads/SkA756kla.png) ### FileChannel 讀寫文件 * 接下來我們會使用 FileChannel 來操控文件,跟以往的 IO 比較起來會多出 Channel、Buffer、Charset 這些角色 * **寫入資料** 1. **`FileChannel` 類**: 使用 `FileOutputStream`、`RandomAccessFile` 取得 `FileChannel` 類 ``` mermaid graph LR; User-->|create|FileOutputStream User-->|create|RandomAccessFile FileOutputStream-->|getChannel|FileChannel; RandomAccessFile-->|getChannel|FileChannel; FileChannel-->|輸出|目標檔案 ``` 2. **`ByteBuffer` 類**: 由於對 `Channel` 寫入資料需要使用 `ByteBuffer` 類,所以使用 `ByteBuffer#wrap` 取得 `ByteBuffer` ``` mermaid graph LR; User-->|wrap|ByteBuffer ByteBuffer-->|write|FileChannel FileChannel-->|輸出|目標檔案 ``` ```java= static void writeFile(String fileName) { try(FileOutputStream fos = new FileOutputStream(fileName); RandomAccessFile raf = new RandomAccessFile(fileName, "rw")) { FileChannel fc = fos.getChannel(); fc.write(ByteBuffer.wrap("安安".getBytes())); fc.close(); fc = raf.getChannel(); fc.position(fc.size()); fc.write(ByteBuffer.wrap(", HelloWorld ~ ".getBytes())); fc.close(); } catch (IOException e) { throw new RuntimeException(e); } } ```` * **讀取資料** 1. **`FileChannel` 類**: 使用 `FileInputStream` 取得 `FileChannel` 類 ``` mermaid graph LR; User-->|create|FileInputStream FileInputStream-->|getChannel|FileChannel; FileChannel-->|取得|目標檔案 ``` 2. **`ByteBuffer` 類**: 由於對 `Channel` 讀取資料需要使用 `ByteBuffer` 類,所以使用 `ByteBuffer#allocate` 創建一塊 `ByteBuffer` 區域 ``` mermaid graph LR; User-->|allocate|ByteBuffer ByteBuffer-->|給予|FileChannel FileChannel-->|1. 讀取|目標檔案 FileChannel-.->|2. 放入|ByteBuffer ``` 3. **`Charset` 類**: 由於從檔案中讀取的資料為 Byte,並非系統可認得的數據,所以使用 `Charset`#`defaultCharset` 取得當前平台的編碼;並使用 `decode` 解碼 Byte 數據 ```java= static void readFile(String fileName) { try(FileInputStream fis = new FileInputStream(fileName)) { FileChannel fc = fis.getChannel(); // 創建 ByteBuffer 區塊 (1024 Byte 大小) ByteBuffer byteBuffer = ByteBuffer.allocate(1024); fc.read(byteBuffer); byteBuffer.flip(); // limit 改為 position Charset cs = Charset.defaultCharset(); System.out.println(cs.decode(byteBuffer)); } catch (IOException e) { throw new RuntimeException(e); } } ``` ``` mermaid graph LR; Charset-->|取得本地編碼|defaultCharset defaultCharset-->本地編碼 本地編碼-->decode decode-.->ByteBuffer ``` * 使用範例、結果: ```java= class FileChannelUsage { public static void main(String[] args) { String targetFile = "/var/folders/qt/468b6d6j6xxffzc4tydgnj800000gq/T/Hello5.txt"; writeFile(targetFile); readFile(targetFile); } } ``` > ![](https://hackmd.io/_uploads/BJYAGhye6.png) ### 控制 Buffer 緩衝區 * 我們前面有說到 Buffer 與傳統 IO 的差異在於,可以讓使用者手動操控 Buffer;以下來個範例,並觀察如何使用 Buffer、Buffer 的狀態 ```java= class BufferUsage { static void showBufferInfo(ByteBuffer byteBuffer) { // 讀取 position, limit, capacity 資訊 System.out.println("position: " + byteBuffer.position() + ", limit: " + byteBuffer.limit() + ", capacity: " + byteBuffer.capacity()); } public static void main(String[] args) { String existFile = "/var/folders/qt/468b6d6j6xxffzc4tydgnj800000gq/T/Hello5.txt"; String targetFile = "/var/folders/qt/468b6d6j6xxffzc4tydgnj800000gq/T/Hello6.txt"; try(FileInputStream fis = new FileInputStream(existFile); FileOutputStream fos = new FileOutputStream(targetFile)) { FileChannel inputChannel = fis.getChannel(); FileChannel outputChannel = fos.getChannel(); int limitVal = 5; // 設定 Buffer 大小為 10 Byte ByteBuffer byteBuffer = ByteBuffer.allocate(10); while (inputChannel.read(byteBuffer) != -1) { // 設定上限值 byteBuffer.limit(limitVal++); System.out.print("Start - "); showBufferInfo(byteBuffer); byteBuffer.flip(); // position 改為 0, limit 改為 position System.out.print("After flip - "); showBufferInfo(byteBuffer); int written = outputChannel.write(byteBuffer); System.out.println("The byte be written=(" + written + ")"); byteBuffer.clear(); // position 改為 0, limit 改為 capacity System.out.print("After clear - "); showBufferInfo(byteBuffer); System.out.println("--------------------------"); } } catch (IOException e) { throw new RuntimeException(e); } } } ``` 從結果,可以發現幾件事 1. **Buffer 的 limit 值預設與 capacity 相同** 2. **每次 read 都會自動填滿 position 到 limit 之間的數據空間**,並且 **不會超出 limit!** 3. **每次 write 也只會將 position 到 limit 之間的數據寫入**,並且 **不會超出 limit!** > ![](https://hackmd.io/_uploads/ryWAT2ye6.png) ### FileInputStream、FileOutputStream 快速複製 * `Channel` 的子類 `FileChannel` 有提供兩個方便的方法,可以透過 Channel 快速複製檔案 1. `transferTo` 方法: 將 `WritableByteChannel` 資料寫入到預計要輸出的 Channel 中 2. `transferFrom` 方法: 將 `ReadableByteChannel` 資料寫入到預計要輸出的 Channel 中 ```java= class FastCopy { public static void main(String[] args) { String existFile = "/var/folders/qt/468b6d6j6xxffzc4tydgnj800000gq/T/Hello5.txt"; String targetFile = "/var/folders/qt/468b6d6j6xxffzc4tydgnj800000gq/T/Hello6.txt"; try(FileInputStream fis = new FileInputStream(existFile); FileOutputStream fos = new FileOutputStream(targetFile)) { FileChannel inputChannel = fis.getChannel(); FileChannel outputChannel = fos.getChannel(); // 將 inputChannel 資料傳遞到 outputChannel long copySize = inputChannel.transferTo(0, inputChannel.size(), outputChannel); System.out.println("Copy to outputChannel=(" + copySize + ")"); // 將 inputChannel 資料傳遞到 outputChannel copySize = outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); System.out.println("Copy from inputChannel=(" + copySize + ")"); } catch (IOException e) { throw new RuntimeException(e); } } } ``` > ![](https://hackmd.io/_uploads/Sky9JTkgp.png) ## 其他 ### ByteBuffer 產生 - 緩衝區視圖 * ByteBuffer 提供 `asCharBuffer`、`asIntBuffer`、`asFloatBuffer`... 等等方法,這些方法可以用來生成緩衝區視圖 :::success * 緩衝區視圖是什麼? 緩衝區視圖 **允許程式設計師以抽象的方式查看記憶體中的數據,而不需要直接操作內存**,從而提高了程式碼的 **可讀性** 和 **可維護性** > 這種方法通常用於低級別的編程,如網絡編程、文件I/O和編碼/解碼操作,但在高級別的應用程式中也可以有用 ::: 範例如下 1. ByteBuffer 轉換後的視圖緩衝區,也會重新分配每格數據的大小 ```java= class BufferView { static void showBufferInfo(Buffer buffer) { System.out.println("position: " + buffer.position() + ", limit: " + buffer.limit() + ", capacity: " + buffer.capacity()); } public static void main(String[] args) { // 空間為 8 Byte ByteBuffer byteBuffer = ByteBuffer.allocate(4); // hasRemaining: 檢查當前 position ~ limit 之間是否還有數據 while (byteBuffer.hasRemaining()) { System.out.println(byteBuffer.get()); } System.out.println("--- Byte After get ---"); showBufferInfo(byteBuffer); // 將 Position 設置為 0 byteBuffer.rewind(); System.out.println("--- AByte fter rewind ---"); showBufferInfo(byteBuffer); // 轉換為 char 緩衝視圖 CharBuffer charBuffer = byteBuffer.asCharBuffer(); System.out.println("--- Byte After asCharBuffer ---"); showBufferInfo(charBuffer); // 放入數據 charBuffer.put("你好"); while (byteBuffer.hasRemaining()) { System.out.println(byteBuffer.get()); } System.out.println("--- Char After get ---"); showBufferInfo(charBuffer); } } ``` 從結果可以發現,**對於 4 byte 空間數據,不同緩衝區會有不同解釋** * ByteBuffer 解釋為 4 capacity,代表一個數據擁有 1 Byte 空間 * CharBuffer 則解釋為 2 capacity,代表一個數據擁有 2 Byte 空間 > ![](https://hackmd.io/_uploads/B1jeFZWlT.png) 2. ByteBuffer 可以取出指定數據的格式 ```java= class BufferView2 { public static void main(String[] args) { ByteBuffer byteBuffer = ByteBuffer.allocate(10); byteBuffer.asCharBuffer().put("安"); System.out.println(byteBuffer.getChar()); byteBuffer.rewind(); byteBuffer.asIntBuffer().put(666); System.out.println(byteBuffer.getInt()); byteBuffer.rewind(); byteBuffer.asFloatBuffer().put(222F); System.out.println(byteBuffer.getFloat()); } } ``` > ![](https://hackmd.io/_uploads/BJca9WZlp.png) ### 文件應設緩衝區 - MappedByteBuffer * `MappedByteBuffer` 用來建立、修改那些因「太大不能放入記憶體的檔案」! **`MappedByteBuffer` 是透過檔案映射記憶體的方式來達到這個操作**,也就是說該檔案會與某塊物理記憶體產生關聯 * 而 `MappedByteBuffer` 也不能直接創建,必須透過 FileChannel#`map` 方法取得實例 > ![](https://hackmd.io/_uploads/r1K0jW-lT.png) 其中的 `MapMode` 參數會對應到該緩衝區的相關權限 | MapMode | 概述 | | - | - | | READ_ONLY | 只能讀取該映射記憶體區塊 | | READ_WRITE | 可對該映射記憶體區塊進行讀寫 | | PRIVATE | 這次修改不會被保存到檔案中、並且其他程式不可見(也就是操作不會影響原文件) | :::warning * `PRIVATE` 時,會使用 Copy-on-Write 技術,對於文件的存取速度會提高許多 ::: 使用範例如下 ```java= class MappedByteBufferUsage { public static void main(String[] args) { String targetFile = "/var/folders/qt/468b6d6j6xxffzc4tydgnj800000gq/T/Hello7.txt"; try(RandomAccessFile raf = new RandomAccessFile(targetFile, "rw")) { int capacity = 0x7A12000; // 128MB MappedByteBuffer mbb = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, capacity); mbb.put("安安".getBytes(StandardCharsets.UTF_8)); // limit fix to current position mbb.flip(); System.out.println(StandardCharsets.UTF_8.decode(mbb)); } catch (IOException e) { throw new RuntimeException(e); } } } ``` > ![](https://hackmd.io/_uploads/HJPDJz-la.png) 檔案大小也確實是 128M > ![](https://hackmd.io/_uploads/B1okZfbxa.png) ### 文件同步 - 分區加鎖 🔒 * 在多線程中如果只是單單讀取文件,那是安全操作,但如果 **有涉及到文件的「寫」,那就是不安全操作** * NIO 庫則有引入檔案加鎖 🔒 機制,允許程式 **同步存取共享檔案** :::danger * Java 使用的檔案鎖,對於作業系統的其他執行序(`Thread`)是可見的操作 因為這種檔案鎖直接映射到本地作業系統的加鎖 🔒 工具,這個鎖由作業系提供,並非 Java 自身提供 ::: 其中,關於鎖有提供幾個方法,如下表 | 方法 | 說明 | | - | - | | tryLock | 嘗試獲取鎖,如果無法獲取,則直接返回 | | lock | 同樣是獲取鎖,但是如果無法獲取它會一直等待(該線程會被掛起 Blocking) | 並且這兩個方法都有以下參數,這些參數較為特別 | 參數 | 說明 | | - | - | | position、size | 可以對同一個文件的不同區塊進行鎖定,不會全部鎖住;範圍是 position ~ position+size | | shared: Boolean | 決定是否使用共享鎖 | :::success * **共享鎖、排他鎖**? > 一個檔案中有兩把不同的鎖 ``` mermaid graph TD; subgraph 檔案 共享鎖 排他鎖 end ``` * 共享鎖: 當一個執行序獲得該檔案的共享鎖時,其他執行序仍可獲得該檔案的共享鎖,但不能獲得該檔案的排他鎖 * 排他鎖: 當一個執行序已經獲得檔案的排他鎖時,其他執行不允許獲得 共享鎖、排他鎖 ::: * 分區加鎖 🔒 範例如下 > 詳細說明請看註解 ```java= class SynAccessFile { static class Modifier extends Thread { private final ByteBuffer byteBuffer; private final FileChannel fileChannel; private final int lockStart, lockEnd; Modifier(FileChannel fileChannel, ByteBuffer byteBuffer, int start, int end) { this.fileChannel = fileChannel; this.lockStart = start; this.lockEnd = end; // 設定 Buffer byteBuffer.limit(end); byteBuffer.position(start); // 獲得處理區域緩衝區,該緩衝區對應檔案的映射區 this.byteBuffer = byteBuffer.slice(); // Start thread start(); } @Override public void run() { System.out.println(Thread.currentThread().getName() + ", Lock range=(" + lockStart + "~" + lockEnd + ")"); try { // 這裡記得鎖定區塊 FileLock fileLock = fileChannel.lock(lockStart, lockEnd, false); while (byteBuffer.position() < byteBuffer.limit() - 1) { // ByteBuffer#put, ByteBuffer#get // 都會移動 position byteBuffer.put((byte) (byteBuffer.get() + 1)); } fileLock.release(); } catch (IOException e) { throw new RuntimeException(e); } } } public static void main(String[] args) { String targetFile = "/var/folders/qt/468b6d6j6xxffzc4tydgnj800000gq/T/Hello8.txt"; // 這裡不能使用 try-with-resouces, // 否則會造成檔案還沒寫入就被 close 掉 // try(RandomAccessFile raf = new RandomAccessFile(targetFile, "rw")) { try { RandomAccessFile raf = new RandomAccessFile(targetFile, "rw"); FileChannel fileChannel = raf.getChannel(); int capacity = 0x2710; // 10KB MappedByteBuffer mbb = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, capacity); // 一半寫 A for (int i = 0; i < capacity / 2; i++) { mbb.put((byte) 'A'); } // 一半寫 B for (int i = capacity / 2; i < capacity; i++) { mbb.put((byte) 'B'); } // Create Thread, 訪問上半 new Modifier(fileChannel, mbb, 0, capacity / 2 - 1); // Create Thread, 訪問下半 new Modifier(fileChannel, mbb, capacity / 2, capacity); } catch (IOException e) { throw new RuntimeException(e); } } } ``` > ![](https://hackmd.io/_uploads/B1sHR0Xx6.png) ## 更多的 Java 語言相關文章 ### Java 語言深入 * 在這個系列中,我們深入探討了 Java 語言的各個方面,從基礎類型到異常處理,從運算子到物件創建與引用細節。點擊連結了解更多! :::info * [**深入探索 Java 基礎類型、編碼、浮點數、參考類型和變數作用域 | 探討細節**](https://devtechascendancy.com/basic-types_encoding_reference_variables-scopes/) * [**深入了解 Java 應用與編譯:從原始檔到命令產出 JavaDoc 文件 | JDK 結構**](https://devtechascendancy.com/java-compilation_jdk_javadoc_jar_guide/) * [**深入理解 Java 異常處理:從基礎概念到最佳實踐指南**](https://devtechascendancy.com/java-jvm-exception-handling-guide/) * [**深入理解 Java 運算子與修飾符 | 重要概念、細節 | equals 比較**](https://devtechascendancy.com/java-operators-modifiers-key-concepts_equals/) * [**深入探索 Java 物件創建與引用細節:Clone 和finalize 特性,以及強、軟、弱、虛引用**](https://devtechascendancy.com/java-object-creation_jvm-reference-details/) ::: ### Java IO 相關文章 * 探索 Java IO 的奧秘,了解檔案操作、流處理、NIO等精彩內容! :::warning * [**Java File 操作指南:基礎屬性判斷、資料夾和檔案的創建、以及簡單示範**](https://devtechascendancy.com/java-file-operations-guide/) * [**深入探索 Java 編碼知識**](https://devtechascendancy.com/basic-types_encoding_reference_variables-scopes/) * [**深入理解 Java IO 操作:徹底了解流、讀寫、序列化與技巧**](https://devtechascendancy.com/deep-in-java-io-operations_stream-io/) * [**深入理解 Java NIO:緩衝、通道與編碼 | Buffer、Channel、Charset**](https://devtechascendancy.com/deep-dive-into-java-nio_buf-channel-charset/) ::: ### 深入 Java 物件導向 * 探索 Java 物件導向的奧妙,掌握介面、抽象類、繼承等重要概念! :::danger * [**深入比較介面與抽象類:從多個角度剖析**](https://devtechascendancy.com/comparing-interfaces-abstract-classes/) * [**深度探究物件導向:繼承的利與弊 | Java、Kotlin 為例 | 最佳實踐 | 內部類細節**](https://devtechascendancy.com/deep-dive-into-oop-inheritance/) * [**「類」的生命週期、ClassLoader 加載 | JVM 與 Class | Java 為例**](https://devtechascendancy.com/class-lifecycle_classloader-exploration_jvm/) ::: ## Appendix & FAQ :::info ::: ###### tags: `Java 基礎`