---
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 支持的字元編碼都會繼承該類
> 
### 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` 編碼,所以會出錯
:::
> 
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);
}
```
> 
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);
}
```
> 
* 使用 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);
}
```
> 
### 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);
}
}
```
> 
### 控制 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!**
> 
### 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);
}
}
}
```
> 
## 其他
### 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 空間
> 
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());
}
}
```
> 
### 文件應設緩衝區 - MappedByteBuffer
* `MappedByteBuffer` 用來建立、修改那些因「太大不能放入記憶體的檔案」!
**`MappedByteBuffer` 是透過檔案映射記憶體的方式來達到這個操作**,也就是說該檔案會與某塊物理記憶體產生關聯
* 而 `MappedByteBuffer` 也不能直接創建,必須透過 FileChannel#`map` 方法取得實例
> 
其中的 `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);
}
}
}
```
> 
檔案大小也確實是 128M
> 
### 文件同步 - 分區加鎖 🔒
* 在多線程中如果只是單單讀取文件,那是安全操作,但如果 **有涉及到文件的「寫」,那就是不安全操作**
* 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);
}
}
}
```
> 
## 更多的 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 基礎`