You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is// used, by convention, to distinguish byte values from 8-bit unsigned// integer values.typebyte=uint8// rune is an alias for int32 and is equivalent to int32 in all ways. It is// used, by convention, to distinguish character values from integer values.typerune=int32
在Go语言中没有字符类型,字符只是整数的特殊用例,使用了
byte
和rune
作为别名Go的字符串使用了UTF-8的编码来表示,所以要明确好Unicode码和ASCII码的区别
如何使用Go来遍历字符串、修改字符串,这也是一个常见的问题
1.Go的byte和rune
Go的源码表示
由上可知:
byte
是uint8
的别名,长度为 1 个字节,可以表示 2^8 = 255 个字符,在Go中用于表示 ASCII 字符rune
是int32
的别名,长度为 4 个字节,可以表示 2^32个字符,用于表示以 UTF-8 编码的 Unicode 码点2.ASCII、Unicode 和 UTF-8字符的区别
2.1 ASCII码
ASCII
使用了一个字节实现对英语字符与二进制位之间的关系,做了统一规定每一个二进制位(bit)有
0
和1
两种状态,又因为一个字节等于8位所以,八个二进制位就可以组合出2^8 = 256种状态,足够表示255个字符。
英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。
2.2 Unicode
可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失,所以
Unicode
码出现了。Unicode
当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639
表示阿拉伯字母Ain
,U+0041
表示英语的大写字母A
,U+4E25
表示汉字严
。Unicode
编码也存在问题,因为Unicode
只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。两个严重的问题:
第一个问题是,
如何才能区别 Unicode 和 ASCII
?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,
会出现明显浪费存储的问题
,英文字母只用一个字节表示就够了,如果Unicode
统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。这两个问题造成的结果就是:
出现了 Unicode 的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示 Unicode。
Unicode 在很长一段时间内无法推广,直到互联网的出现。
2.3 UTF-8
互联网的普及,强烈要求出现一种统一的编码方式。
UTF-8
就是在互联网上使用最广的一种Unicode
的实现方式。UTF-8
最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。UTF-8 的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,
UTF-8
编码和ASCII
码是相同的。2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
下表总结了编码规则,字母
x
表示可用编码的位:如果一个字节的第一位是
0
,则这个字节单独就是一个字符;如果第一位是
1
,则连续有多少个1
,就表示当前字符占用多少个字节。以汉字严为例,演示如何实现 UTF-8 编码:
严
的Unicode
是4E25
(100111000100101)根据上表,可以发现
4E25
处在第三行的范围内(0000 0800 - 0000 FFFF),因此严的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从
严
的最后一个二进制位开始,依次从后向前填入格式中的x
,多出的位补0
。这样就得到了,严
的UTF-8
编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5
。参考链接:阮一峰:字符编码笔记:ASCII,Unicode 和 UTF-8
3.Go 语言中表示字符呢?
byte
如果要表示 byte 类型的字符,可以使用
byte
关键字来指明字符变量的类型:直接输出的话会输出字符对应的ASCII码
如果想要输出具体字符,需要格式化说明符
%c
来输出代码如下:
rune
与 byte 相同,想要声明 rune 类型的字符可以使用
rune
关键字指明:注:如果在声明一个字符变量时没有指明类型,Go 会默认它是
rune
类型4.Go为什么需要两种类型?
在 Go 语言中,使用的是
UTF-8
编码,用UTF-8
编码来存放一个ASCII
字符依然只需要一个字节,而存放一个非 ASCII
字符,则需要 2个、3个、4个字节,它是不固定的。byte 占用一个字节,因此它可以用于表示 ASCII 字符。
rune占用4个字节,可以用它表示 UTF-8 字符,因为UTF-8 是一种变长的编码方法,字符长度从 1 个字节到 4 个字节不等。
所以:
Go 中的字符串存放的是 UTF-8 编码,那么我们使用 s[i] 这样的下标方式获取到的内容就是 UTF-8 编码中的一个字节。
对于非 ASCII 字符而言,这样的一个字节没有实际的意义,除非你想编码或解码 UTF-8 字节流。
在 Go 语言中,已经有很多现成的方法来编码或解码 UTF-8 字节流了。
输出结果:
上面的问题,如何单独截取字符呢?
利用
[]rune()
将字符串转为 Unicode 码点再进行截取,这样就无需考虑字符串中含有 UTF-8 字符的情况了输出结果:
5.遍历字符串
5.1 下标遍历
由于在 Go 语言中,字符串以
UTF-8
编码方式存储,使用len()
函数获取字符串长度时,注意获取到的是UTF-8
编码字符串的字节长度通过下标索引获取值将会产生一个字节,所以,如果字符串中含有非ASCII编码字符,就会出现乱码,例如中文字符需要1~4不等的字节来表示。
例如,遍历 "Hello,世界"
输出结果:
逗号和中文字符就出现了乱码,因为属于非ASCII码,它们不能用ASCII码表示。
另外说明了使用 s[i] 这样的下标方式获取到的内容就是 UTF-8 编码中的一个字节,无法使用byte类型表示,否则出现乱码
如果想要正确表示中文字符,需要使用rune类型,才能对字符进行存储和表示
通过
rune
类型使用下标遍历:输出结果:
5.2 range遍历
输出结果:
6.字符串的修改
在 Go 语言中,字符串的内容是不能修改的,也就是说,你不能用 s[i] 这种方式修改字符串中的 UTF-8 编码
如果你一定要修改,那么你可以将字符串的内容复制到一个
可写的缓冲区
中,然后再进行修改。这样的缓冲区一般是
[]byte
或[]rune
。如果要对字符串中的字节进行修改,则转换为[]byte
格式,如果要对字符串中的字符进行修改,则转换为[]rune
格式,转换过程会自动复制数据。使用用 []byte修改字符串中的字节:
输出结果:
使用用 []rune修改字符串中的字符:
输出结果:
在 []byte 中处理 Rune 字符(需要用到 utf8 包中的解码函数)
输出结果:
总结:
Go 语言中没有字符的概念,一个字符就是一堆字节,它可能是单个字节(ASCII 字符集),也有可能是多个字节(Unicode 字符集)
byte
是 uint8 的别名,长度为 1 个字节,用于表示 ASCII 字符rune
则是 int32 的别名,长度为 4 个字节,用于表示以 UTF-8 编码的 Unicode 码点字符串的截取是以字节为单位的,使用下标索引字符串只能获取到字节,获取字符需要进行准换
想要遍历
rune
类型的字符则使用range
方法进行遍历。微信公众号
The text was updated successfully, but these errors were encountered: