Ein Go-String ist eine Folge von Bytes, und Go-Quelltext und String-Literale sind UTF-8. Ein byte ist eins dieser Bytes. Eine rune ist ein Unicode-Zeichen (ein Codepoint), das UTF-8 in 1 bis 4 Bytes speichert. Die meiste Verwirrung um Strings in Go entsteht, wenn man die beiden verwechselt:
語 belegt in UTF-8 drei Bytes, also hat "語Go" 5 Bytes, aber 3 Runes. Die Indexschleife sieht fünf Bytes, und die Bytes 0, 1 und 2 sind für sich allein bedeutungslos. Die range-Schleife dekodiert UTF-8 und liefert drei Runes an den Indizes 0, 3 und 4: i ist immer ein Byte-Offset, keine Zeichenanzahl.
byte und rune sind Aliase
| Name | Gleicher Typ wie | Literal | Bedeutung |
|---|---|---|---|
byte | uint8 | byte('A'), 0x41 | ein Byte Daten |
rune | int32 | 'A', 'é', '世' | ein Unicode-Codepoint |
Da es Aliase sind, ist ein byte ein uint8 und eine rune ein int32; zwischen Alias und zugrunde liegendem Typ ist keine Konvertierung nötig. Einfache Anführungszeichen ergeben eine Rune, doppelte einen String. 'ab' ist ein Compilerfehler, da ein Rune-Literal genau ein Zeichen enthält.
Runes sind Zahlen, also kannst du mit ihnen rechnen:
'7' - '0' ist der klassische Weg, ein Ziffernzeichen in seinen Wert umzuwandeln. Das Paket unicode klassifiziert Runes (IsLetter, IsDigit, IsSpace, IsUpper) und ändert die Groß- und Kleinschreibung auch bei Nicht-ASCII-Buchstaben korrekt. %c gibt eine Rune als Zeichen aus, %U in der Form U+4E16, %q als Rune-Literal in Anführungszeichen.
Wie UTF-8 Zeichen speichert
| Codepoints | Bytes | Beispiele |
|---|---|---|
| U+0000 bis U+007F | 1 | ASCII: a, 7, { |
| U+0080 bis U+07FF | 2 | é, ß, ж, ع |
| U+0800 bis U+FFFF | 3 | 語, €, 한 |
| U+10000 und höher | 4 | Emoji, seltenes CJK |
ASCII-Text ist in UTF-8 identisch. Deshalb funktioniert bytebasierter Code im Test oft und bricht beim ersten Namen mit Akzent. Das Paket unicode/utf8 arbeitet direkt mit der Kodierung:
% x (mit Leerzeichen) gibt Bytes als Hex aus, getrennt durch Leerzeichen. utf8.ValidString meldet, ob ein String gültiges UTF-8 ist. Go-Strings dürfen beliebige Bytes enthalten, nicht nur gültiges UTF-8; trifft range auf ein ungültiges Byte, liefert es utf8.RuneError (U+FFFD, das Ersatzzeichen) und rückt ein Byte weiter.
Zwischen string, []byte und []rune konvertieren
| Konvertierung | Ergebnis | Kosten |
|---|---|---|
[]byte(s) | die UTF-8-Bytes | kopiert |
[]rune(s) | dekodierte Codepoints | dekodiert und kopiert (4 Bytes pro Rune) |
string(b) | ein String aus Bytes | kopiert |
string(r) mit r als []rune | kodiert in UTF-8 | kopiert |
string(r) mit r als rune | ein String aus einem Zeichen | gering |
Ein Umdrehen nach Bytes würde 世界 zu ungültigem UTF-8 verwürfeln, deshalb arbeitet reverse auf Runes. Und selbst Runes sind nicht die ganze Wahrheit: Ein Zeichen wie é kann auch als e plus kombinierender Akzent geschrieben werden, also als zwei Runes. Für sichtbare Zeichen (Graphem-Cluster) nimmst du eine Bibliothek wie github.com/rivo/uniseg.
Jede Konvertierung kopiert, weil Strings unveränderlich sind und Slices nicht. In heißem Code solltest du nicht ständig hin und her konvertieren; der Compiler optimiert manche Fälle (etwa string(b) als Map-Schlüssel oder in einem Vergleich) und spart sich die Kopie.
Indizieren liefert ein Byte
s[i] gibt ein byte zurück, und s[i:j] schneidet nach Byte-Offsets:
s := "café"
fmt.Println(s[3]) // 195: the first byte of é
fmt.Println(string(s[3])) // "Ã": that byte read as a code point
fmt.Println(s[:3]) // "caf"
fmt.Println(s[:4]) // "caf\xc3": half of é, invalid UTF-8
string(s[3]) ist eine Falle: Die Konvertierung eines einzelnen Bytes in string behandelt es als Codepoint 195, also Ã, nicht als das Byte selbst. Zeichengrenzen findest du mit range, mit der strings.Index-Familie (die Byte-Offsets liefert, die immer auf Grenzen liegen) oder mit utf8.DecodeRuneInString.
Das Paket bytes
bytes spiegelt strings für []byte: bytes.Contains, bytes.Split, bytes.Fields, bytes.TrimSpace, bytes.Equal, bytes.ToUpper und so weiter. Nutz es, wenn Daten als Bytes ankommen (Dateiinhalte, HTTP-Bodies, Netzwerkpuffer), damit du nicht in einen String und zurück konvertierst:
bytes.Buffer ist ein wachsender Byte-Puffer, der io.Reader und io.Writer implementiert. Er ist das richtige Werkzeug, wenn du Bytes sowohl schreibst als auch liest; zum Bauen eines Strings ist strings.Builder etwas billiger. Vergleiche Byte-Slices mit bytes.Equal, da == auf Slices nicht funktioniert.
Die richtige Wahl
- Text, den du anzeigst, vergleichst oder durchsuchst: Lass ihn ein
stringund nimmrange, wenn du ihn Zeichen für Zeichen durchgehst. - Zeichenpositionen, Umdrehen, Kürzen auf n Zeichen: in
[]runekonvertieren. - I/O, Hashing, Kodierung, binäre Protokolle:
[]byteund das Paketbytes. - Ein einzelnes Zeichen: eine
rune. Ein einzelnes Rohbyte: einbyte.
Häufig gestellte Fragen
Was ist eine Rune in Go?
rune ist ein Alias für int32 und steht für einen Unicode-Codepoint. Ein Rune-Literal steht in einfachen Anführungszeichen: 'a' ist 97, 'é' ist 233, '世' ist 19990. Wenn du mit for i, r := range s über einen String iterierst, bekommst du Runes, dekodiert aus den UTF-8-Bytes des Strings.
Was ist der Unterschied zwischen einer Rune und einem Byte in Go?
Ein byte (Alias für uint8) sind 8 Bit Rohdaten. Eine rune (Alias für int32) ist ein ganzes Unicode-Zeichen. In einem UTF-8-String belegen ASCII-Zeichen je ein Byte, andere Zeichen zwei bis vier Bytes, sind aber trotzdem eine Rune. len(s) zählt Bytes; utf8.RuneCountInString(s) zählt Runes.
Wie durchlaufe ich die Zeichen eines Strings in Go?
Mit for i, r := range s. Jeder Durchlauf liefert den Byte-Offset i und die Rune r, also werden Zeichen aus mehreren Bytes korrekt behandelt. Eine einfache Indexschleife for i := 0; i < len(s); i++ besucht Bytes und zerlegt Nicht-ASCII-Zeichen in Stücke.
Wie konvertiere ich einen String in Go in einen Byte-Slice?
b := []byte(s) kopiert die Bytes des Strings in einen neuen Slice. string(b) konvertiert zurück, wieder mit einer Kopie. Nimm stattdessen []rune(s), wenn du mit Zeichen arbeiten musst, etwa um einen String umzudrehen oder seine ersten n Zeichen zu nehmen.