文字化け調査でハマりがちな罠

いわゆる「文字化け」が発生した際、技術者は原因を調べようとするが、しばしば罠に陥り、原因を見逃したり、別の箇所でひっかかったりする。
主に Java を想定して、メモってみる。

文字コードに対する理解不足でハマる

文字コードを「文字をデータとして表すための決まりごと」くらいにしか理解していないと、原因発見はおぼつかない。
例えば、「文字集合 (文字セット)」と「エンコーディング (符号化方式)」の関係とか。Unicode文字集合UTF-16UTF-8Unicode 用のエンコーディングである。また、JIS X 0201JIS X 0208文字集合で、それに対応するエンコーディングとして Shift_JISEUC-JP、ISO-2022-JP などがある。

ふたごの「シフト JIS」にだまされてハマる

いわゆる「シフト JIS」には、2 種類ある。

Shift_JIS
JIS 規格に準拠した「シフト JIS」
Windows-31J
Windows 独自の「シフト JIS」。俗に言う機種依存文字 (丸付き数字とか、ギリシャ文字とか) が追加されている他、「〜」など一部の文字で扱いが変わることがある

例えば、メモ帳で作ったファイルに、"Shift_JIS" と文字コード指定するのは間違いで、 "Windows-31J" と書くのが正しい。
特に、Java については事情が複雑になっている。風間一洋氏による下記のページを読んでおきたい。

Shift_JISエイリアスの変更について
http://www.ingrid.org/java/i18n/encoding/shift_jis.html

これがどう影響するかというと、JDK 1.3 でコンパイルしたソースを JDK 1.5 で動かしたら文字化け、といったことが起こるのだ。しかし、事情を考えずに「これ Java のバグ?」とか言っちゃダメですよ。

その言語での文字コードの扱いを知らずにハマる

JavaC# などでは、文字列は常に Unicode で扱われる。よくある勘違いを挙げてみる。

入力と出力が両方 Windows-31J なら文字化けは起きないはずだ
ウソ。入力時に「Windows-31JUnicode」、出力時に「UnicodeWindows-31J」という文字コード変換が行われるため、文字化けが発生する可能性はある。
内部形式が Unicode なので 1 文字が必ず char 1 個
ウソ。JavaC# では、Unicode のコードそのままではなく、UTF-16エンコードしたものを内部形式としている。従って、1 文字が複数の char になることはある。

他の言語でいうと、D 言語は内部形式が UTF-8 なので、同じ Unicode 系だからといってそのまま Win32 の UnicodeAPI 関数に渡したりはできない、とか。

入力側が正しいと思い込んでハマる

以下のようなコードを書いたとする。

Writer w = new FileWriter(path);
w.write("あ〜");

これで出力されたファイルが文字化けしていた時、出力の仕方が悪かったと考えるのは気が早い。
Javaソースコードは、コンパイルされる際に Unicode に変換される。この時、メモ帳で書いたのに文字コードとして Shift_JIS が指定された場合、コンパイルした時点で文字化けが発生する可能性がある。

コンソールに出力してみてハマる

ファイルからの入力時の文字化けが起きているのではないか、と思った場合、試しにコンソール (標準出力) に出して見てみる、という人は多いと思う。

String line = br.readLine();
System.out.println(line);

これで化けていたら入力側が文字化けの原因、と思うのは気が早い。
println() メソッドが標準出力へ出力する時に、文字コード変換が行われるからだ。
また、標準出力までは化けずに届いているのに、コンソールで正しく表示できていない、というパターンもありうる。特に、TelnetSSH を使って他のマシン上で実行している場合には、文字コードの設定を確認した方がいい。
この罠を避けるためには、内部形式そのまま (Java なら UTF-16 あたり) でファイルに出力する (標準出力に出してリダイレクト、だと意味ないですよ) か、文字列内の各コードポイントを数字としてコンソールに出力してみる必要がある。

テキストエディタで開いてみてハマる

前項の最後で、ファイルに出力するといいと書いたが、これをテキストエディタで開く場合にも注意がいる。

Unicode 対応」とされているエディタなら大丈夫だよね
甘い。「UTF-8/16 のファイルが扱える」というだけで、内部形式は Windows-31J というものも少なくないからだ。その場合、UTF-16 のファイルを開けても、「Unicode にあって Windows-31J にない文字」が捨てられてしまう。また、Unicode の一部文字は表示されない制御文字なので、内部形式が Unicode のエディタであっても、文字化けの確認に使う場合には注意が必要。
内部形式が Unicodeテキストエディタなら大丈夫だよね
甘い。上と逆のパターンで、Windows-31J のファイルを開く場合、エディタは Unicode に変換してから表示することになる。ここで文字化けが発生する可能性もあるわけで、やはり注意が必要。
UTF-16 のファイルを、内部形式が Unicode のエディタで開けば大丈夫だよね
甘い。エディタはちゃんと読み込んでるんだけど、フォントが足りなくて表示できない、ということも起きうる。

結局、バイナリエディタでないとちゃんとした検証はできないのだ。

環境ごとの「規定値」を忘れてハマる

普段は「文字化けが発生しないのが当たり前」なので忘れがちだが、文字コードを指定しない場合にも必ずいずれかの文字コードが使われている。
例えば、Windows であれば実行時のコードページに応じて決められるだろうし、UNIX/Linux なら環境変数からロケールを取得して決められるだろう。
いったいどれが使われているのかを確認しておかないと、「あっちのマシンでは動いたのに、こっちのサーバでは動かない」といったことになりがち。
ちなみに、Java の場合はシステムプロパティ "file.encoding" を参照すればよい。

自動判別を過信してハマる

Java では、入力時の文字コードを "JISAutoDetect" と指定すると、入力データの内容を読み、自動的に適切な文字コードに切り替えてくれる。テキストエディタや Web ブラウザにも同様の機能がよくみられる。
しかし、自動判別の性能には理論的な限界というものがあって、常に正しく判別できるわけではない。
したがって、「自動判別を使っているのに正しく読めないのは、Java のバグだ」は勘違い。自動判別に頼るのは、最後の手段だと思った方がいい。