2012年9月12日水曜日

Javaで特定の文字が文字化けする

Javaで、機種依存文字では無い特定の文字が文字化けをするという問題があります。「〜‖—−¢£¬」の7文字がそれにあたります。「�」や「�」等の機種依存文字が化ける問題はあたりまえなので取り上げません。

問題が発生するのは、以下の2つの条件を満たす場合です。
  • SJIS系の文字コードを使用している
  • 入力時と出力時で異なる文字コードを使用している
逆に言えば入力時と出力時で文字コードを同一のものにすれば何も問題はありません

Webシステムなどでは画面から入力を受付け、それをDBに格納し検索時に表示するなどの使われ方をしますので、入力時と出力時であえて異なる文字コードを指定するケースは少ないはずです。ですから、実質的に問題が生じるのは、実装者が「何も指定していない」「何を指定しているかわからない」「調べ方もわからない」という場合が多いのではないかと思います。異なる文字コードを使用する複数のシステムを結合している場合にもこの問題が発生する可能性があります。

「〜‖—−¢£¬」の7文字はJIS X 0208で定められた範囲の文字であり、JISでもSJISでもMS932でもCP943でもEUC-JPでも共通して使える普通の文字です。機種依存文字ではありません。にもかかわらず文字化けします。

Java内部ではUnicodeを使用しています。そのため、どんな文字も一旦Unicodeデータとして格納されます。このとき、Unicodeのどの文字として格納するかのマッピング情報が必要になります。このマッピング情報が文字コードごとに少しずつ異なることが原因です。


具体的にセントマーク「¢」について考えます。「¢」はJIS X 0208で定められた文字です。つまり、日本語としての文字集合の範囲です。全角の「A」「0」「¥」などと近い扱いといえるでしょう。

Unicodeでは「¢」に相当するものは「U+00A2」と「U+FFE0」の2箇所で定められています。「U+00A2」があるエリアは、半角英数字記号のASCII文字が定められている領域の近くで、「U+FFE0」があるエリアは、全角の英数字記号や半角カナが定められているエリアです。個人的にはSJISとしての「¢」を格納するのは「U+FFE0」のほうが適切であるとは思いますが、とにかく、1つの文字を格納する候補がUnicodeでは複数あった、という点が問題の源泉です。そしてこのマッピングをベンダがそれぞれ独自に定めたため、文字コードによって同じ文字をUnicode上のどこに格納するかが異なる文字が発生してしまいました。

SJISでは「¢」をUnicodeの「U+00A2」に割り当てており、MS932では「¢」をUnicodeの「U+FFE0」に割り当てています。逆に、SJISではUnicodeの「U+FFE0」を変換できない文字としています。このため、MS932で入力した「¢」はSJISとして出力すると「?」に文字化けするのです。ところがMS932ではUnicodeの「U+00A2」を「¢」に変換できます。これはMS932が互換性を考慮し柔軟な設計にした結果でしょう。

このような仕組みで「〜‖—−¢£¬」の7文字は、条件によって文字化けします。といっても入力時と出力時に同じ文字コードを指定すればいいだけの話なので、それさえわかれば実質的には対策は容易でしょう。

¢ £ ¬
SJIS U+301C U+2016 U+2014 U+2212 U+00A2 U+00A3 U+00AC
CP943 U+301C U+2016 U+2014 U+2212 U+FFE0 U+FFE1 U+FFE2
MS932 U+FF5E U+2225 U+2015 U+FF0D U+FFE0 U+FFE1 U+FFE2
上記はそれぞれの文字を各文字コードで指定したときにUnicodeでどこに格納されるかの対応表です。


¢ £ ¬
SJIS→CP943 × × ×
CP943→SJIS × × ×
SJIS→MS932 × × × × ¢ £ ¬
MS932→SJIS × × × × × × ×
CP943→MS932 × × × × ¢ £ ¬
MS932→CP943 × × × × ¢ £ ¬
実際にJavaで実行した結果がこれです。「×」は文字化けした文字。基本的には、文字コードごとのマッピング先が異なれば、文字化けすると考えていいでしょう。例外的にMS932は「U+00A2」「U+00A3」「U+00AC」も正しく戻せるようにしているようです。

Java の MS932, Cp943C, SJIS の違い

Java※ の MS932, Cp943C, SJIS の変換で異なる点、および注意を要する点をまとめてみました。

※調査したバージョン:Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_02-b06)

■概要

MS932 と Cp943C の両者は、Windows-31J の文字セットを扱えます。

主な違いは、Unicode への変換で一部異なるコードポイントに変換される事と、NEC特殊文字とIBM拡張文字の両方で定義されている文字を、Unicode から MS932/Cp943C に変換する際に、どちらのコードポイントかという点が異なります。

MS932/Cp943C は Unicode との対応付けが 一部の JIS X 0208 の文字に関して、SJIS と異なり、EUC_JP や ISO2022JP へ変換出来ない文字があるので注意が必要となります。

■MS932 と Cp943C で異なる Unicode コードポイント

MS932 と Cp943C では、Unicode に変換した時に 表1 に示した違いがあります。

表1 MS932 と Cp943C で異なる Unicode のコードポイント
表1

次の 表2 は、Unicode のコードポイントは同一だが、MS932/Cp943C への変換で異なるコードポイントへ変換される文字の一覧です。

表2 Unicode → MS932/Cp943C で変換先が異なるコードポイント
表2
※MS932 では、NEC特殊文字に変換され、Cp943C では、IBM拡張文字に変換されます。

■JIS X 0208-1983 で追加された文字に変換されるコードポイント

NEC特殊文字、NEC選定IBM拡張文字、IBM拡張文字で定義され、後に JIS X 0208-1983 で 2 区に追加になった文字は、JIS X 0208 の 2 区のコードポイントに変換されます。
表3 は、その一覧です。

表3 JIS文字に変換されるコードポイント
表3

■MS932/Cp943C と SJIS の相違点

MS932 もしくは Cp943C コンバーターは Unicode との対応付けで、表4 の赤字で示したコードポイントが、SJIS コンバーターで変換した場合と異なります。

EUC_JP や ISO02202JP コンバーターは、SJIS コンバーターと同じ Unicode のコードポイントを用いていますので、932/Cp943C との相互変換の際には注意が必要となります。

表4

■重複符号化されている文字

MS932/Cp943C とも、Unicode への変換で、重複符号化されている文字は、多対1 の変換となっています。
Unicode から MS932/Cp943C への変換は、表2 の文字以外は、MS932 と Cp943C は同一の変換となっています。

MS932 の変換は、次のページをご覧下さい。

Windows-31J の重複符号化文字と Unicode (当サイト・コンテンツ)
http://www2d.biglobe.ne.jp/~msyk/charcode/cp932/uni2sjis.html

■ユーザー定義文字

MS932/Cp943C では、95〜114区 (F040〜F9FC) がユーザー定義文字の領域となっていています。

両方のコンバーターとも Unicode の私用領域の U+E000〜U+E757 と対応付けされています。

■参照

WebSphere V3.5 発表ワークショップ資料
> 第3回 「プログラミング・モデル(JSP、Servlet)」 > 文字コード
http://www-6.ibm.com/jp/software/websphere/developer/wsv35wslib/pdf/was35_psj5_1.pdf

Windows-31J情報 (当サイト・コンテンツ)
http://www2d.biglobe.ne.jp/~msyk/charcode/cp932/index.html

文字コード掲示板 過去ログ
http://www2d.biglobe.ne.jp/~msyk/cgi-bin/charcode/bbs.cgi?c=gr&n=55

※間違いや不明な点がありましたらご指摘ください。よろしくお願いいたします。