こんにちは。 新規プロダクト開発に携わっているエンジニアの島田です。
私は社会人大学生として放送大学で情報工学の勉強をしておりまして、直近受けた授業では、データ構造やアルゴリズム、データの符号化といったものが扱われていました。中でも文字列については普段から業務で扱っているのもあって非常に興味深かったです。
せっかく大学で体系的に学びましたので、文字列の扱いについての振り返りも兼ねて、とりわけ Web アプリケーション上で扱う上で個人的に気になった点にフォーカスし、この記事にまとめたいと思います。
コンピューターはどのように文字を扱うのか
世の中で使われている文字や数値、記号は、それぞれ所定の非負整数値と対応付けられています。この値を所定のスキームに基づいて符号化し、データ列として扱えるようになったものがコンピューター上で扱われます。
Unicode
各文字等がどの非負整数値 (コードポイント) と対応付けられるのかについては、Unicode という標準規格に定められています。Unicode は世の中にある全ての文字を扱う目的で定められており、対応付けられる値は 0 から 10FFFF の16進数で表されます。例えば N
という文字は U+004E と対応付けられています (U+ は Unicode コードポイントであることを表す接頭辞です)。
合計で約111万の値を扱えるようになっており、これだけの数であればあらゆる文字を格納出来ると考えられています。
文字符号化
世界中のあらゆる文字を効率的に表現し、将来の拡張にも対応できるよう、Unicode は各文字に固有のコードポイントを割り当て、これを最大21ビットの範囲で表現します。コードポイントを実際にコンピューターが扱える最大4バイトのデータ列に変換することを文字符号化と言います。
Unicode の符号化には UTF-8、UTF-16、UTF-32 の3つの形式があります。それぞれにある数字は符号単位を表していて、例えば UTF-8 の符号単位は8ビット (1バイト) となります。ASCII 文字のような1バイトで表せる文字は1符号単位で扱われ、そうでないものは最大で4符号単位で扱われるので、UTF-8 で扱われる文字のサイズは1\~4バイトのいずれかになります。UTF-16は2バイトまたは4バイトで文字を表現します。UTF-32は常に4バイトで文字を表現します。それぞれの形式には長所もあれば短所もあるので、用途や対象とするデータ、システムの要件に応じて適切に使い分けることが求められます。
各言語の比較
Web アプリケーション開発に使われるいくつかの言語で、文字がどのように扱われるのかを見ていきます。尚、各言語は筆者にとって親しみ深いものを選んでいます。
Go
文字表現には rune という型が使われており、これは int32 の値のエイリアスです。4バイトの情報を扱えるため、Unicode コードポイントである 0 から 0x10FFFF の範囲の値をそのまま保持することができます。
一方で文字列を表現する string 型は、ASCII 文字や多くの文字を扱う際にメモリ効率が良くなる、UTF-8 でエンコードされたバイトのスライス (可変長配列) を内部的に保持しています。string 値を一文字ずつ走査して rune 型の文字として扱う際は、バイト列から rune 値への変換が必要となります。
Java
文字表現に使われる char 型は2バイトの情報を扱うことができ、UTF-16 の符号単位を表しています。2バイトを超える情報量の文字を表す際には、サロゲートペアという仕組みを使って符号単位2つで表現します。文字列型 (String) は内部的に char の配列として実装されていますので、文字列の走査で char の各要素を見ていくと、サロゲートペアで表現される文字、例えば多くの絵文字などは適切に表現されないといったことが起こるので注意が必要です。
TypeScript (JavaScript)
明示的な文字型は無く、単一の文字も含め文字は string 型の値として扱われます。内部的には2バイトの符号単位の配列として扱われているため、Java と同じく、文字によってはその表現のためにサロゲートペアが必要となり、個々の文字へアクセスする際にはそれに対しての考慮が必要となります。
尚、TypeScript は基本的にJavaScriptの型システムを拡張したものですので、文字表現の基本的な部分は JavaScript と同じです。
合字の絵文字
ここまで、4バイト以下で表現される文字について話をしてきましたが、絵文字についてはこれに当てはまらないものもあります。Zero Width Joiner (ZWJ) という制御文字を使って複数の絵文字を結合して作られたもの (合字) がそれにあたります。詳しくは参考文献を御覧ください。
おわりに
調べてみたら面白く、結構な文量になってしまいました。文字の扱いは各言語やデータベースといったエコシステムの発展により、仕事ではそんなに向き合わなくて良くなってきたように思います。しかしながら、いざ問題が発生して対応を試みてみると、その内部構造の複雑さに面食らってしまうかもしれないので、余裕があるときに参考文献を読むなどして知識を蓄えておくと良いのではないかと思いました。
この記事が誰かの役に立つことを願っています。
参考文献
- The Unicode Standard, Version 11.0: https://www.unicode.org/versions/Unicode11.0.0/UnicodeStandard-11.0.pdf
- IT 用語辞典: ASCII 【American Standard Code for Information Interchange】 アスキー: https://e-words.jp/w/ASCII.html
- Go Packages: builtin package: https://pkg.go.dev/builtin
- JavaScript Primer: 文字列とUnicode: https://jsprimer.net/basic/string-unicode/
- Java(tm) Platform, Standard Edition 8 API仕様: クラスString: https://docs.oracle.com/javase/jp/8/docs/api/java/lang/String.html
- 絵文字👨🏻🦱は何文字としてカウントする?関連する文字コードの仕様を詳しく調べてみた: https://qiita.com/comware_harase/items/59c60ab1c6e1797f0821