2012-04-18 87 views
0

我從一個InputStreamReader開始,但是這緩衝了它的輸入,從輸入流中讀取更多的數據(正如Java文檔中提到的那樣)。鑽研源代碼(Java版「1.7.0_147-IcedTea項目」),我到了sun.nio.cs.StreamDecoder類,它包含的評論:是否可以從輸入流中讀取單個字符?

// In order to handle surrogates properly we must never try to produce 
// fewer than two characters at a time. If we're only asked to return one 
// character then the other is saved here to be returned later. 

所以我猜這個問題就變成了「是這樣的真的,如果是的話,爲什麼?「從對JLS所需的6個字符集的理解(非常基本!)中,始終可以確定讀取單個字符所需的確切字節數,因此不需要預讀。

背景是我有一個二進制文件包含一堆數據與不同的編碼(數字,字符串,單字節標記等)。基本格式是一組重複的字節標記(表示數據類型),如果需要該類型,則選擇數據。包含字符數據的兩種類型是以空字符結尾的字符串和前一個2字節長度的字符串。因此,對於空終止字符串我想這樣的事情會做的伎倆:

String readStringWithNull(InputStream in) throws IOException { 
    StringWriter sw = new StringWriter(); 
    InputStreamReader isr = new InputStreamReader(in, "UTF-16LE"); 
    for (int i; (i = isr.read()) > 0;) { 
    sw.write(i); 
    } 
    return sw.toString(); 
} 

但使用InputStreamReader從緩存預讀取,對基地的InputStream所以隨後的讀操作丟失的數據。對於我的特殊情況,我知道所有字符都是UTF-16LE BMP(UCS-2LE),所以我只是編碼,但我仍然對上面的一般情況感興趣。

此外,我見過InputStreamReader buffering issue這是相似的,但似乎回答這個具體問題。

乾杯,

+0

謝謝,但看起來像'DataInputStream'返回一個大端字符,在那裏我的具體數據是little-endian。 – Barney 2012-04-19 01:23:21

回答

3

所以我猜這個問題就變成了「這是真的,如果是這樣,爲什麼?」

是的評論是正確的,雖然在它的措辭中可能有點模糊。

單個Unicode代碼點的UTF-8編碼由1到4個字節組成;請參閱維基百科UTF-8 examples.。但在某些情況下,Unicode代碼點不能表示爲一個Java char。因此解碼器潛在的必須將多字節UTF-8序列解碼爲TWO Java char值......並將其中一個返回。

從我對JLS要求的6個字符集的理解中,始終可以確定讀取單個字符所需的確切字節數,因此不需要預讀。

對於可變長度編碼,這比這更復雜一點。解碼器讀取足夠的字節以形成一個Unicode代碼點。對於UTF-8,這將在1到4個字節之間,並且通過檢查它知道何時停止的字節。然後它將字節解碼爲1或2個UTF-16代碼單元(即Java char值),傳遞第一個字節,並保存第二個字節。

因此,您有可能以字節爲單位進行讀取,但不能用代碼點。這很好,因爲用戶的鍵盤(例如)正在生成代碼點。


而且,應該有可能創建無緩衝讀出器,其精確地執行作爲標準之一,但每次只從基礎流拉動單個碼點,因此可以在使用我上面的例子。

是的,應該可以做到這一點。然而,這樣的閱讀器需要進行4次獨立的系統調用才能讀取單個代碼點,而且效率非常低。

事實上,這似乎不是一個首選的實現,因爲如果需要我總是可以自己緩衝流。

不,它不是首選的實現。是的,您可以(理論上)在編碼器下方自行緩衝流。然而大多數程序不寫入建立棧是這樣的:

Buffered Reader > InputStreamReader > BufferedInputStream > raw InputStream 

相反,他們只是這樣做:

Buffered Reader > InputStreamReader > raw InputStream 

這將使你的方法執行真正的慢。 (並嘗試解釋給普通人的程序員,他爲什麼要放一個額外的緩衝明確的層進棧)

從OpenJDK7標準InputStreamReader的出現立即讀取和緩衝了來自基本流爲8K。

如果他們沒有這樣做,表現會很糟糕......見上文。此外,這是記錄的行爲 - 的Javadoc說:

「的InputStreamReader的read()的一箇中的每一個調用方法可能會導致從底層字節輸入流中讀取一個或多個字節要。啓用字節到字符的有效轉換,可以從底層流中讀取比滿足當前讀取操作所需的字節更多的字節。「

底線是,你的用例(您想絕對在Reader堆棧沒有低級預讀。)是極不尋常的,而不是由Java SE標準類庫的支持。如果你真的需要這個,可以自由地實現你自己的版本InputStreamReader,它不會提前閱讀。但它讓我覺得奇怪,你真的需要這個。

+0

感謝你的理解 - 使它更清晰......因此總是可以確定從流中讀取單個**代碼點**所需的確切字節數,但這可能需要多個Java字符代表它,所以ISR必須緩衝那個角色? – Barney 2012-04-19 01:11:10

+0

另外,應該可以創建一個完全像標準一樣執行的非緩衝讀取器,但只能從基礎流中一次抽取一個代碼點,因此可以在上面的示例中使用。事實上,這似乎不是一個首選的實現,因爲如果需要,我總是可以自己緩衝流。來自OpenJDK7的標準InputStreamReader似乎可以立即讀取並從基礎數據流緩存到8k。 – Barney 2012-04-19 01:17:55

+0

夠公平的。再次感謝更清楚地解釋它是如何工作的。 – Barney 2012-04-19 02:35:59

相關問題