- ベストアンサー
String <=> byte配列 の際のエンコード(続)
- Stringのエンコードに関する勘違いについて質問があります。
- サーブレットでの日本語リクエストパラメータのエンコーディングについて誤解があった。
- request.getParameter("test")の結果はブラウザのエンコーディングでバイト列を用い、値はそのままでエンコード名だけが指定されたものとなる。
- みんなの回答 (4)
- 専門家の回答
質問者が選んだベストアンサー
Unicodeと8859-1の対応表を見つけることができました。 参考URLを見て下さい。1バイトで表現できるすべてのコードについて 対応づけがされていますので、8859-1からUnicodeへの変換は常に 1対1にできます。コードも、8ビットを16ビットに拡張しただけで、 値としては同じものです。 Harryさんの元のご質問は、こういう状態になっているかどうかを 確認したかったのではないでしょうか。 少し補足しておく必要がありそうですね。 Stringは常にUnicodeで表現されます。一つの文字は常に固有のコード で表され、同じコードでも解釈によって別の文字になるということが ありません。つまり、「エンコード」という概念の入り込む余地が 無いのです。 String str1 = new String(b, "8859_1"); は、8859-1でエンコーディングされた文字列ではありません。 バイト列bを8859-1というエンコーディングによって解釈された 文字列です。気をつけておかなくてはならないのは、任意のバイト列は 任意のエンコーディングによって解釈できるというものではないと いうことです。例えば0x80というコード1バイトだけのデータは、 SJISでは解釈のしようがありません。つまり、Stringに変換することが できないわけです。Big5等の他のエンコーディングを介在させたときに 元の文字列に戻らなかったというのはそのためだと思います。 8859-1の場合は、1バイトのコードをすべて1文字として解釈できます から、データの欠落が起きなかったわけです。 前回の回答で未定義部分があると書いたのは不十分な情報でした。 お詫びします。ただ、元々のISOによる規格を私は確認できていませんし、 Unicode Consortiumによる参考URLでも、いくつかのコントロールコード として対応づけられている部分が含まれています。これらは非表示文字 として扱われますので、Harryさんの試行で"?"と表示されたのはその ためだと思われます。次のようなやり方で1文字ずつコードの内容を 確認できると思います。 for (int i=0 ; i<str1.length ; i++) System.out.print(" "+Integer.toHexString((int)str1.charAt(i))); System.out.println(); なお、前回の回答で「たまたま」という表現を使ったのは、「たまたま Harryさんのプログラムでは」という意味ではなく、「たまたまサン・ マイクロシステムズ社等による現在のJavaの実装では」という意味で 使ったつもりでした。規格と実装が一致しているとは限らないと警告した つもりでしたが、Unicode Consortiumによる対応表が見つかりましたので、 今回の件については、これは無視して下さい。
その他の回答 (3)
- ranx
- ベストアンサー率24% (357/1463)
ご免なさい。参考URLを忘れていました。
- ranx
- ベストアンサー率24% (357/1463)
SJISは文字を1~2バイトのコードで表します。したがって、UnicodeのString 型に変換すると、コードに応じて1バイトが1文字になったり2バイトが1文字に なったりします。一方8859-1は常に1文字を1バイトで表しますから、String型 に変換しても、そのバイト数に応じた文字数になります。 したがって、 byte[] b = "あいうえお".getBytes("SJIS"); のbは10バイトで5文字を表すコードになります。ところが、 String str1 = new String(b, "8859_1"); とした場合のstr1は、元の"あいうえお"ではなく、10文字の文字列となります。 String str2 = new String(str1.getBytes("8859_1"), "SJIS"); では10バイトのコードを介してもとの"あいうえお"を復元していることになります。 例えばSJISの"あ"が8859-1では別の2文字を表し、それをSJISに戻すとまた"あ"に なる・・・ということだと思うのですが、実際のところ、私が参照したコード表では、 SJISで表された"あいうえお"にはISO-8859-1には定義されていないコードが含まれる ことになっていますので、上記の変換ができたのは、たまたま現在の実装がそうなって いただけのことかもしれません。いずれにせよ、一つのエンコーディングで処理 されたバイト列をそのまま他のエンコーディングで解釈するというのは、かなり危険な ものと言え、できれば避けるべきだと思います。
お礼
回答ありがとうございます。 さっきの質問補足で書いたコードでは省きましたが、 str1 の内容も、もちろん出力して確認はしていました。 大部分が、「?」だったので、それが、ranxさんのおっしゃっている 定義されていないコードなのだと思います。 回答を読ませてもらいましたが疑問はまだ消えません。 もしString型が内部でUnicodeを間にかませてしまうと、 『 ???¢?????¨ 』という8859-1エンコードの文字列(str1)が Unicodeに変換された時点でバイトデータは破壊されてしまうと 思うんです。8859-1でないエンコードを使った場合、先のコードと 同様の変換ができないというのも腑に落ちません。 コンストラクタ String(byte[] bytes, String encode) が バイト列を指定のエンコードでエンコーディングされたものとして理解する だけなら、指定エンコードの種類を問わず、先のコードは必ず元の 文字列を復元できるはずです。内部でUnicodeに変換されたらもちろん バイトデータは破壊されますが、8859_1 の場合、実際の動作を見る限り それがあてはまりません。 先のコードで正しい変換ができたのは、 決して"たまたま"ではないと思います。 なぜなら、質問中にふれたサーブレットのコードも、Propertiesクラス利用 の際も同様の処理を行っているからです。 でも危険なものであるという点は私も同じ意見です。 実際、質問No,235091 のような文字化けの問題などがあるわけですから。 (このNo,235091 の質問の意味が最近やっとわかりました。)
補足
一部訂正します。 文字化けの問題はないです。 あれは、Propertiesクラスがわざわざ内部でエスケープ処理を してくれているのが裏目にでてるだけのようです。
- ranx
- ベストアンサー率24% (357/1463)
> では、request.getParameter("test") の結果返ってくる String は > ブラウザのエンコードでパラメータを表現するバイト列を用い、 > 値はそのままで、エンコード名だけを8859-1として構築されたもの、 > になると思うのですが、 > そういう認識で正しいのでしょうか。 質問の意味がよく分からないのですが、String型はつねにUnicodeで表現 されますから、バイト列そのままということはないですよね。もっとも、 0x0020~0x007eの分については、ASCIIそのままですから、そういう意味で あれば値はそのままとも言えます。もちろん、漢字のコードなどが入り 込んでいればそういうわけにはいきませんし、円通貨記号のように、日本で 通常ASCIIとして扱っているものでも8859-1とは異なるものもありますね。 というわけで、ご質問の文章、何だかおかしいという感じですが、よく 分からないので、明確にして頂けると回答しやすいと思います。
お礼
レスありがとうございます。 私は、自分の上記の認識であってるという自信がありません。 もし正しくないのであれば、つまるところ、質問は、 String#getBytes("8859_1") の結果、日本語が返ってくるというのは 一体どういうことか、ということになります。 byte[] b = "あいうえお".getBytes("SJIS"); String str1 = new String(b, "8859_1"); String str2 = new String(str1.getBytes("8859_1"), "SJIS"); System.out.println(str2); このコードが、正しく"あいうえお"を出力するのは確認しました。 そこで、二行目の str1 が、バイト列 b の元の値と、現在のエンコードが 8859_1 である、という情報とを両方保持してなければ、これらの 一連の変換の後、もとの文字列が復元できることが説明できない、 と思ったのが、質問で書いた"認識"の詳しい内容です。 ところで、ranxさんのレスのString型は常にUnicode...のところを読んで、 はっ、としました。言われてみればそうだった、と思ったんですが、 それを考えると、上のコードが元の文字列を出力するのが何故なのか、 全くわからなくなりました。 で、色々試してると、上のコードの 8859_1 の部分を二箇所とも BIG5 とか 別のエンコードに変えると、うまく変換できなくなることに気づきました。 それで勝手な推測なんですが、 String型は文字列表現にUnicodeだけでなく、8859_1も 利用できる、ということはないですか。
お礼
おはようございます。URL見ました。 >Harryさんの元のご質問は、こういう状態になっているかどうかを >確認したかったのではないでしょうか。 「確認」という言葉を使えるほど、 こういう状態であることを予測してませんでした。 String型が8859-1も内部的な文字表現手段として利用してるのではないか、 という方向にばかり考えが向いていたので・・・ そうではなく、そもそも8859-1がUnicodeのサブセットのようなコード構成 になっていたのですね。 あるバイト列を8859-1エンコードで解釈した結果、「?」と表示されてしまう 未定義(と思っていた)部分は、それがUnicodeのコードに変換されるときに、 すべて、同一のコードにマッピングされて、復元不可能になってしまうのでは ないか、 という疑問があったのですが、これも、これらが未定義ではなく制御文字、 しかもやはり1対1対応、ということで氷解しました。 とてもすっきりしました。 本当にどうもありがとうございます。 ところで、URLの完全一対一の対応表を見て、 ちょっとこれらの文字コードの成り立ちに興味が湧いてきました。 この辺もうすこし自分で調べてみようと思います。