2014年6月3日火曜日

文字化ける

文字を化けさせる

iアプリをAndrod上で動作させるため、
SJIS(Shift_JS)のテキストファイルを読み込もうとしている。

しかしこれには文字化けの問題がある。

Androidは一旦置いとく。

実験

SJISのテキストファイルをつくる

まず元となるテキストファイルを用意する。
文字コードはutf-8とした。

hoge.txt

アンドロイド☆イズ☆デッド 

このファイルの文字コードをSJISに変更する。

% iconv -f utf-8 -t sjis hoge.txt > hoge.sjis.txt

UTF-8で読み込む

InputStreamReaderに文字コードを指定する。
指定しないとデフォルトの文字コードを使う。
Androidも(キミのmacも)デフォルトはutf-8だ。

FileInputStream is = new FileInputStream("hoge.sjis.txt");
InputStreamReader isr = new InputStreamReader(is/*, "utf-8"*/);
BufferedReader reader = new BufferedReader(isr);
String line = reader.readLine();
System.out.println("line: "+bytesToHex(line.getBytes(/*"utf-8"*/)));

bytesToHex(byte[]) http://stackoverflow.com/a/9855338

実験結果

line: EFBFBD41EFBFBDEFBFBDEFBFBD68EFBFBDEFBFBDEFBFBD43EFBFBD68EFBFBDEFBFBDEFBFBD43EFBFBD59EFBFBDEFBFBDEFBFBD66EFBFBD62EFBFBD68

もちろんこんなString lineをそのままUTF-8文字として出力しても
文字化けして読めないんだけど、
そのバイト列としても、なんだかおおよそ文字とは思えないものがでてくる。
なんだこれは。

もとの文字列のバイト列とくらべてみよう。

% hexdump hoge.sjis.txt

の出力は

0000000 83 41 83 93 83 68 83 8d 83 43 83 68 81 99 83 43
0000010 83 59 81 99 83 66 83 62 83 68 0a               
000001b

である。やっぱぜんぜん違う感じする。

utf-8として読み込んだ文字列のバイト列には
EFBFBDというものが繰り返し出現することがわかる。
これを**に置き換えて整形すると……

0000000 ** 41 ** ** ** 68 ** ** ** 43 ** 68 ** ** ** 43
0000010 ** 59 ** ** ** 66 ** 62 ** 68

似ている。

考察

InputStreamReaderhoge.sjis.txtの内容を読むときに、
utf-8の文字が来ると期待するため、
その範疇にない文字を「表現できない文字」として
EFBFBDに置き換えてしまっている。

JavaのStringになった時点で、元の情報を失う。

結論

InputStreamReaderで文字化けしたStringを、元のbyte列に戻すことはできない。
それゆえ、他の文字コードに変換することもできない。

iアプリとAndroid

iアプリではデフォルトの文字コードがSJISだった。
Androidではutf-8
だから、

new InputStreamReader(is);

とか、

"いやまずiアプリって何".getBytes();

とかやったときに使われる文字コードが
iアプリとAndroidで異なる。