Java的I/O函式庫可分成讀寫字節流的InputStream和OutputStream和讀寫字元流的Reader和Writer。如果InputStream的內容都是字元,那麼可以透過InputStreamReader,將InputStream轉為Reader。通常使用Reader讀取文字會比使用InputStream讀取還要來得方便,因為InputStream是以byte為單位,而Reader是以char為單位。但是最近筆者發現InputStreamReader(或是Reader本身)有一些問題,呼籲大家最好不要再使用InputStreamReader(或是Reader)了。



發生過程

事情是這樣的發生的,筆者在Android上使用InputStreamReader來讀取URLConnection的InputStream(getInputStream()),因為確定該URL會回傳純文字的內容,所以很直覺的就使用Reader來處理文字。但是,後來卻發現InputStreamReader在某些Android 4.3版本以下的裝置(不是所有裝置)上,讀取特定的InputStream內容時,會沒辦法正常的讀取,InputStreamReader的ready()方法永遠傳回false,但是InputStream內卻是有資料的。

推測原因

單就問題的發生過程來看,Android 4.3版本以下某些裝置上的Java Runtime環境可能有BUG,會導致特定資料內容的InputStream無法使用InputStreamReader進行讀取。

最遭的情況是,任意平台下的Java Runtime環境可能都有這個BUG,會導致特定資料無法使用Reader來讀取。

處理方式

將InputStream讀取到ByteArrayOutputStream中,而不使用InputStreamReader。然後再將ByteArrayOutputStream讀出來的byte陣列(toByteArray())解碼(decode)成原本的文字(String)。之後再進行測試,結果完全正常。

結論

若要避免掉這問題的發生,那就別再使用InputStreamReader了!要更保險一點的話,連Reader也不要用了。

一般使用Reader來讀取InputStream的文字,可能會撰寫以下程式:

Reader r = new InputStreamReader(inputStream, DECODER);
StringBuilder sb = new StringBuilder();
char[] c = new char[BUFFER_SIZE];
while (r.ready()) {
    int length = r.read(c);
    for (int i = 0; i < length; i++) {
        sb.append(c[i]);
    }
}
r.close();

若配合BufferedReader,程式會較容易理解:

Reader r = new InputStreamReader(inputStream, DECODER);
BufferedReader br = new BufferedReader(r);
StringBuilder sb = new StringBuilder();
while (br.ready()) {
    sb.append(br.readLine()).append("n");
}
br.close();

不使用Reader的話,可以改用ByteArrayOutputStream來暫存byte陣列,之後再用new String的方式進行byte陣列的文字解碼,程式如下:

ByteArrayOutputStream bao = new ByteArrayOutputStream();
String text;
int c;
byte[] buffer = new byte[BUFFER_SIZE];
while ((c = inputStream.read(buffer)) >= 0) {
    bao.write(buffer, 0, c);
}
inputStream.close();
text = new String(bao.toByteArray(), DECODER);
bao.close();

用以上的方式來讀取文字,因為沒有使用到Reader,所以也就沒有讀取失敗的問題了。