概述
在 Netty學習(2)中,我們先淺淺認識了 NIO 的3大核心組件,現在就讓我們針對其深入學習,通過一些簡單的文件操作來深入理解其中的 Buffer 和 Channel 的概念。
文件寫入
將內存中的數據寫入到文件中,如果文件不存在,那么就新建文件。
// 數據 -> 文件
private static void dataToFile(String data, String filePath) {
// 構建輸出流 -> 從輸出流中獲取 channel
try (FileOutputStream fileOutputStream = new FileOutputStream(filePath);
FileChannel fileChannel = fileOutputStream.getChannel()) {
// 設置緩沖區
ByteBuffer byteBuffer = ByteBuffer.allocate(BYTE_BUFFER_LENGTH);
// 將需要讀寫的數據放到緩沖區
int i = 0;
int length = data.getBytes().length;
// 一次就可以讀完
if (BYTE_BUFFER_LENGTH > data.getBytes().length) {
byteBuffer.put(data.getBytes(), i, data.getBytes().length);
byteBuffer.flip();
fileChannel.write(byteBuffer);
} else {
// 一次讀不完 需要循環讀取
for (int temp = 0; temp < data.getBytes().length; temp += BYTE_BUFFER_LENGTH) {
byteBuffer.clear();
byteBuffer.put(data.getBytes(), temp, BYTE_BUFFER_LENGTH);
// 翻轉緩沖區,可以對外讀
// 這里的 flip() 是重點,其可以將Buffer的屬性重置,可以對外寫
byteBuffer.flip();
// 將緩沖區內的數據寫到 channel中
fileChannel.write(byteBuffer);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
這樣,我們就寫完了一個文件寫入的函數,在需要時傳入指定的字符串即可。
文件讀取
從文件中讀取數據,並將其輸出到控制台中。
// 文件 -> 內存
private static void dataFromFile(String filePath) {
File file = new File(filePath);
// 從輸入流中獲取 channel
try (FileInputStream fileInputStream = new FileInputStream(file);
FileChannel channel = fileInputStream.getChannel()) {
// 分配緩沖區
ByteBuffer byteBuffer = ByteBuffer.allocate(BYTE_BUFFER_LENGTH);
StringBuilder result = new StringBuilder();
while (true) {
byteBuffer.clear();
// 將 channel數據寫到buffer中
int read = channel.read(byteBuffer);
// 因為byteBuffer大小原因,因此需要用一個中間字符串接受一下
result.append(new String(byteBuffer.array()));
if (read == -1) {
break;
}
}
logger.info("從文本讀取結果:{}", result);
} catch (Exception e) {
logger.error("文件讀取錯誤,錯誤原因 :{}", e);
}
}
文件拷貝
用 NIO 來完成文件拷貝,有兩種實現方式,一種是用 Buffer 完成兩個文件之間數據的轉移,另一種是直接使用 Channel 來完成文件復制。
Buffer 完成
通過 Buffer 來完成文件復制,步驟如下:
- 獲取源文件(source)和目標文件(target)的 channel;
- 設置緩沖區;
- 在循環中,通過緩沖區,將 source 的數據寫入到 target 的 channel 中,完成寫入,即復制成功。
// 將兩個channel通過byteBuffer進行轉移
private static void copyFileUseBuffer(String sourceFilePath, String targetFilePath) {
File source = new File(sourceFilePath);
File target = new File(targetFilePath);
// 獲取文件輸入輸出流
// 從輸入輸出流中獲取輸入輸出 channel
try (FileInputStream fileInputStream = new FileInputStream(source);
FileOutputStream fileOutputStream = new FileOutputStream(target);
FileChannel fileInputStreamChannel = fileInputStream.getChannel();
FileChannel fileOutputStreamChannel = fileOutputStream.getChannel()) {
// 分配緩沖區
ByteBuffer byteBuffer = ByteBuffer.allocate(BYTE_BUFFER_LENGTH);
// 將輸入流中的數據寫到緩沖區
// 這里需要循環讀取,如果是大文件,不能直接建立一個很大的內存空間,直接全部放進去,並且還可能放不進去
while (true) {
byteBuffer.clear();
int read = fileInputStreamChannel.read(byteBuffer);
if (read == -1) {
break;
}
// 翻轉緩沖區
byteBuffer.flip();
// 將翻轉后可以對外寫的緩存區的內容寫到輸出流,從而形成文件
fileOutputStreamChannel.write(byteBuffer);
}
} catch (Exception e) {
logger.error("文件復制錯誤,錯誤原因 :{0}", e);
}
Channel 完成
但其實,Java 官方也考慮到這個需求,其內置了一個通道復制的函數,可以直接完成復制。
// 直接用channel的復制完成文件復制
private static void copyFileUseChannelTransfer(String sourceFilePath, String targetFilePath) {
File source = new File(sourceFilePath);
File target = new File(targetFilePath);
// 獲取文件輸入輸出流
// 從輸入輸出流中獲取輸入輸出 channel
try (FileInputStream fileInputStream = new FileInputStream(source);
FileOutputStream fileOutputStream = new FileOutputStream(target);
FileChannel fileInputStreamChannel = fileInputStream.getChannel();
FileChannel fileOutputStreamChannel = fileOutputStream.getChannel()) {
// 直接將輸入channel復制到輸出channel
fileOutputStreamChannel.transferFrom(fileInputStreamChannel, fileInputStreamChannel.position(), fileInputStreamChannel.size());
} catch (Exception e) {
logger.error("文件復制錯誤,錯誤原因 :{0}", e);
}
}
總結
本文,我們通過文件的讀取,寫入,復制,從而理解了 Buffer 和 Channel 的作用和使用方式,在后續的網絡編程中,我們還要用到這些操作方式,以便逐步深入到 Netty 的學習范圍。
本文中代碼已上傳到 GitHub 上,地址為 https://github.com/wb1069003157/nettyPre-research ,歡迎大家來討論,探討。

文章在公眾號「iceWang」第一手更新,有興趣的朋友可以關注公眾號,第一時間看到筆者分享的各項知識點,謝謝!筆芯!
