内部ではUTF-32かUTF-16か

寝正月延長戦みたいになってはよくないと思い、東京までおでかけ。

東京国立近代美術館に行きコレクション展を見てきたが、

珍しくも特別展の一部がコレクション展のフロアに含まれていた。

特に検札係もいなさそうだったから、ここだけ見られそうだったが、それはせず。

京都ではよくあるんですけどね。東京では初めて見たかな。


最近、プライベートで実験目的でPythonをちょこちょこ触っている。

PerlでもRubyでもPythonでもできることはあまり変わらないが、

最近はPythonが無難で使いやすいと考えてこうしている。

習熟度で言えばPerlが一番高いけど、ライブラリとか総合的に考えればPythonかなと。


ふと、Pythonの文字列が内部的にどう扱われているかが気になった。

Unicodeということは知っているのだが……

調べたところ、現在はUTF-32を使っているようである。

sys.maxunicode (import sysが必要)で文字コードの最大値が確認出来るのだが、

これが1114111(0x10FFFF)になっている場合は、内部的にはUTF-32を使っている。

一部の環境ではUCS-2を使っていることがあり、その場合は65535(0xFFFF)となる。


現在は国際的な統一文字コードであるUnicodeが広く使われている。

Unicode自体は「田」に0x7530のようなコードを振ったもので、

そのUnicodeの文字列をどのように表現するかというのにはいろいろな方法がある。

しかし、文字列データを保存・伝送する目的ではほぼUTF-8しか使われていない。

Unicode=UTF-8と考えている人もけっこういそうですけどね。

UTF-8はUnicodeの1文字を1~4バイトのデータとして表現するもので、

ASCIIの範囲の文字であれば1バイトでASCIIコードと同じように表現され、

それ以外の文字はASCIIコードと被らない2~4バイトのデータで表現される。

2~4バイトとはいうが、通常の日本語の文字は3バイトである。


一方、ソフトウェアの内部処理では可変長のコードでは不都合なので、UTF-16またはUTF-32が使われることが多いという。

UTF-16は16bitデータとしてUnicodeの1文字を格納するもので、UTF-32は32bitデータとしてUnicodeの1文字を格納するものである。

しかし、現在はUnicodeの文字コードの最大値は 0x10FFFF となっている。

このためUTF-16は全てのUnicode文字を16bitで格納することはできない。

Unicodeの当初構想では65536種類の文字で全世界の文字を網羅しようと考えていて、16bitのUTF-16が使われているのはその時代の名残らしい。

UTF-16で0x10000以降の文字を表現する場合は、サロゲートペアという機能を使う。

0x10000以降の1文字を16bitデータ2つで表現するんですね。

固定長だからUTF-16を使ったはずなのに、サロゲートペアがあるために可変長になってしまった。

UTF-32を使用する場合はこのような問題はなく、必ず固定長である。


ちなみに一部環境のPythonではUCS-2を使っていることがあると書いたが、

UCS-2はUTF-16と同じで16bitデータとしてUnicodeの1文字を格納するものである。

しかし文字コードの最大値は0xFFFFに制限されている。

じゃあ、UCS-2環境のPythonではUnicodeで0x10000以降のコードになる文字は使えないのか?

実際にはサロゲートペアとして処理できる場合もあるらしい。


UTF-16を使っているプラットフォームとしては.NET Frameworkが有名である。

というかWindows自体がUTF-16を多用していて、それに合わせてるんですよね。

.NET FrameworkというとC#のプログラムで使われていることが知られているが、

Windows PowerShellも.NETを使っているので、そちらで実験。

Unicodeで0x10000以降の文字と言えば、使用頻度の低い漢字などもあるが、

やはりなんといっても絵文字でしょう。例えば0x1F44Dは「👍」である。

絵文字1文字を格納した文字列の長さを出すと「2」と出てきた。

{0xD83D,0xDC4D}という16bitデータ2つで1文字が表現されていることが確認できるが、これこそがサロゲートペアである。


用途にもよるが、そこまでサロゲートペアを意識する必要はない。

フォームに入力された文字列をファイルに保存する場合に、

0x10000以降の文字が入力されたら、1文字が2文字相当で格納されるが、

しかしそれをファイルに保存するときにはUTF-8などに変換するわけである。

これらの変換ではサロゲートペアのことは自動で処理される。

ただ、1字ずつ文字コードを取得しようと考えると、サロゲートペアを意識する必要がある。

文字列の1文字ずつを整数変換する方法ではサロゲートペアが処理されない。

System.Char.ConvertToUtf32関数で文字列中の指定位置でサロゲートペアを処理して文字コードを取得できる。

そしてサロゲートペアを使う0x10000以上の場合は、位置を2文字進める処理が必要であろう。


.NET FrameworkにしてもWindowsにしても過去のプログラムのバイナリは変えられないので、

UTF-16は固定長の文字コードではないという問題があるのは知っていても、互換性のために変えられないということなんだろうなと。

一方でPythonの場合、ソースコードを実行時に解釈するわけで、

ソースコードに記載された“ABC”という文字列が、実行時に{0x41, 0x42, 0x43}となっても、{0x0041, 0x0042, 0x0043}となっても、{0x00000041, 0x00000042, 0x00000043}となっても、

3文字のデータであること、格納されている値が0x41~0x43であることは同じである。

内部的な取扱が違ったとしても、結果的にその差を意識しない場合はある。

だからPythonではUTF-32が一般的に使われるようになったと言えるのでは?