channel和Stream的對比


這篇文章主要想總結下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慢嗎? 值得懷疑。 哪位大佬可以幫忙解下惑呢?

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM