Научитесь играть быстро. Урок 7. Струны.

задняя часть Go переводчик Unicode
Научитесь играть быстро. Урок 7. Струны.

Строки обычно имеют два дизайна: одна представляет собой «символьную» строку, а другая — «байтовую». Каждое слово в «символьной» строке имеет фиксированную длину, а каждое слово в «байтовой» строке имеет переменную длину. Строки в языке Go являются «байтовыми» строками, английские символы занимают 1 байт, а неанглийские символы занимают несколько байтов. Это означает, что полный персонаж не может быть быстро расположен по положению, а отдельные символы должны быть получены один за другим путем обхода.

Символы, о которых мы говорим, обычно относятся к символам юникода. Можно подумать, что все английские и китайские иероглифы имеют уникальное целое число в наборе символов юникода. Юникод обычно представлен 4 байтами, что соответствует символьной руне в Go язык Занимает 4 байта. В исходном коде языка Go можно найти следующую строку кода: Тип rune является производным типом, который использует 4 байта типа int32 для хранения в памяти.

type rune int32

Использование «символьной» строки для представления строки связано с пустой тратой места, потому что всем английским символам изначально требуется только 1 байт для представления, а если используется рунический символ, оставшиеся 3 байта равны нулю. Но «символьная» строка имеет то преимущество, что ее можно быстро найти.

Для того, чтобы еще больше облегчить читателям понимание взаимосвязи между байтом и символьной руной, я сделал следующую картинку

где кодовая точка - это фактическое смещение каждого «слова». Строки в языке Go кодируются в utf8, китайские символы обычно занимают 3 байта, а английские — только 1 байт. Функция len() получает количество байтов, а доступ к строке путем индексации получает «байты».

обход по байтам

Строка может получить доступ к байту в определенной позиции внутреннего массива байтов путем индексации, а байт имеет тип byte.

package main

import "fmt"

func main() {
	var s = "嘻哈china"
	for i:=0;i<len(s);i++ {
		fmt.Printf("%x ", s[i])
	}
 
}

-----------
e5 98 bb e5 93 88 63 68 69 6e 61

Перемещение по руне персонажа

package main

import "fmt"

func main() {
	var s = "嘻哈china"
	for codepoint, runeValue := range s {
		fmt.Printf("%d %d ", codepoint, int32(runeValue))
	}
}

-----------
0 22075 3 21704 6 99 7 104 8 105 9 110 10 97

Выполните обход диапазона в строке, и каждая итерация создает две переменные codepoint и runeValue. codepoint представляет начальную позицию символа, а runeValue представляет соответствующую кодировку Unicode (тип — руна).

представление строки байтов в памяти

Если строка представляет собой просто массив байтов, как получить информацию о длине строки? Если строки являются литералами, длину можно вычислить во время компиляции, но если строки создаются во время выполнения, как получается длина?

var s1 = "hello" // 静态字面量
var s2 = ""
for i:=0;i<10;i++ {
  s2 += s1 // 动态构造
}
fmt.Println(len(s1))
fmt.Println(len(s2))

Чтобы объяснить это, необходимо понять структуру памяти строки, это не просто массив байтов, упомянутый ранее, компилятор также выделяет поле заголовка для хранения информации о длине и указатель на базовый массив байтов, как показано на рисунке ниже структура очень похожа на срез, разница в том, что в заголовке отсутствует поле емкости.

Когда мы присваиваем строковую переменную другой строковой переменной, базовый массив байтов является общим, он просто копирует поле заголовка.

Строки доступны только для чтения

Вы можете использовать индексы для чтения байта в указанной позиции в строке, но вы не можете изменить содержимое байта в этой позиции. Если вы попытаетесь использовать присваивание индекса, компилятор просто отклонит вас синтаксически.

package main

func main() {
	var s = "hello"
	s[0] = 'H'
}
--------
./main.go:5:7: cannot assign to s[0]

резать резать

Строки ближе к слайсам по форме памяти, и их тоже можно нарезать как слайсы, чтобы получить подстроки. Подстрока и родительская строка совместно используют базовый массив байтов.

package main

import "fmt"

func main() {
	var s1 = "hello world"
	var s2 = s1[3:8]
	fmt.Println(s2)
}

-------
lo wo

Преобразование фрагментов байтов в строки

При использовании языка Go для сетевого программирования часто необходимо преобразовать поток байтов из сети в строку памяти, а также преобразовать строку памяти в сетевой поток байтов. Язык Go имеет встроенный синтаксис преобразования между байтовыми фрагментами и строками.

package main

import "fmt"

func main() {
	var s1 = "hello world"
	var b = []byte(s1)  // 字符串转字节切片
	var s2 = string(b)  // 字节切片转字符串
	fmt.Println(b)
	fmt.Println(s2)
}

--------
[104 101 108 108 111 32 119 111 114 108 100]
hello world

С точки зрения экономии памяти вы можете подумать, что срез байтов и базовый массив байтов строки являются общими. Но это не так, базовый массив байтов будет скопирован. Если контент большой, то операция конвертации имеет определенную стоимость.

Так зачем тебе копия? Поскольку содержимое базового массива байтовых срезов может быть изменено, а базовый байтовый массив строк доступен только для чтения, если он является общим, свойство строк только для чтения больше не сохраняется.

Читайте больше глав «Быстро выучить язык Go» и обратите внимание на паблик-аккаунт «Code Cave»