関連記事
GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ
概要
以下、自分用のメモです。
特にGo限定の話でもなんでもないのですが、サンプルをGoで作ったのでここに。
UTF-8の文字は、1バイトから4バイトまでの可変長でエンコードされます。
Goでは、utf8.RuneLen()
とかを使うとサイズが分かりますが、C言語とかだと自力でやる必要があります。
UTF-8では、先頭バイトを見ることで、その文字が何バイトでエンコードされているかが分かります。
ソースとコメントでご覧になったほうが分かりやすいと思いますので、以下に画像で貼り付け。
上記の理屈は、どの言語でも同じ。
ちなみに、utf8.RuneLen()
の実装の方は、先頭バイトのビット状態ではなくUnicode コードポイントの上限の値を判定条件にして何バイトか判定していますね。
https://cs.opensource.google/go/go/+/refs/tags/go1.21.1:src/unicode/utf8/utf8.go;l=321
サンプル
package main import ( "flag" "fmt" "unicode/utf8" "github.com/devlights/gomy/output" ) func main() { var ( u = flag.Bool("u", false, "use rune") ) flag.Parse() if err := run(*u); err != nil { panic(err) } } func run(runeMode bool) error { var ( strs = []string{ // 全角かな (3bytes) "こんにちは", // 全角カタカナ (3bytes) "コンニチハ", // 半角カタカナ (3bytes) "コンニチハ", // 英数字記号 (1byte) "golang->60l4n6", // ©¼½¾ (2bytes) "\U000000A9\U000000BC\U000000BD\U000000BE", // 🍺🍻🍷🍜 (4bytes) "\U0001F37A\U0001F37B\U0001F377\U0001F35C", } fn = manual ) if runeMode { fn = useRune } for _, v := range strs { output.Stdoutf("", "[%s]", v) output.StdoutHr() if err := fn(v); err != nil { return err } } return nil } func manual(s string) error { for i := 0; i < len(s); { var ( b = s[i] l = 0 ) // // UTF-8の先頭バイトを判定し、バイトサイズ算出 // // UTF-8エンコーディングでは、各文字は1バイトから4バイトまでの可変長でエンコードされる。 // 先頭バイト(最初のバイト)を見ることで、その文字が何バイトでエンコードされているかを判定できる。 // // - 0xxxxxxx: 1バイト(ASCIIと互換性あり) // - 110xxxxx: 続く1バイトと合わせて2バイト // - 1110xxxx: 続く2バイトと合わせて3バイト // - 11110xxx: 続く3バイトと合わせて4バイト // // 以下の case は上記を判定している. // // - (b & 0x80) == 0 : 最上位1ビットが0 であるなら、この文字は1バイト // - (b & 0xE0) == 0xC0: 最上位2ビットが110 であるなら、この文字は2バイト // - (b & 0xF0) == 0xE0: 最上位3ビットが1110 であるなら、この文字は3バイト // - (b & 0xF8) == 0xF0: 最上位4ビットが11110であるなら、この文字は4バイト // // REFERENCES: // - https://ja.wikipedia.org/wiki/UTF-8 // switch { case (b & 0x80) == 0: l = 1 case (b & 0xE0) == 0xC0: l = 2 case (b & 0xF0) == 0xE0: l = 3 case (b & 0xF8) == 0xF0: l = 4 default: return fmt.Errorf("invalid utf-8 char (%b)", b) } output.Stdoutf("[byte-count]", "%s (%d)\n", s[i:i+l], l) i += l } return nil } func useRune(s string) error { for _, r := range s { l := utf8.RuneLen(r) if l == -1 { return fmt.Errorf("invalid utf-8 char (%c)", r) } output.Stdoutf("[byte-count]", "%c (%d)\n", r, l) } return nil }
タスクファイルは以下。
# https://taskfile.dev version: '3' tasks: default: cmds: - task: run-manual run-manual: cmds: - go run main.go run-userune: cmds: - go run main.go -u diff: cmds: - go run main.go > manual.txt - go run main.go -u > userune.txt - diff manual.txt userune.txt ignore_error: true clean: cmds: - rm -f ./*.txt
実行すると以下のようになります。
$ task task: [run-manual] go run main.go [こんにちは]-------------------------------------------------- [byte-count] こ (3) [byte-count] ん (3) [byte-count] に (3) [byte-count] ち (3) [byte-count] は (3) [コンニチハ]-------------------------------------------------- [byte-count] コ (3) [byte-count] ン (3) [byte-count] ニ (3) [byte-count] チ (3) [byte-count] ハ (3) [コンニチハ]-------------------------------------------------- [byte-count] コ (3) [byte-count] ン (3) [byte-count] ニ (3) [byte-count] チ (3) [byte-count] ハ (3) [golang->60l4n6]-------------------------------------------------- [byte-count] g (1) [byte-count] o (1) [byte-count] l (1) [byte-count] a (1) [byte-count] n (1) [byte-count] g (1) [byte-count] - (1) [byte-count] > (1) [byte-count] 6 (1) [byte-count] 0 (1) [byte-count] l (1) [byte-count] 4 (1) [byte-count] n (1) [byte-count] 6 (1) [©¼½¾]-------------------------------------------------- [byte-count] © (2) [byte-count] ¼ (2) [byte-count] ½ (2) [byte-count] ¾ (2) [🍺🍻🍷🍜]-------------------------------------------------- [byte-count] 🍺 (4) [byte-count] 🍻 (4) [byte-count] 🍷 (4) [byte-count] 🍜 (4) $ task diff task: [diff] go run main.go > manual.txt task: [diff] go run main.go -u > userune.txt task: [diff] diff manual.txt userune.txt # # 何も出力されない。つまり utf8.RuneLen を使った場合は同じ結果が出力出来ている #
リポジトリ
参考情報
Goのおすすめ書籍
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。