- 字符串表示不可改变的字节序列
- 字符串虽然可以包含任意数据,但通常指用来包含人类可读的文本。
- 字符串是一种值类型且值不可变,即字节的定长数组,因此创建文本后将无法再次修改内容。
概念
- 字节
字节即byte
由8个比特位组成,即1byte = 8bit,字节是计算机中基本的计量单位。Golang中存在byte
类型,其本质是uint8
的别名。
- 字符
字符的概念比较模糊,在Unicode中通常使用code point
码元来表示,简单来说字符是一种信息单元,比如符号、字母等。
rune
rune
实际上是int32
的别名,为了区分将字符和整数值分开,而增加了rune
类型代表一个字符。
- UTF-8
Golang中string
是Unicode兼容的,是UTF-8编码的。
Golang中string
的内部实现使用UTF-8编码。由于采用UTF-8编码,string
可以包含文本,而文本是世界上任何语言的混合,因而不会造成页面的混乱和限制。
UTF-8编码格式是文本的标准编码,对占用的字节长度具有不定性。string
是UTF-8字符的一个序列,当字符为ASCII码表上的字符时则会占用1个字节,其它字符会根据需要占用2到4个字节。Golang为减少内存和磁盘占用空间,string
可能会根据需要占用1到4个字节。
Golang中string
不同于其他语言,它是一个变宽字符序列,每个字符使用UTF-8编码的一个或多个字节表示。string
是任意字节(包括值为零的字节)的不可变链。
Golang中的string
的字节采用UTF-8编码来标识Unicode文本,统一使用UTF-8编码以解决中文乱码问题。
string
Golang标准库builtin
给出了所有内置类型的定义
type string string
-
string
是一系列8位字节的集合,但并不一定代表UTF-8编码的文本。 -
string
可以为空(长度为0),但不能为nil
。 -
string
对象不可修改
例如:使用var
关键字声明字符串变量
var str string
stringStruct
Golang的string
是一种数据类型,占16字节空间。string
在runtime
包中就是stringStruct
对外呈现才叫做string
。
- 前8字节是一个指针指向字符串值(字节切片)的地址
- 后8个字节是一个整数,用于标识字符串的长度。
type stringStruct struct{
str unsafe.Pointer //字符串的首地址
len int //字符串的长度
}
string
数据结构跟切片类似,只不过切片还有一个表示容量的成员。事实上,string
和切片准确来说是byte
切片,经常会发生转移。因此string
本质上可看作是一个只读的字节切片。
从string
的结构上看实际上是一个指针str
,指向某个字节切片的首地址,字节切片中存放着字符串的真正内容。
例如:字符串“你好”在内存中的表示
var str string
str = "你好"
内存表示
由于Golang源码默认采用UTF-8编码,因此string
字面量也是UTF-8编码的。中文汉字的“你”会被编码为\xe4\xbd\xa0
,中文汉字的“好”会被编码为\xe5\xa5\xbd
。
例如:将UTF-8编码转换为字符串
bs := []byte{0xe4, 0xbd, 0xa0}
fmt.Printf("%s", string(bs))//你
虽然string
并非切片,但支持切片操作。
对于同一个string
字面量,不同的string
变量指向相同的底层数组,因为string
是只读的,为了节省内存,相同字面量的string
通常对应同一个string
产量。
str1 := "hello"
str2 := "hello"
str3 := "hello "
data1 := (*reflect.StringHeader)(unsafe.Pointer(&str1)).Data
fmt.Printf("str1 data = %d\n", data1) //str1 data = 7858269
data2 := (*reflect.StringHeader)(unsafe.Pointer(&str2)).Data
fmt.Printf("str2 data = %d\n", data2) //str2 data = 7858269
data3 := (*reflect.StringHeader)(unsafe.Pointer(&str3)).Data
fmt.Printf("str3 data = %d\n", data3) //str3 data = 7858734
Golang中string
内部并非以\0
作为结尾,而是通过一个长度域来表示字符串的长度。
字面量
Golang中string
的值使用双引号包裹内容(使用单引号包裹的是字符),因此可在Golang的源码中直接添加非ASCII码字符。string
以原生数据类型出现,使用string
就像使用其他原生数据类型一样。
str := "hello world"
Golang中string
字面量可以通过两种方式创建
- 使用双引号包裹
使用双引号创建的字符串支持转义字符,但不能跨行。
- 使用反引号包裹
使用反引号创建的字符串又称为原始文本(raw literals),原始文本不支持转义字符,但可以跨越多行。两个反引号之间包裹的字符串会被原样输出,因此在反引号中使用转义字符将会是无效的。在反引号之间的所有代码均不会被编译器识别,只会作为字符串的一部分。
- 不可变性
Golang中string
一旦创建则不可变,即无法更改string
的值,也就是说string
是只读的。若尝试更改则编译器会引发错误。这是由于字符串字面量的存储位置并不在堆和栈上,通常编译器会分配到只读段(.rodata)上,因此对应的地址是不可修改的。
Golang的实现中string
不包含内存空间,只有一个内存的指针,这样做的好处是string
变得轻量,可以方便地进行传递且无需担心内存拷贝。由于string
通常指向字符串字面量,而字符串字面量存储位置是只读段,而不是堆或栈上,所有string
才不可修改。
string
是一个只读字节片,string
的字节可以使用UTF-8编码在Unicode文本中表示。
转义字符
使用双引号包裹的字符串支持转义字符
转义字符 | 含义 |
---|---|
\r |
回车符,返回行首。 |
\n |
换行符,直接跳到下一行的同列位置。 |
\t |
制表符 |
\' |
单引号 |
\" |
双引号 |
\\ |
反斜杠 |
长度
-
len()
获取字符串占用字节的长度(字节数) -
utf8.RuneCountInString()
获取字符串的长度(符文数)
var str string
str = "hell?我"
fmt.Printf("len = %d, RuneCount = %d\n", len(str), utf8.RuneCountInString(str)) //len = 8, RuneCount = 6
拼接
- 多个字符串可使用
+
字符串拼接符来进行追加形成新的字符串 - 由于编译器会自动在行尾补全分号,因此字符串拼接符必须放在行尾。连接跨行字符串时
+
必须在上一行末尾否则会导致编译错误。
var str string
str = "hello " +
"world"
fmt.Println(str)
- 可使用
+=
来对字符串进行拼接 - 可使用
fmt.Sprintf
进行拼接
var str string
str = fmt.Sprintf("id = %d, name = %s\n", 1, "admin")
fmt.Println(str)
字节
var str string
str = "h1你"
i := 0
fmt.Printf("str[%d] = %q, val = %v, hex = %x, unicode = %U\n", i, str[i], str[i], str[i], str[i])
str[0] = 'h', val = 104, hex = 68, unicode = U+0068
占位符 | 描述 |
---|---|
%q | 字符字面量,字节对应的字符字面量。 |
%v | 默认格式,字节对应的编码值。 |
%x | 小写十六进制 |
%U | Unicode格式 |
字节切片
Golang中string
是一个字节切片,而非字符切片。通过标准索引的方式获取的是字节,即在方括号[]
中指定索引值,索引值从0开始计数。
var str string
str = "hello"
fmt.Printf("char = %c, byte = %d\n", str[0], str[0]) //char = h, byte = 104
通过rune
类型可方便地对每个UTF-8
字符进行访问
var str string
str = "hello"
for i:=0; i<len(str); i++{
char := str[i]
fmt.Printf("char = %c, byte = %d\n", char, char)
}
char = h, byte = 104
char = e, byte = 101
char = l, byte = 108
char = l, byte = 108
char = o, byte = 111
- 获取
string
中某个字节的地址属于非法行为
var str string
str = "hello"
for i, v := range str {
fmt.Printf("index = %d, value = %c, byte = %d\n", i, v, v)
}
index = 0, value = h, byte = 104
index = 1, value = e, byte = 101
index = 2, value = l, byte = 108
index = 3, value = l, byte = 108
index = 4, value = o, byte = 111
比较
- 比较运算符在内存中会按字节比较来实现字符串的比较,因此比较的结果是字符串自然编码的顺序。
遍历
- 使用
for
以byte
形式遍历字符串
var str string
str = "h1你"
for i := 0; i < len(str); i++ {
fmt.Printf("i = %d, v = %v, char = %c\n", i, str[i], str[i])
}
i = 0, v = 104, char = h
i = 1, v = 49, char = 1
i = 2, v = 228, char = ä
i = 3, v = 189, char = ½
i = 4, v = 160, char =
- 使用
for...range
以rune
方式遍历字符串
当使用for...range
迭代字符串时,每次迭代Golang都会使用UTF-8解码出一个rune
类型的字符,且索引为当前rune
的起始位置(以字节为最小单位)。
var str string
str = "h1你"
fmt.Println(reflect.TypeOf(str)) //string
fmt.Println(reflect.TypeOf(str[0])) //uint8 = byte
for _, v := range str {
fmt.Println(reflect.TypeOf(v)) // int32 = rune
}
实际上使用for...range
迭代string
时,会将string
中的byte
重新转换为UTF-8字符,对于错误的编码则会使用一个字节的占位符来替代。
for i,v := range []rune(str){
}
例如:
var str string
str = "h1你"
for i, v := range str {
fmt.Printf("i = %d, v = %v, char = %c\n", i, v, v)
}
i = 0, v = 104, char = h
i = 1, v = 49, char = 1
i = 2, v = 20320, char = 你
转换
要修改字符串,可先将其转换为[]byte
或[]rune
,然后再转换为string
。无论哪种转换,都会重新分配内存,并复制字节数组。
网友评论