文字コードの話

コンピュータでの文字の扱いの基礎知識について
知らないでもなんとかなっちゃっうもんだけど、知っておくと良いこと。

用語にも混乱があることが分かりました。文字セット、と、文字コード、があるとか。

  文字セットというと「英数字だけのセット」「英数字と日本語のセット」「全世界で使用されている全ての文字のセット」というように文字の集合のことらしい です。これらは単に文字の集合のことで、別に"A"を0x01に割り当てる、などの決まりは無いです。この純粋な文字セットに関しては検索してもほとんど ヒットません。

 文字コードというのは”ある文字セット”の中の文字に対して数値を割り当てたものです("A"を0x01、"B"を0x02とか)。
 対象とする文字セット、に対してイマイチ名前が無いのが混乱の元なんだと思うんです。
文字セットにもきちんと名前があれば理解しやすい(こんなに混乱する必要は無かった!)と思うんですが、残念ながら「JIS X 201」などを調べるといきなり文字コードとして説明されてしまいます(「0x82A0は”あ”」などの表が載ってます)。
 これは「対象とした文字の集合」と「文字と数値を割り当てた結果」を同時に見せれる、という説明上の都合で、初心者に理解させよう、という説明ではありません。
ほんとうに理解してもらいたいなら、「JIS X201」は以下の文字を対象としました、「英語と日本語の文字」をだーっと載せ、そしてそれらの文字を16ビット、0x0000から0xFFFFまでの数値に割り当てました、と書いた後に「割り当て表」を載せるべきです。

 さてさて、これが分かってやっとUnicodeとUTF-何々の関係も分かりました。
  Unicodeはまず、「全世界で使用されている全ての文字のセット」を対象にしています。文字を数値への割り当てる際は、文字の数が多いので1文字を 21ビットも使いました。0x00000から0x1FFFFFまで使った大きな文字コードです(実際フルには使ってないが)。
 最初(ver1.0)は16ビットで表していましたが文字が多すぎてビットが足りなくなったのでUnicode Ver2.0で21ビットにした、という歴史があるようです。なので普通の文字は16ビットで表せるが、マニアックな文字は21ビット使って表す、というような感じです(実際は21ビットだとかなり空きがあるようで19ビットくらいしか使ってないように読めます)。

 で、さらにUnicodeが混乱させるのはUnicodeはそのまま文字コードとしては使わない、ということです。1文字が21ビットのUnicodeをそのまま文字コードとして使うと結構メモリが無駄(必ず1文字3byte?)になってしまいます。
そこで(他にも理由はあるんでしょうが知らん)UTF-8やUTF-16、UTF-32などの形式で圧縮して文字コードとして使うのです(UTF-32は圧縮しないでUnicodeをそのまま文字コードとして使う文字コードなんでしょうか、1文字32ビットってもっとでかくなってませんか・・・)(UTF-32はデータベースとかで使うらしいです。やっぱ高速化でしょうか)。

 世間で流行っているUTF-8は文字を1byte~4byteで表します。感じとしては1byteで表せる文字は1byte使って表す、って感じです。つまり英数字が多い文字列ではデータが少なくて済みます。プログラミングではメモリの長さと文字列の文字数の対応を計算するのが面倒そうですがどうなんでしょう。

 さて、JavaはUTF-16を採用しています。
 UTF-16は16ビットだけで表せる文字は16ビットで、21ビット使うマニアックな文字は16ビットのを2文字分使って表します。構造的には処理しやすそうです。(1文字21ビットで済むところを32ビット使いますが、16ビットの文字とコードが被ってはダメなので、圧縮というより符号化が必要なことなどから、意外と良いバランスのようです。)
 この2文字分使って表すマニアックな文字をサロゲートペア文字と言います。たいていのJavaアプリはサロゲートペア文字に対応していません。つまりサロゲートペア文字には非対応です、といってしまえば「1文字は2byte」と単純になり、めでたしめでたし、です。
 頑張ってサロゲートペアに対応するためには、以下を考慮します。
 普通の文字は2byteなので、char型は2byteを確保します。で、サロゲートペア文字はcharを2つ使って表します。
 そのため、Javaでサロゲートペア文字を含むStringに対してlength()をやると文字の数と違う数値が出ます。lengh()はcharの数を返すのでサロゲートペア文字一つで「2」となります。
  本質的にはそのような文字列処理を行う場所全てで「一文字ずつisSalogatePear()というAPIでサロゲートペア文字かどうか判定しながら処 理を行う」ようにすることでJavaプログラムをサロゲートペア対応にする、ということです。(length()をオーバーライドしたいですか?残念でし た。Stringはfinal宣言されています。Stringを内部に持ったMyStringを作っても良いですね)

 実際には、サロゲートペアを含む文字列に対してはサロゲートペアを含む文字列用のAPIが用意され、少し簡単な書き方が出来るようです。
少し例を書いておくと、
str.length()
の代わりに
str.codePointCount(0, str.length())
で、
str.charAt(i)
の代わりに
str.codePointAt(i)
で、というような置き換えがあるようです。



「JIS」「Shift_JIS」「MS932」は全部、日本でよく使う文字セット(英数字と日本語文字のセット)を対象にした文字コードです。
「MS932」 は「Shift_JIS」と大体同じような割り当てです、が、微妙に違います。が、「~」が「?」に化けてもあまり気にしない一般ユーザーにとってはその2つは呼び方が違うだけ、と理解されます。対象の文字セットが一つなら文字コードなんて一つだけで良いんですがね~。



C++ のWCHARは16ビットの文字でUTF-16を格納するんだー。と勝手に理解してましたが、自由奔放なC++でWCHARは16ビットでUTF-16 だったり32ビットでUTF-32だったりまったく違う文字コードを格納するやつだったりとコンパイラやOSで変わるようです。
2011年のC++ではchar16_tとchar32_tが導入され、ビット数だけでも確定できるようになったと、まぁWindowsでしか動かさないアプリ、VC++を使うならWCHARは16ビットでUTF-16、という理解でも問題ないでしょう。
あんまり知りませんがサロゲートペア文字を数えると一文字で「2」と出るのはVC++でも同じようです。



Unicodeは最初16ビット、65535文字で表そうとしたそうですが、常用漢字は2千字程度としても非常用を含めると1万近いんでしょうか?
「『大漢和辞典』には、この世のありとあらゆる漢字が収められていて、その数は約5万である」と言われてたらしいので65535文字はギリギリ間に合いそうな気がします。(漢字以外はそんなに文字数ないだろうと思います)
16ビットの頃のUnicode表を俯瞰するとほとんど漢字で埋め尽くされてるんでしょうか。その様子はちょっと見てみたい。
ちなみにその16ビットの頃のUnicode表(256 x 256の65535)をBMP領域と呼ぶそうです。ま、よく使われる文字、ってくらいのイメージですね。
残念なことにそれ系の研究が進むうちに漢字の数は5万どころではなくなったらしく現在では幾つあるのか分からないらしいです。
21ビットは200万文字くらい行けるので宇宙語が入ってきてもきっと大丈夫でしょう。



Unicodeは良い選択か?
Unicodeの知られざる世界
「草」という漢字は日本、中国、韓国で使われる一つの漢字であり、Unicodeでは一つにエンコードされている。が、文字の形はそれぞれの国でちょっとずつ違う(バランスとかかな?)。文字の形が違うなら違う数値にエンコードするべきじゃないか?という話。
日本語の文章中から中国語の文献を参照した文章で「草」の文字は同じフォントが使われてしまい、違和感を感じる、という話かな?日本語と中国語を同時に表示できることがそもそも素晴らしいことだけど。

文字コード、メンドクサイ
コード、っていうか中のバイト配列を考えた場合メンドクサイ

シングルバイトAsciiなら簡単。
あるいはUTF16で必ず2バイトで一文字なら簡単。

マルチバイト文字コードを処理するとして、例えば「0x4A」と「0x4A4A」は「4A,4A」という2文字なのか「4A4A」という1文字なのか区別できないもん。
結局上位バイトだけでシングルバイト文字か2バイト文字か区別できるように気を付けて割り当てることになる。

具体的にシフトJISでは2バイト文字の上位バイトは80~9F、E0~FFだけしか使えない、かなり狭い範囲になる(バイト配列を読込んでこの範囲の値が来たら2バイト文 字だ、として処理する)。2バイト文字、と言いつつほとんどマッピングされていないことになる。また下位バイトに使えるのは40~FFまでだがこの制限は別の理由かな?。

UTF-8は1バイト文字から4バイト文字まであるのでもっと複雑っぽい。
UTF16もサロゲートペア文字かどうかは同様に上位2バイトで区別する。

0 件のコメント:

コメントを投稿