Javaにおける文字コードまわりの話

毎度毎度、文字コードの話は面倒である。開発のメインストリームでないことと、外部調整が必要であることが原因だと思う*1
プラットフォームが変わるたびに、毎回検証しているような気がする。私の場合だと、毎回OSが違う。Windows Server、AIXHP-UXRed Hat Linux。毎回Java仮想マシンJVM)が違う。SunのJDK(今はOracleか)、JRockit(これもOracleか)、IBMJVM、HPのJVM。データベースエンジンは、だいたい同じ。
本当にみんな、毎回やっているのだろうか?


とりあえず、Javaで開発することは決まっている前提で、外部設計と内部設計にて、やるべき(と思っている)ことをまとめておく。
以降で挙げる例は、次のようなシステムをイメージしている*2

  • 構築対象システム
    • Webアプリケーション
    • バッチ
  • 外部システム
    • ホスト
外部設計

画面や帳票のようなユーザインターフェースであれば、外部設計時には、システムで用いる文字集合の見た目だけ*3を決定しておけばよい。例えば、次のようになる*4

  • 半角
    • JIS X 0201
      • スペース
      • ASCII制御文字(TAB、CR、LFの3字)
      • ラテン文字用図形文字(94字)
      • 仮名文字用図形文字(63字。ただし、オーバーラインは対象外とし、チルダを対象とする)
  • 全角
    • JIS X 0208:1997
      • 非漢字(523字)
      • 第一水準漢字(2965字)
      • 第二水準漢字(3390字)
    • IBM拡張文字(115区〜119区)

ここで、これらの文字が、エンドユーザの端末(大概Windows)で取り扱えるかどうかを調査しておく。この例であれば簡単。日本のWindows 2000、XP、Vistaであれば、バックスラッシュやオーバーラインは対象外にすべきだろう。で、文字の一覧を(テキストで)作成して、提示する。
UnicodeにおけるBMP基本多言語面)にある文字のみであれば、Javaとデータベースは、まず大丈夫。BMPにある文字のみしか扱ったことがないので、以降はこの前提で。


外部システムとファイル連携がある場合には、文字の見た目の集合を決めただけではいけない。ファイルに書かれているのは「文字」ではなく「バイト(列)」だからだ。そこで今度は、上記で定めた文字の集合を、連携時にどのようなバイト列で取り扱うかを決める。
Shift_JISを用いる」、「Windows-31Jエンコード方式を用いる」などとしてもよいが、きちんとやるなら、文字の見た目とバイト列の一覧表を付け合わせるのがよい。これであれば、お互いの勘違いもなくなるし、文字に対して複数のバイト表現が定義されている場合(今回の例であれば、IBM拡張文字と、13区に定義されているNEC特殊文字)にも対応できる。

連携先がホストである場合には、MQやHULFTの文字変換テーブルを用いる。この場合、まず、どちら側で変換を行うか決定する。
こちら側で変換するとなったら、「文字の見た目、変換前のバイト列、変換後のバイト列」の一覧を作成しておく。これと、連携先システムにおける「文字の見た目、バイト列」の一覧と付け合わせを行いたい。バイト列が不一致のところは、MQやHULFTの設定にて、変換テーブルを変更する。
検証するための環境(MQやHULFT)がなければ、課題として次フェーズへ持ち越す。


「文字の見た目、変換前のバイト列」の一覧は、Javaで検証しながら作成する。この時、Javaの内部コードとバイト表現が、1対1に対応することが望ましい。
次に、(私の環境で)Javaにおいて、1対多、多対1になる例を示す*5

(1) 1対多の例
  Ⅰ(ローマ数字の1)
    U+2160 <--(windows-31j)--> 0x8754
    U+2160 <--(windows-31j)--- 0xFA4A
(2) 多対1の例
  ¢(セント記号)
    U+00A2 ---(windows-31j)--> 0x8191
    U+FFE0 <--(windows-31j)--> 0x8191

(1)の例であれば、Javaにおける内部コード(Unicodeのコードポイント)がU+2160の場合は、Windows-31Jエンコード方式にてエンコードすると、0x8754のバイト列になる。0x8754をデコードすると、U+2160になる。しかし、0xFA4AをデコードしてもU+2160になる。この場合、ローマ数字の1のバイト表現は、0x8754とした方がよいだろう。こうしないと、バイナリレベルで文字やファイルそのものを突合する際などに問題が発生する。
(2)の例であれば、U+00A2は、処理の入口で許容対象外としてはじくか、処理の入口でU+FFE0によせた方がよいだろう。こうしないと、Javaの内部で、同一文字かどうかを判定する際などに問題が発生する。

内部設計

構築するシステムの内部で文字をどのように取り扱うかを決める。作業量は増えるが、外部調整がなくなる分、楽になるはず。作業自体は、外部設計の場合とあまり変わらない。
詳細には残さないが、次のような表ができればよいのかな。

対象 エンコード 設定内容(設定対象) 設定理由
データベース Windows-31J JA16SJISTILDE(Oracle 検証結果
外部システムと連携するファイル(パス名) Windows-31J ja_JP.SJISLinux
外部システムと連携するファイル(内容) Windows-31J windows-31jJava 検証結果
ログファイル(パス名) Windows-31J ja_JP.SJISLinux ログ監視ソフト
ログファイル(内容) Windows-31J windows-31jJava ログ監視ソフト
ダウンロードファイル(パス名) UTF-8 ja_JP.UTF-8Linux IEの「ファイルのダウンロード」ダイアログ
ダウンロードファイル(内容) バイナリファイル

検証に使うプログラムは、だいたい同じ。整理しておく。

2010/07/24追記

Javaにおける文字コードまわりの話(5)」に、(整理し切れてはいないのだが)簡単なまとめを書いた。

*1:今となってはどうにもならない、そもそも論はおいておいて。

*2:自分が携わったシステム開発のうち、3/4がこの形態だったから。

*3:システム内部で、これらの文字をどうエンコードしてストレージに保存していたとしても、エンドユーザに文句は言われないだろう。

*4:簡単のため、字体に関する要件がないとする。

*5:なぜこんなことになるのかは、それなりの理由がある。例の(1)と(2)は別の理由。それはまた今度。