Home: Up

JavaTips(メモ)

説明文は自分の解釈で書いている為間違ってたりする可能性がかなりあります.
間違いなどがありましたらぜひ指摘してください.

文字コード(国際化関連)

String#getBytes()

UnicodeであるStringオブジェクトを指定したエンコーディングで変換したバイト配列を得ます。
指定できるエンコーディングでよく使うものは以下のような感じです。
(ちょっと調査不足でいいかげんなので注意。今度直します)
正確なものはJDKドキュメントの「国際化」の項を参照してください。
風間さんのページをご紹介しておきます.

ISO-8859-1 US-ASCII相当。上位バイトは捨てられる。
ISO-2022-JP JISコード。メールでおなじみ
Shift_JIS JDK1.1まではSJIS相当、JDK1.2からはMS932相当となっています。
MS932 Windowsネイティブなコード系
SJIS 古いMS-JVMのみMS932相当のマッピングのようです。
古いIEでファイル書き込みを行って見て発覚しましたが。正確なバージョンはよく覚えていません。
たぶんBuild29??というやつだと思います。
最近のMS-JVMではそういうことも無いようです。ややこしい。
EUC_JIS Unixでおなじみ。半角カナは使わないっと

尚、デフォルト(無指定)ではプラットフォームのデフォルトエンコーディングとなります。これがJDKバージョンなどによって違うので要注意です。

文字化け(Servlet/ファイル出力)

「文字列が全然表示されない」というのは、他のFAQにお任せするとして、ここでは、一部の文字だけが意図と異なる文字で出力される件です。

特によく問題になるのは、波線記号(WAVE DASH)/マイナス記号(MINUS SIGN)他いくつかの文字だけが'?'(0x3f)になってしまうという物です。

これはさまざまな文字コード系からUnicodeにコード変換する段階で起こる問題によります。各種文字コードからUnicodeに変換するときに使われる変換表に(同じ文字コード系からでも)いくつかの種類があり、そのどれを使うかによってUnicodeの異なる文字に変換される場合があります。

この変換表の相違については「XML日本語プロファイル」に詳しく書かれています。

しかし、異なる文字コードに変換されたからといって、「どちらか一方の変換表が間違っているせいだ」とも言い切れない事情があるようです。
それはともかく、同じ文字が異なる変換表を使ってUnicodeに変換された場合に、異なる文字コードになるということは、文字コードの比較による検索などに失敗するようになるということです。

私のメインがWindows環境なので、そういう例ばかりで恐縮ですが、'〜'(WAVE DASH)という記号に関して、Unicodeでは、0x301c/0xff5eという二通りの表現があります。(というよりその何れかに変換されてしまう)

その変換を行ったコンバータで出力も行っている限りは問題は発生しませんが、異なるコンバータ、つまり、入力エンコーディングと出力エンコーディングが異なる場合には正しく逆変換が行われないケースが発生します。

★例:'〜'問題
JISで書かれた'〜'を"ISO-2022-JP"等のコンバータで変換すると0x301cとなり、Shift_JISで書かれた'〜'を"Shift_JIS"(JDK1.2以降)"MS932"等のコンバータ※で変換すると0xff5eとなります。
※x-sjis-cp932と呼ばれるWindowsが使っている変換表に基づくコンバータ。

問題はUnicodeからのコンバートで、これは日本語プロファイルには書かれていませんが、
・0xff5eを"ISO-2022-JP"コンバータで変換するとunmatch('?':0x3f)となる。
・0x301cを"Shift_JIS"などで変換するとunmatch('?':0x3f)となる。
つまりは、入力と出力のエンコードを別のものにしてしまうと"〜"が正常に出力できない・・。
'〜'がうまくいっても別の文字で問題が出る可能性があります。

特に入力文字列が複数のエンコーディングで書かれていると最悪の状況になります。
(どのコンバータを使ってもどこかの文字が化ける)

例:
Windowsでnative2asciiでUnicodeエスケープに変換したリソースとencoding="ISO-2022-JP"なXML文書を混ぜ合わせて出力しようとした結果、出力エンコーディングを"ISO-2022-JP"にした場合、リソース中の'〜'が'?'になり、出力エンコーディングを"Shift_JIS"などにした場合、XML中の'〜'が'?'となる。

これはコンバータで吸収してくれても良さそうなものだと私は思いますが・・・。
# unmatch(0x3f)にするくらいだったら両方のUnicodeから変換できてもよかろうに

っと思ったら、[Bug-ID:4211555]で改善要求が出されていたのですね・・・。
出所は[Servlet-ML:1112]から始まるスレッドにあります通り村田さん/風間さんの御尽力のおかげでした。ありがとうございます。
しかし、JDK1.3ではまだ修正されない模様です・・・T_T

こんなことを注意しながらプログラミングするのは馬鹿らしいので、
入力エンコーディングと出力エンコーディングは完全に一致させるのが懸命でしょうね。

しかし、native2asciiを使ったリソースファイルの元ファイルの文字コードは知るすべがないわけだから(JIS固定!っとか言っちゃえばいいんだけど)出力エンコーディングを入力に合わせようとしても無理だよねえ。XMLだって、encodingは〜〜固定!って言っちゃえば出力エンコーディングとあわせられるけど、parse後に元のエンコーディングを知るすべが無いのは同じだし。

回避方法1:以下のような約束を守る。

  1. 「このプログラムは出力エンコーディングに〜〜を用いていますので、リソースファイルは〜〜コードで記述して、native2ascii -encodingを使ってください」とお願いする。
  2. 「*出力エンコーディングと異なる文字コードで書く場合は*特定の文字だけは、これまた特定のUnicodeエスケープで予め★記述してください」とお願いする。
    ★native2asciiを行う前の段階で、です。
  3. 「このプログラムは出力エンコーディングに〜〜を用いていますので、XMLファイルは〜〜コードで記述して、encodingには〜〜と記述してください」とお願いする。
  4. 「出力エンコーディングと異なる文字コードでXMLを書く場合は、特定の文字だけは、これまた特定の文字参照で記述してください」とお願いする。
  5. ソースファイルのエンコーディングと出力エンコーディングが異なる場合は、ソースファイル中に'〜'等の変換揺れのある文字を記述しない。または出力エンコーディングに応じて異なるUnicodeエスケープで記述する。(例:出力が"ISO-2022-JP"なら"\u301c"と書く)

しかし、それではUnicodeエスケープを解釈しないべたのテキストファイルリソースなどでは回避できません。
# そんな物ははなから出力エンコーディングと同じコードで書くように
# 指示しているはずなので普通は問題は出ないとは思いますが。
## 私はプレーンテキストなリソースを好きな文字コードで書いてもらって、
## パラメタでエンコーディング名も指定できるようにしたいのです!!

回避方法2:変換揺れを吸収する出力ストリームをかませる。
出力エンコーディングに応じて、他の入力エンコーディングで異なるUnicodeに変換されて、そのままでは正しく出力エンコーディングに変換できないような文字に関してだけ補正を行うラッパストリームを使う。

OutputStreamWriterの外側にそのエンコーディングに応じて異なる補正を行うようなラッパストリームを被せる。
出力エンコーディング毎にクラスまたは処理を追加しなければならないので、作るのが大変。
取り敢えず、"ISO-2022-JP""MS932"だけに対応したWriterを作っています。
まだ、ちゃんと作る価値があるかはっきりしていないので参考用ソースとして置いておきます(Shift_JISコメントです)。こういうことをしようとしているってことで。

文字列リソースの扱い

国際化されたプログラムの条件として、特定の言語やプラットフォームに依存した文字コードを使用してはならないというのがあります.
そこでメッセージの文字列などは国と言語に応じたリソースファイルを用意し、プログラムの方では 国コード/言語コードに応じたリソースセット(ファイル)から、 特定のキーに応じたローカライズされたメッセージを取り出して使用する必要があります.
以下のようなリソースファイルを作成しておきます.

[Resource.java]
import java.util.*;
public class Resource extends ListResourceBundle {
  public Object[][] getContents() {
        return contents;
  }
  static final Object[][] contents = {
        {"key1","Open"},
        {"key2","Save"},
        {"key3","Exit"},
        {"key4","Edit"},
          :
          :
  };
}
[Resource_ja.java]
import java.util.*;
public class Resource extends ListResourceBundle {
  public Object[][] getContents() {
        return contents;
  }
  static final Object[][] contents = {
        {"key1","開く"},
        {"key2","保存"},
        {"key3","終了"},
        {"key4","編集"},
          :
          :
  };
}

_jaは日本語用リソースです。
そして、プログラム中では以下のように文字列を利用します。

ResourceBundle  resources_;
resources_ = ResourceBundle.getBundle(
                   "Resource",Locale.getDefault());
try {
    str = resources_.getString("key1");
} catch (MissingResourceException mre) {
    str = null;
}

上記のように文字列を扱う事で、とりあえず英語圏と日本で正常に文字表示が可能になります。
サポート言語を増やす場合はリソースファイルを追加するだけですみます。
なおリソースファイルは下記のように.propertiesとして記述しても良いです。

[Resource_ja.properties]
key1=開く
key2=保存
key3=終了
key4=編集
  :
  :

呼び出し部分はまったく変わりませんのでこちらの方が楽です。
ただし、こちらはこのファイル自体をクラスファイルと同じディレクトリに配置する必要があるので、コンパイラの出力先を変えている場合はご注意。
また、native2asciiでUnicodeエスケープに変換しておかなければなりません。


JavaおよびHotJavaは米国Sun Microsystems社の商標または登録商標です.