go之字符
编码规范
🔗ASCII-单字节
ASCII 编码方案使用单个字节(byte)的二进制数来编码一个字符。标准的 ASCII 编码用一个字节的最高比特(bit)位作为奇偶校验位,而扩展的 ASCII 编码则将此位也用于表示字符。ASCII 编码支持的可打印字符和控制字符的集合也被叫做 ASCII 编码集。
ASCII 将每个英文字符以及数字和符号映射到 32 - 127 范围内的数字。这意味着每个字符都可以用单个字节(8 位)表示。低于 32 的代码被称为不可打印并用作控制字符,例如,7 使您的计算机发出哔哔声,10 用于 LF(换行)。
🔗Unicode-多字节
我们所说的 Unicode 编码规范,实际上是另一个更加通用的、针对书面字符和文本的字符编码标准。它为世界上现存的所有自然语言中的每一个字符,都设定了一个唯一的二进制编码。
它定义了不同自然语言的文本数据在国际间交换的统一方式,并为全球化软件创建了一个重要的基础。
Unicode 编码规范以 ASCII 编码集为出发点,并突破了 ASCII 只能对拉丁字母进行编码的限制。它不但提供了可以对世界上超过百万的字符进行编码的能力,还支持所有已知的转义序列和控制代码。
将字符抽象为Unicode code point
包含地球上所有合理书写系统的单一字符集(ASCII 和 127 之前的 Unicode 彼此完全相似)
String: H e l l o Unicode code point:U+0048 U+0065 U+006C U+006C U+006F
Unicode 编码规范提供了三种不同的编码格式,即:UTF-8、UTF-16 和 UTF-32。其中的 UTF 是 UCS Transformation Format 的缩写。而 UCS 又是 Universal Character Set 的缩写,但也可以代表 Unicode Character Set。所以,UTF 也可以被翻译为 Unicode 转换格式。它代表的是字符与字节序列之间的转换方式。
🔗编码格式
🔗UTF-8
Unicode code point在内存中的具体实现
🔗UTF-8的特点
- 在这几种编码格式的名称中,“-” 右边的整数的含义是,以多少个比特位作为一个编码单元。以 UTF-8 为例,它会以 8 个比特,也就是一个字节,作为一个编码单元。并且,它与标准的 ASCII 编码是完全兼容的。也就是说,在 [0x00, 0x7F] 的范围内,这两种编码表示的字符都是相同的。
- UTF-8 是一种可变宽的编码方案。换句话说,它会用一个或多个字节的二进制数来表示某个字符,最多使用四个字节。比如,对于一个英文字符,它仅用一个字节的二进制数就可以表示,而对于一个中文字符,它需要使用三个字节才能够表示。不论怎样,一个受支持的字符总是可以由 UTF-8 编码为一个字节序列。
🔗UTF-8 in Go
🔗Go源码文件
注意: 在Golang语言中的标识符可以包含 " 任何Unicode编码可以标识的字母字符 “。
被转换的整数值应该可以代表一个有效的 Unicode 代码点,否则转换的结果就将会是 “�”,即:一个仅由高亮的问号组成的字符串值。
显然,Go 语言采用的字符编码方案从属于 Unicode 编码规范。更确切地说,Go 语言的代码正是由 Unicode 字符组成的。Go 语言的所有源代码,都必须按照 Unicode 编码规范中的 UTF-8 编码格式进行编码。
Go 语言的源码文件必须使用 UTF-8 编码格式进行存储。如果源码文件中出现了非 UTF-8 编码的字符,那么在构建、安装以及运行的时候,go 命令就会报告错误 " illegal UTF-8 encoding “。
🔗不可转义字节
package main
import (
"fmt"
)
func main() {
sample := "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98"//该字符串包含不可转义成UTF8的字节序列
fmt.Println(sample)
}
//��=� ⌘
Go 字符串不总是按UTF-8编码的,只有字符串文字是 UTF-8编码的。
因为字符串可以包含任意字节,如果该字符串包含不可转义的字节(如sample),那么该字符串不是UTF-8编码的。
没有字节级转义的字符串文字始终包含有效的 UTF-8 序列。
这些序列代表 Unicode 代码点,称为rune。当一个 string 类型的值被转换为 []rune 类型值的时候,其中的字符串会被拆分成一个一个的 Unicode 字符。
unicode 代码点 <==> rune
Go之string
🔗数据结构
//字符串
type StringHeader struct {
Data uintptr
Len int
}
//切片
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
🔗底层存储
在 Go 中,字符串只是一个只读的字节切片。不需要保存 UTF-8 或任何其他预定义的编码格式。它保存的唯一数据是一些字节。
字符串是从字节构建的,因此对它们进行索引会产生字节,而不是字符。字符串甚至可能不包含字符。
通常string常量是编译器分配到只读段的(.rodata),对应的数据地址不可写入。fmt.Sprintf
生成的字符串分配在堆上,对应数据地址可修改。
在 Go 语言中,一个 string 类型的值既可以被拆分为一个包含多个字符
的序列,也可以被拆分为一个包含多个字节
的序列。
前者可以由一个以 rune
为元素类型的切片来表示,而后者则可以由一个以 byte
为元素类型的切片代表。
🔗字符串拼接
方法一:+。字符串在 Go 中是不可变的——每次将字符串分配给变量时,都会在内存中分配一个新地址来表示新值。这与 C++ 和 BASIC 等语言不同,后者可以就地修改字符串变量。在 Go 中,每次附加到字符串的末尾时,它必须创建一个新字符串并将现有字符串和附加字符串的内容copy到其中。一旦需要拼接的字符串非常大,拷贝带来的性能损失是无法忽略的。事实上,这种朴素追加的方法是 O(N2)
方法二:strings.Join。在我们的例子中,这需要首先构建一个字符串切片,然后调用切片上的函数。如果您还没有切片,则构建切片需要时间,因此我们将其计为在我们的基准测试中执行连接所需时间的一部分。最终的连接将是 O(N),但构建切片是 O(N)(平均)并且使用 O(N) 内存
方法三:bytes.Buffer。bytes.Buffer 实现了 io.Writer,并且可以在 O(N) 时间内连接字符串,而无需我们构建新切片。
对于极少数的短字符串连接(少于 100 个,长度少于 10 个),使用方法一,朴素的字符串附加,就可以了,实际上性能稍微好一些。对于效率很重要的更繁重的情况,方法三,bytes.Buffer 是评估方法中的最佳选择。也就是说,当您已经有一个只需要连接成一个字符串的字符串切片时,方法二strings.Join 是一个不错的选择。
🔗string与[]byte类型转换
字符串和 []byte
中的内容虽然一样,但是字符串的内容是只读的,我们不能通过下标或者其他形式改变其中的数据,而 []byte
中的内容是可以读写的。不过无论从哪种类型转换到另一种都需要拷贝数据,而内存拷贝的性能损耗会随着字符串和 []byte
长度的增长而增长。
🔗字符串遍历
strArray := []rune(str)
for index, runeValue := range strArray {
fmt.Printf("%s starts at byte position %d\n", string(runeValue), index)
}
package main
import "fmt"
func main() {
tempStr := "01Aa 人生"
fmt.Println("index rune(char) rune(hex) bytes(hex)")
for k, v := range tempStr {
fmt.Printf("%d %q %x [% x]\n", k, v, []rune(string(v)), []byte(string(v)))
}
}
/*
index rune(char) rune(hex) bytes(hex)
0 '0' [30] [30]
1 '1' [31] [31]
2 'A' [41] [41]
3 'a' [61] [61]
4 ' ' [20] [20] --前五个字符"01Aa "与ASCII编码一致
5 '人' [4eba] [e4 ba ba]
8 '生' [751f] [e7 94 9f] --中文字符底层占三个字节
*/
for range 语句可以逐一的迭代出字符串值里的每个Unicode字符,但是相邻的Unicode字符的索引值并不一定是连续的,这取决于前一个Unicode字符是否为单字节字符。
🔗字符(rune==int32)、字节(byte==uint8)与字符串(string==[]uint8)
var var1 = '0' //int32 字符0
var var2 byte = '0' //uint8 字节
var3 := "0"
fmt.Println(var1, var2, var3) //48 48 0
for _, runeV := range var3 {
fmt.Println(var1-runeV == 0) //true 都是int32类型,可比较
}
fmt.Println(var2 == var3[0]) //true 都是uint8类型,可比较