Как Golang понимает где какая руна в строке, если у рун может быть разное количество байт?
Go понимает, где заканчивается каждая руна в строке, благодаря тому, что строки в Go представляют собой последовательности байт в кодировке UTF-8. UTF-8 — это переменная длина кодировки, где каждому символу (или кодовой точке Unicode) может соответствовать от 1 до 4 байтов.
Для корректной обработки строк, Go использует принцип, при котором каждую руну можно однозначно определить по начальному байту, а длина руны определяется на основе первых байтов.
Как Go понимает, где заканчивается каждая руна в строке:
Go использует алгоритм, который позволяет понять, где заканчивается каждая руна, исходя из начальных байтов в UTF-8-строке:
- UTF-8 структура байтов:
- Один байт: символы с кодами от U+0000 до U+007F (ASCII-символы).
- Два байта: символы с кодами от U+0080 до U+07FF.
- Три байта: символы с кодами от U+0800 до U+FFFF.
- Четыре байта: символы с кодами от U+10000 до U+10FFFF.
- Как Go определяет, где заканчивается руна:
- Каждый символ в UTF-8 начинается с определённой маски, которая указывает, сколько байт он занимает.
- Для 1 байта (0xxxxxxx): это обычный ASCII символ.
- Для 2 байтов (110xxxxx 10xxxxxx): маски показывают, что руна начинается с первого байта, который начинается с
110
, и следующий байт будет начинаться с10
. - Для 3 байтов (1110xxxx 10xxxxxx 10xxxxxx): аналогично для символов, которые требуют 3 байта.
- Для 4 байтов (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx): для символов, которые требуют 4 байта.
- Каждый символ в UTF-8 начинается с определённой маски, которая указывает, сколько байт он занимает.
- Алгоритм: Go использует эти маски, чтобы распознать, когда заканчивается руна и перейти к следующей. Например, если первый байт начинается с
0xxx xxxx
, то это один байт, если с110x xxxx
, то это начало двухбайтового символа, и так далее.
Пример на Go: как Go обрабатывает строки с разными рунными длинами
Когда вы используете range
для итерации по строке в Go, он автоматически распознаёт, где заканчивается каждая руна, и правильно её извлекает, независимо от количества байтов.
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main import "fmt" func main() { s := "Hello, 世界" // Строка с ASCII и Unicode символами (2 байта для китайских символов) // Итерация по строке как по рунному срезу for i, r := range s { fmt.Printf("Index: %d, Rune: %c, Code Point: %U\n", i, r, r) } } |
Вывод:
1 2 3 4 5 6 7 8 9 10 |
Index: 0, Rune: H, Code Point: U+0048 Index: 1, Rune: e, Code Point: U+0065 Index: 2, Rune: l, Code Point: U+006C Index: 3, Rune: l, Code Point: U+006C Index: 4, Rune: o, Code Point: U+006F Index: 5, Rune: ,, Code Point: U+002C Index: 6, Rune: , Code Point: U+0020 Index: 7, Rune: 世, Code Point: U+4E16 Index: 10, Rune: 界, Code Point: U+754C |
Здесь Go корректно понимает, что символы 世
и 界
занимают по 3 байта (UTF-8), а остальная строка состоит из обычных ASCII-символов, которые занимают по 1 байту.
Важность масок для понимания разной длины байтов
Когда Go читает строку, он использует первую часть каждого байта для того, чтобы понять, сколько байтов требуется для символа. Например:
- Если первый байт строки начинается с
0xxxxxxx
, то это один байт. - Если начинается с
110xxxxx
, то это начало двухбайтового символа. - Если с
1110xxxx
, то это начало трёхбайтового символа, и так далее.
Это позволяет Go правильно обрабатывать строки, состоящие как из однобайтовых символов (например, ASCII), так и из многобайтовых символов Unicode.
Итерация через range
Когда мы используем конструкцию range
для итерации по строкам в Go, каждый символ в строке автоматически трактуется как руна, независимо от того, сколько байт она занимает. range
будет правильно «пропускать» нужное количество байтов для каждого символа и переходить к следующему.
Пример с различными длинами рун:
1 2 3 4 5 6 7 8 9 10 11 12 |
package main import "fmt" func main() { s := "A世B界" // Строка: один ASCII символ (A), два китайских символа (по 3 байта каждый), и один ASCII символ (B) for i, r := range s { fmt.Printf("Index: %d, Rune: %c, Code Point: %U\n", i, r, r) } } |
1 2 3 4 5 |
Index: 0, Rune: A, Code Point: U+0041 Index: 1, Rune: 世, Code Point: U+4E16 Index: 4, Rune: B, Code Point: U+0042 Index: 5, Rune: 界, Code Point: U+754C |
Заключение
Go использует UTF-8 для строк, где символы могут иметь разную длину в байтах. Когда вы итерируете по строке с помощью range
, Go использует маски в байтах, чтобы правильно идентифицировать, где заканчивается одна руна и начинается следующая. Это позволяет Go эффективно работать с многобайтовыми символами и кодовыми точками Unicode.
Recommended Posts
Что такое oneof в Protobuf?
25.04.2024