字符串
原理
在go当中字符串是指所有8比特位字节字符串的集合。字符串可以为空"" 但是不能为nil. 字符串在编译时就确定了长度。值是不能够改变的
字符串本质上是一串字符数组
字符串在编译的时候类型为string 在运行时其类型定义为一个结构体。位于reflect包中
type StringHeader struct {
Data uintptr
Len int
}
在程序运行时。字符串储存了长度(len) 以及指向实际数据(data)的指针
字符串长度
golang 当中所有的文件都是采用utf8编码字符集。1个英文字母占一个字节。一个汉字占三个字节。go当中对字符串取长度len(s)返回的是其字节长度
go当中对字符串取长度len(s)返回的是其字节长度
go当中对字符串取长度len(s)返回的是其字节长度
这与动态语言python有着很大的区别
print(len("go语言"))
以上代码输出的值是4
fmt.println(len("go语言"))
而在go语言当中以上代码输出的值是8
字符与符文
golang中存在着一种特殊的类型叫做rune 符文类型。rune的本质类型是int32. 而字符串的符文个数往往才比较符合我们直观感受上的字符串长度。就好像在python中的len("go语言") 长度为4。而在go 语言中如果按照rune来算的话len("go语言")长度也是4。也就表示有4个rune符文
字符串字面值
字符串值也可以用字符串面值方式编写
"Hello, 世界"
在一个双引号包含的字符串面值中,可以用以反斜杠 \ 开头的转义序列插入任意数据。下面换行,回传和制表符是常见的ASCll控制代码的转义方法
\a 响铃
\b 退格
\f 换页
\n 换行
\r 回车
\t 制表符
\v 垂直制表符
\' 单引号(只用在 '\'' 形式的rune符号面值中)
\" 双引号(只用在 "..." 形式的字符串面值中)
\\ 反斜杠
例如给字符串插入换行符,使用\n
str := "go go go \n"
也可以通过十六进制和八进制转义在字符串面值中包含任意字节,一个十六进制的转义形式是\xhh,其中两个hh表示十六进制数字(大写小写都可以)。一个八进制转义形式是\ooo,包含三个八进制的数字(0-7)。但是不能 超过\377
如下代码表示,使用转义符号反斜杠\ 插入一个255面值的16进制表示的数字,此时str是只有一个字符的字符串,也就是说字符串长度为1,而这个字符是255的十六进制转义。
func main(){
str := "\xff"
fmt.Printf("%d",str[0]) //255
}
一个原生的字符串面值形式是`...`,使用反引号代替双引号。在原生字符串面值中,没有转义操作,全部内容都是字面的意思。包含退格和换行。因为正在表达式中有很多反斜杠表示,所以一般用于正则表达式匹配的字符串都采用反引号表示的原生字符串
双引号"" ,单引号‘’,反引号``区别
str := `aaaa`
str1 := "aaaa"
c := 'c'
fmt.Println(c)
fmt.Println(str1)
fmt.Println([]byte(str))
-
使用双引号"" 表示字符串的面值,在使用双引号括起来的字符串中,可以使用反斜杠的转义序列插入任意数据,例如下代码使用双引号表示一个字符的字面值,我们使用反斜杠\给这个字符串插入一个换行符(也可以是回车,制表符等),同时插入了一个16进制表示的数字255。
str := "hello,world\n number is \xff"以下使用双引号引入字符串,使用了反斜杠\插入两个字符,一个是\n表示换行符,一个是\xff表示一个16进制数。所以字符串长度为2
str := "\n\xff" fmt.Println("长度",len(str)) //2 -
使用反引号``表示原生字符串,也就是字符串字面值就是代表字符串本身,不包含转义。
如下原生字符串表示一个长度为6的字符串,字符串的第一个字符是'\' ,第二个字符是'n',第三个字符是'\',以此类推
str := `\n\xff` //表示原生字符串 -
使用单引号''表示一个字符面值,如下使用单引号表示a这个字符,同样的也可以使用转义符号反斜杠\,插入转义数据。
c := 'a' // c := '\xff' fmt.Printf("%d",c)
Unicode
在很久以前,世界还是比较简单的,起码计算机世界就只有一个ASCII字符集:美国信息交换标准代码。ASCII,更准确地说是美国的ASCII,使用7bit来表示128个字符:包含英文字母的大小写、数字、各种标点符号和设备控制符。对于早期的计算机程序来说,这些就足够了,但是这也导致了世界上很多其他地区的用户无法直接使用自己的符号系统。随着互联网的发展,混合多种语言的数据变得很常见(译注:比如本身的英文原文或中文翻译都包含了ASCII、中文、日文等多种语言字符)。如何有效处理这些包含了各种语言的丰富多样的文本数据呢?
答案就是使用Unicode( unicode.org ),它收集了这个世界上所有的符号系统,包括重音符号和其它变音符号,制表符和回车符,还有很多神秘的符号,每个符号都分配一个唯一的Unicode码点,Unicode码点对应Go语言中的rune整数类型(译注:rune是int32等价类型)。
在第八版本的Unicode标准里收集了超过120,000个字符,涵盖超过100多种语言
码点
码点:字符的标号,
- 比如在unicode字符集中,“世”这个字符是第19990个字符,所以它的码点就是19990,
- 比如在unicode字符集中,“界”这个字符是第30028个字符,所以它的码点就是30028
Go中unicode
Unicode中的码点对应Go语言中的rune(符文)整数类型(也就是int32)。也就是说一个字符的码点是按照USC-4中一个字符使用4个字节进行表示。
UCS-4 与 UTF-32
USC-4标准中一个字符的码点用一个int32的整型进行表示,也就是说理论上一共可以保存2^32个字符。
而将USC-4中码点表示的数字直接使用的编码的方式,就叫做UTF-32。每个Unicode码点都使用同样大小的32bit来表示。这种方式比较简单统一,但是它会浪费很多存储空间,因为大多数计算机可读的文本是ASCII字符,本来每个ASCII字符只需要8bit或1字节就能表示。而且即使是常用的字符也远少于65,536个,也就是说用16bit编码方式就能表达常用字符
UTF-32编码格式
'\U00004e16' //表示字符 '世'的UTF-32编码
USC-2 与 UTF-16
USC-2标准中一个字符的码点用一个int16(2个字节)的整型进行表示,也就是说理论上一共可以保存2^16(65536)个字符。
而将USC-2中int16表示的数字直接使用的编码方式,就叫做UTF-16,与UTF-32一样的是,UTF16在表示英文字母或者ASCII字符时,也是直接使用2个字节表示。所以也会造成存储空间。
使用UTF-16表示UCS-4 超过65535的字符时候,因为两个字节已经表示不了了,所以需要进行变长,
UTF-16编码格式
'\u4e16' //表示字符'世'的UTF-16编码
UTF-8
UTF-8将unicode(UCS-2标准或UCS-4标准)中字符对应的码点进行编码从而转换成使用1到4个字节来表示。因此ASCII部分字符只使用1字节,而中文大则一般使用3个字节来表示。
其编码规则如下
- 单字节的字符,字节第一位设为0。对于英语文本,UTF-8码只占用一个字节,和ASCII码完全相同,
- n个字节的字符(n > 1),第一个字节的前n位设为1,第n+1位设为0,后面字节的前两位设为10,这n个字节的其余空位填充该字符unicode码,高位用0补足。
这样就形成了如下的UTF-8标记为:
0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
进过计算可以发现UTF-8 长度与码点表示的范围
0xxxxxxx runes 0-127 (ASCII)
110xxxxx 10xxxxxx 128-2047 (values <128 unused)
1110xxxx 10xxxxxx 10xxxxxx 2048-65535 (values <2048 unused)
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-0x10ffff (other values unused)
Go语言的源文件采用UTF8编码
并且Go语言处理UTF8编码的文本也很出色。unicode包提供了诸多处理rune字符相关功能的函数(比如区分字母和数字,或者是字母的大写和小写转换等),unicode/utf8包则提供了用于rune字符序列的UTF8编码和解码的功能
UTF-8 编码格式
'\xe4\xb8\x96' 表示字符'世'的UTF-8编码
UTF-8常见使用
字符串长度与字节数量
len函数负责统计字符串中的字节数量,也就是字符串以UTF-8 编码之后的字节数组的长度
而utf8包下的RuneCountInString用于统计该字符串中的字符的数量(5个英文字母,加上空格和标点,还有2个汉字)
str := "Hello, 世界"
fmt.Println(len(str)) //13
fmt.Println(utf8.RuneCountInString(str)) //9
字符解码
每次utf8.DecodeRuneInString调用都会返回一个rune和size,其中rune表示当前解码的字符,而size表示该字符的大小
str := "Hello, 世界"
r,s :=utf8.DecodeRuneInString(str)
fmt.Println(r,s) //72 , 1
Go语言的range循环在处理字符串的时候,会自动隐式解码UTF8字符串,其中i 表示
for i, r := range "Hello, 世界" {
fmt.Printf("%d\t%q\t%d\n", i, r, r)
}
//打印如下
0 'H' 72
1 'e' 101
2 'l' 108
3 'l' 108
4 'o' 111
5 ',' 44
6 ' ' 32
7 '世' 19990
10 '界' 30028
注意:非ASCII,索引i更新步长超过1字节
UTF-8解码错误
如果UTF-8字符串在解码过程中发生了错误,将生成一个特别的Unicode字符\uFFFD,在印刷中(以字符的形式输出)这个符号通常是一个黑色六角或钻石形状�,当程序遇到这样的一个字符,通常是一个危险信号,说明输入并不是一个完美没有错误的UTF8字符串
UTF-8 字符串转化为rune类型
将字符串s 转化成rune类型的切片,然后再打印每个字符对应的unicode码点,其中一个rune类型对应一个字符
s := "我是"
fmt.Printf("% x\n", s) // "e6 88 91 e6 98 af"
r := []rune(s)
fmt.Printf("%x\n", r) //[6211 662f]
其中%x中多加了一个空格% x,在每个字节(16进制2位为一个字节)之间加入一个空格。
"% x" :e6 88 91 e6 98 af //6个字节
"%x" : e29da4efb88f //6个字节
UTF-8 将rune类型切片转换为字符串
r //e6 88 91 e6 98 af
直接使用T.() 类型转化
fmt.Printf("%x\n", r)
将一个整数转型为字符串
生成以只包含对应Unicode码点字符的UTF8字符串
string(65) //显示码点为65,对应的字符为'a'
如果对应码点是无效的则使用\uFFFD无效字符作为替换
fmt.Println(string(12345512321312)) // 无效字符�
字符,字符串编码形式
字符串和Byte切片
标准库中字符串处理包
- bytes包
- 提供与string类似的功能,但是针对于和字符串有着相同结果的[]byte类型,因为字符串是只读的,因此逐步构建字符串会导致很多分配和赋值。在这种情况下,使用bytes.Buffer类型将会更有效
- strings包
- 提供字符串查询,替换,比较,截断,拆分和合并功能
- strconv包
- 提供整型数,布尔型,浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换
- unicode包
- 提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,它们用于给字符分类。每个函数有一个单一的rune类型的参数,然后返回一个布尔值。而像ToUpper和ToLower之类的转换函数将用于rune字符的大小写转换。所有的这些函数都是遵循Unicode标准定义的字母、数字等分类规范。strings包也有类似的函数,它们是ToUpper和ToLower,将原始字符串的每个字符都做相应的转换,然后返回新的字符串