這篇文章主要想總結下NIO的channel的傳統io中的stream的差別在哪。網上找了很多文章,都感覺只是說了概念。然后自己大概看了下源碼,結合概念,整理一下。有些地方可能不是很准確,也希望可以給點意見,互相學習。
這里不講異步方面的東西,只是想單純講一下stream和channel在操作內存時的一些差異。
讓我產生問題的來源主要有倆個:
1. 從概念上解讀,stream是按照字節去處理的,看起來就像是水流一樣,一個接一個。而channel是按照數據塊來處理的。那么bufferedStream呢?加了buffer后是不是也是按照數據塊來處理呢。那么這時,bufferedStream和channel的性能區別是在哪里呢?
2. 網上發現了一些文章,通過拷貝文件的實驗來對比channel和stream的性能。stream使用的是bufferedStream。結果是channel的性能要比stream快1/3。當然我沒有做實驗去驗證,只是通過代碼的解讀來理解下為什么性能會有這么大的差別。
首先需要引入一些背景知識,用戶的線程是如何讀入和寫出文件的。下面這個圖是從一本書上截下來的,簡單的說明下讀取文件時的流程。
1. 磁盤的controller把數據從磁盤拷貝到系統內核區。
2.然后cpu把數據從系統內核拷貝的用戶的內存區。
3.系統對內存塊的操作是按數據塊操作的,這也是NIO的一個重要的概念,操作數據時盡量和操作系統相吻合,來提高內存操作的效率。

然后再分別分析一下channel和stream在操作內存時分別的步驟,就可以比較清晰的看出倆者的差別。
一 channel:
首先總結一下,這也是一個大家都知道的概念。channel的輸入端和輸出端都是byteBuffer。在內存操作的時候,也是以數據塊為單位來進行數據移動。下面具體說下輸出的步驟:
1. 如果我們使用的是directBuffer。 那么會直接調用native方法,把整塊的內存寫出到磁盤。
2.如果我們使用的是堆內的buffer,java會創建一個臨時的directBuffer,把堆內buffer的數據拷貝到臨時的 directBuffer。然后調用native方法把臨時的 directBuffer的內容整塊的寫入磁盤。
下面貼一下源碼來看一下:
static int write(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException { if(var1 instanceof DirectBuffer) { // 如果是directBuffer,直接整塊寫入磁盤 return writeFromNativeBuffer(var0, var1, var2, var4); } else { int var5 = var1.position(); int var6 = var1.limit(); assert var5 <= var6; int var7 = var5 <= var6?var6 - var5:0; // 創建臨時directBuffer ByteBuffer var8 = Util.getTemporaryDirectBuffer(var7); int var10; try { var8.put(var1); var8.flip(); var1.position(var5); // 直接將臨沭directBuffer整塊寫入磁盤 int var9 = writeFromNativeBuffer(var0, var8, var2, var4); if(var9 > 0) { var1.position(var5 + var9); } var10 = var9; } finally { Util.offerFirstTemporaryDirectBuffer(var8); } return var10; } }
二 stream
首先也說一下概念,stream是按照字節,一個一個操作內存的。但是,真的是這樣的嗎?
先看下FileOutputStream這個類:
public void write(byte b[], int off, int len) throws IOException { writeBytes(b, off, len, append); } private native void writeBytes(byte b[], int off, int len, boolean append) throws IOException;
可以看到,這里也是調用native方法,把整個byte塊都寫入了磁盤。具體native方法是怎么實現的,哪位大大可以幫忙分析一下?
再看下BufferedOutPutStream這個類,主要看下write方法和flushBuffer方法
public synchronized void write(byte b[], int off, int len) throws IOException {
if (len >= buf.length) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
flushBuffer();
out.write(b, off, len);
return;
}
if (len > buf.length - count) {
flushBuffer();
}
// 寫入內部的buffer區
System.arraycopy(b, off, buf, count, len);
count += len;
}
/** Flush the internal buffer */ private void flushBuffer() throws IOException { if (count > 0) { 調用代理的outputStream的寫出方法 out.write(buf, 0, count); count = 0; } }
所以,從這個角度來看,stream和channel都是直接把整個數據塊對底層進行寫入的。
那么,stream真的比channel慢嗎? 值得懷疑。 哪位大佬可以幫忙解下惑呢?
