Go言語で文字列のエンコーディングを理解!UTF-8の基本を初心者向けに解説
生徒
「Go言語で文字列を扱うとき、エンコーディングって何ですか?」
先生
「エンコーディングとは、文字をコンピュータが理解できる数字の形に変換するルールのことです。Go言語では基本的にUTF-8という方式を使っています。」
生徒
「UTF-8って聞いたことあります。具体的にはどんなものなんですか?」
先生
「UTF-8は、世界中のほとんどの文字を表現できる方式で、1文字が1〜4バイトで表されます。ASCII文字は1バイト、日本語などの文字は複数バイトで表現されます。」
生徒
「なるほど!Goで文字列を扱うときは、いつもUTF-8で保存されているんですね?」
先生
「その通りです。UTF-8を理解すると、文字列操作やバイトスライスの扱い方もより理解しやすくなります。」
1. Go言語の文字列はUTF-8で保存される
Go言語の文字列は、内部的にUTF-8で保存されます。UTF-8は可変長のエンコーディング方式で、英数字は1バイト、日本語などのマルチバイト文字は2〜4バイトで表現されます。この仕組みを理解すると、文字列の長さや文字単位の操作が重要であることがわかります。
2. 文字列の長さとバイト数の違い
UTF-8では、文字列の長さとバイト数は一致しないことがあります。英語の文字は1バイト、日本語の文字は3バイト程度になることが多いです。そのため、len()関数で取得できるのはバイト数で、文字数ではありません。
package main
import (
"fmt"
)
func main() {
str := "Go言語"
fmt.Println("バイト数:", len(str))
}
バイト数: 8
上記の例では、日本語2文字と英字1文字で構成されていますが、バイト数は8になります。これはUTF-8で日本語1文字が3バイトで表現されるためです。
3. 文字単位で扱うにはrune型を使う
文字単位で操作したい場合、Goではrune型を使います。runeはUnicodeコードポイントを表す型で、1文字ごとに扱うことができます。
package main
import (
"fmt"
)
func main() {
str := "Go言語"
for i, r := range str {
fmt.Printf("インデックス %d: %c\n", i, r)
}
}
インデックス 0: G
インデックス 1: o
インデックス 3: 言
インデックス 6: 語
インデックスがバイト単位で表示されることに注意してください。runeを使うと、文字単位で正確に処理できます。
4. バイトスライスとの関係
文字列をバイトスライスに変換すると、UTF-8でエンコードされたバイト列として扱えます。バイト単位での処理や、ファイルへの書き込み、ネットワーク通信などに便利です。
package main
import (
"fmt"
)
func main() {
str := "Go言語"
b := []byte(str)
fmt.Println(b)
}
[71 111 230 149 136 232 170 158]
英字は1バイト、日本語はUTF-8で3バイトずつに変換されていることがわかります。
5. ポイント整理
Go言語では文字列はUTF-8で保存され、len()で取得できるのはバイト数です。文字単位で操作する場合はrune型を使い、バイト単位で処理する場合は[]byte型を使用します。UTF-8の仕組みを理解すると、日本語や特殊文字を含む文字列の操作も安全に行えます。
6. 文字列のインデックスアクセスで注意する点
Go言語の文字列はUTF-8で保存されているため、インデックスを使って直接アクセスする場合は注意が必要です。str[0]のような書き方で取得できるのは「文字」ではなく「バイト」です。英字であれば問題ありませんが、日本語などのマルチバイト文字では、意図しない値になることがあります。
package main
import "fmt"
func main() {
str := "Go言語"
fmt.Println(str[0])
}
71
この出力結果は文字ではなく、UTF-8でエンコードされたバイト値です。文字として扱いたい場合は、インデックスアクセスではなくruneやfor rangeを使うのが安全です。
7. 文字列をスライスするときの考え方
Go言語では、文字列もスライスのように一部を切り出すことができます。ただし、この操作もバイト単位で行われるため、日本語を含む文字列では注意が必要です。途中のバイトで切り出すと、文字が壊れてしまう可能性があります。
package main
import "fmt"
func main() {
str := "Go言語"
fmt.Println(str[:2])
}
Go
この例では英字部分だけを切り出しているため問題ありませんが、日本語部分を含めてスライスすると正しく表示されないことがあります。文字単位で安全に切り出したい場合は、一度[]runeに変換してから処理する方法がよく使われます。
8. UTF-8を意識することで防げるトラブル
UTF-8の仕組みを理解していないと、「文字数制限が合わない」「入力チェックでエラーになる」「画面表示が崩れる」といったトラブルが起きやすくなります。特に日本語を扱うWebアプリケーションや業務システムでは、文字数とバイト数の違いを意識することが重要です。
Go言語では、文字列・rune・[]byteという3つの視点を使い分けることで、こうした問題を未然に防ぐことができます。最初は少し難しく感じるかもしれませんが、UTF-8を前提とした考え方に慣れることで、文字列処理への理解が一段と深まります。
まとめ
Go言語の文字列とUTF-8エンコーディングを振り返る
この記事では、Go言語における文字列のエンコーディングの基本として、UTF-8の考え方を中心に解説してきました。 Go言語の文字列は、内部的にすべてUTF-8で保存されており、この前提を理解することが文字列処理の第一歩になります。 UTF-8は世界中の文字を扱える柔軟なエンコーディング方式で、英数字は1バイト、日本語や記号などは複数バイトで表現されます。 この仕組みを知らないまま文字列を操作すると、「文字数が合わない」「途中で文字が壊れる」といった問題に直面しやすくなります。
特に重要なのは、Go言語のlen()関数が返す値は「文字数」ではなく「バイト数」であるという点です。
英語だけの文字列であれば、文字数とバイト数は同じになるため違和感がありませんが、
日本語や全角文字を含む場合は、見た目の文字数とlen()の結果が大きく異なります。
そのため、画面表示や文字数制限、入力チェックなどを行う場面では、
「今扱っているのはバイトなのか、それとも文字なのか」を常に意識することが大切です。
runeとbyteを正しく使い分ける考え方
Go言語では、文字単位で扱いたい場合にrune型を使います。
runeはUnicodeのコードポイントを表す型で、1文字ずつ安全に処理できるのが特徴です。
for range構文で文字列をループすると、自動的にrune単位で処理されるため、
日本語を含む文字列でも安心して1文字ずつ取り出せます。
一方で、インデックスがバイト単位で進む点は、初学者が混乱しやすいポイントでもあります。
反対に、ファイル操作やネットワーク通信、バイナリデータの処理などでは、
文字ではなくバイト単位での扱いが必要になります。
その場合は、文字列を[]byteに変換して処理するのが一般的です。
Go言語では文字列とバイトスライスの相互変換が簡単にできるため、
「文字として扱うのか」「データとして扱うのか」という目的に応じて型を選ぶことが重要になります。
振り返り用サンプルプログラム
ここで、UTF-8と文字列の扱いをもう一度整理するために、 バイト数と文字単位の違いが分かるシンプルなサンプルプログラムを見てみましょう。
package main
import (
"fmt"
)
func main() {
str := "Go言語"
fmt.Println("バイト数:", len(str))
count := 0
for range str {
count++
}
fmt.Println("文字数:", count)
}
この例では、len()で取得したバイト数と、
for rangeを使って数えた文字数が異なることを確認できます。
実際の開発では、この違いを理解しているかどうかが、
文字列処理の正確さに大きく影響します。
生徒
「lenで取れるのが文字数じゃなくてバイト数だっていうのは、 正直かなり意外でした。」
先生
「多くの人が最初につまずくポイントですね。 でもUTF-8の仕組みを知っていれば、自然な仕様だと分かります。」
生徒
「文字単位で扱いたいときはrune、 データとして扱いたいときはbyteという使い分けが大事なんですね。」
先生
「その理解で完璧です。 UTF-8とGo言語の文字列の特徴を押さえておけば、 日本語を含むプログラムでも安心して開発できますよ。」