Golang: string和byte slie字节问题(9)

讲清楚 string 和 bytes[], 字符,字面量,unicode编码, rune。

专栏的介绍可以参考 《GotchaGolang专栏》,代码可以看《宝库-Gotcha》

为什么 string 不可变,[]bytes 可变?

字符串是一段只读切片,存储的是字节;utf8 编码的字符,字节数不确定,所以 string 再按照 C 语言的习惯(ascii编码的字符)去操作字符串,势必出问题,好在这方面 golang 语言自己已经做了一定的处理, fmt包,range循环, encoding/utf8包等。

下面慢慢看看详情。


golang 中的字符串,不管是字面量,还是先定义 string 后赋值的都是只读的,要操作必须先转换层字符串

此外,byte slice 也可以转化成字符串。这两种转化都需要分配一块新的内存,然后进行内容拷贝。

1
2
3
4
s := "abc"
b := []byte(s) //新内存

s2 := string(b) //新内存

运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

func main() {

s := "abc"
b := []byte(s) //新内存
fmt.Printf("%#x\n", &s) //0x1040c138
fmt.Printf("%#x\n", &b[0]) //0x10414020

s2 := string(b) //新内存
fmt.Printf("%#x\n", &s2) //0x1040c148
}

string 截取操作没有改变底层数组(即重新分配内存)但是关于底层数组的索引已经变了,长度改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
"fmt"
"reflect"
)

func main() {

str := "hello"
fmt.Println(string(str[2])) //l

fmt.Printf("%#x\n", &str) //0x1040c138


//str[2] = 'x'
//fmt.Println(str[2]) //unicode 不支持下标赋值
//(按字节提取的并不是一定是具体的 ascii字符,或可打印字符)

//底层数组已经改变, len 变为1
str = str[2:3]
fmt.Println(len(str)) // 1

fmt.Printf("%#x\n", &str) //0x1040c138


fmt.Println(str) // l
fmt.Println(string(str[0])) //l

//str[0] = 'x'
//fmt.Println(str[0]) //unicode 不支持下标赋值

//下面越界,所以报错
//fmt.Println(str[2]) //panic: runtime error: index out of range

fmt.Println(reflect.TypeOf(str)) //string
}

字符串实际上是一个只读的片段,保存任意字节。

字符串保存的是字节, 另一部分就是 字符的含义很难定义 ,UTF8的字符可能是2-6字节(utf16,像 Qt 里面的 QString 则明确的说用 utf16 进行内部字符串编码;utf8, ascii 都有它不方便的地方),rune 就专门用来定义单个字符Unicode编码,而不仅仅是 int32 的别名。

rune : fmt.Println(str[2]); //108 ,打印的数字就表明了 rune 代表的是该字节对应 Unicode 编码

而对于上面的 "hello" 根本没有字节级别的转义,所以不能取按照字节赋值。打印是经过特殊处理了,也就是说不仅仅可以按小标打印字面量,也可以通过循环打印,他们已经解码 utf8的rune:

1
2
3
4
const nihongo = "日本語"
for index, runeValue := range nihongo {
fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
}

循环输出按照 unicode 编码来的,可以看到字节占用: (索引操作是按照字节来的)

1
2
3
U+65E5 '日' starts at byte position 0
U+672C '本' starts at byte position 3
U+8A9E '語' starts at byte position 6

上面的串,不是英文字母,也就是一个字符占用多过一个字节,那么再用字节操作,即下标索引,那么打印就很奇怪了。

当然,如果在 string 字面量里面初始化一堆无效的 unicode,打印出来的状况很诡异的。

一定要自己去处理和组合这些 utf8 编码的字节,或者称 string,unicode/utf8 标准库操作:

(用的很少,特殊需求)

1
2
3
4
5
6
const nihongo = "日本語"
for i, w := 0, 0; i < len(nihongo); i += w {
runeValue, width := utf8.DecodeRuneInString(nihongo[i:])
fmt.Printf("%#U starts at byte position %d\n", runeValue, i)
w = width
}

字节、字符、rune之间的区别,UTF-8编码和Unicode编码之间的区别, 一个字符串和一个字符串字面量的区别。

扩展阅读:《string, bytes, runes, chars》

与其这样采用 utf8,然后为了适配字符和字节问题专门弄一个 utf8 包,还不如一开始采用 utf16呢。笑。(那样占用的控件就大了)其他关于 string 和 []byte 的操作可以借助标准库 stringsbytes


Merlin 2018.3 string 和 []bytes 之间的恩怨

文章目录
|