go笔记——编码及string与[]byte

参考链接:https://www.cnblogs.com/zhangboyu/p/7623712.html

1.编码

参考链接:https://www.cnblogs.com/liupp123/articles/8023861.html
首先明确一点计算机里面存的始终是二进制的数据,但是为了让数据变为可读的,于是就出现了编码,比如ascll、gbk、utf-8等编码方式。

  1. Ascll编码
    对于英语而言,其字符状态空间较小,只有52个字符,外加一些标点符号,1byte的长度(2^8个状态=256)就可以满足了,即ascll编码就可以满足(ASCII码一共规定了128个字符的编码,只占用了一个字节的后面7位,最前面的1位统一规定为0)

  2. 中文编码
    但是对于其他语言,比如中文,字符状态空间极大,128个字符完全不够,于是要求更大的编码长度来表示。比如,简体中文常见的编码方式是GB2312,使用2byte表示一个汉字,所以理论上最多可以表示256x256=65536个符号

  3. Unicode编码
    世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。
    可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。
    Unicode当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字"严"。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。

  4. UTF-8编码
    由于Unicode 编码的字符空间太大,编码占用的空间太大,为了压缩编码空间,于是出现了utf-8。其他实现方式还包括UTF-16(字符用两个字节或四个字节表示)和UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。
    UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个byte表示一个符号,根据不同的符号而变化字节长度。
    UTF-8的编码规则很简单,只有二条:
    1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
    2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

2.字符

在上面说过了所有的可读性字符(串)都是通过解码字节序列而呈现的,在go中默认是utf-8编码,所以对于一个字符而言其最大的字节(byte[])长度为4,所以需要使用32bit的大小来存,即int32(go的基本类型,相当于是对32bit的数据的封装),更进一步,go通过rune类型来作为int32在字符方面的别名,所以使用单引号的字面量其类型就是rune(最底层还是int32),类似于其他语言的char。

2.[]byte

上面说了编码方式,于是引出[]byte。从上面可知要想以可读字符形式呈现在计算机中,必须要将字符文本以某种编码方式存入。比如,如果要存为utf-8编码的文本,就要存入1~4个byte大小单位的[]byte。所以说实际上所有文本都是以byte单位存入到计算机中的。程序要想打印数据到屏幕上,需要根据不同的编码方式将机器码按照编码所规定的字符byte大小的长度进行解码翻译,从而以可读方式呈现。

3.string

简单的来说字符串是一系列8位字节的集合,通常但不一定代表UTF-8编码的文本。字符串可以为空,但不能为nil。而且字符串的值是不能改变的。
不同的语言字符串有不同的实现,在go的源码中src/runtime/string.go,string的定义如下:

type stringStruct struct {
    str unsafe.Pointer
    len int
}

可以看到str其实是个指针,指向某个数组的首地址,另一个字段是len长度。那到这个数组是什么呢? 在实例化这个stringStruct的时候:

func gostringnocopy(str *byte) string {
	ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}
	s := *(*string)(unsafe.Pointer(&ss))
	return s
}

哈哈,其实就是byte数组,而且要注意string其实就是个struct,所以索引字符串时s[i],是取的其字节byte,而非字符。
所以对于计算string的长度,在go中是看其byte[]所占的长度,而非字符总长度,这与其它语言很不同,因为其他语言是按照字符来计算string的长度,比如:

java中

main(){
	String s = "长安a"
	System.out.println(s.length())
}

————output————
3

而在go中

package main
import (
	"fmt"
)
func main() {
	s := "长安a"
	fmt.Println(len(s))
}

——————————output——————————
7
因为utf-8中文占3byte,字母数字占1byte,所以最终长度为7

4.字符串的遍历方式和修改

在go中遍历字符串,有两种方式,byte和rune

package main
import (
	"fmt"
)
func main() {
	s := "长安a"
	//byte
	for index := 0; index < len(s); index++ {
		fmt.Printf("%d: %c\n", index, s[index])
	}
	//rune
	for i, c := range s {
		fmt.Printf("%d: %c\n", i, c)
	}
}
————output————

0: é	1: •	2: ¿	3: å	4: ®	5: ‰	6: a	
0: 长	3: 安	6: a	

从上面可看出,byte方式由于按照1byte方式取,每次取的值都只有长度为1byte,所以最终解码出来和中文对不上
rune方式是按照字符长度取的,索引间隔是实际一个字符的byte长度,所以最终文字对得上。

5.[]byte与string取舍

既然string就是一系列字节,而[]byte也可以表达一系列字节,那么实际运用中应当如何取舍?

  1. string可以直接比较,而[]byte不可以,所以[]byte不可以当map的key值。
  2. 因为无法修改string中的某个字符,需要粒度小到操作一个字符时,用[]byte。
  3. string值不可为nil,所以如果你想要通过返回nil表达额外的含义,就用[]byte。
  4. []byte切片这么灵活,想要用切片的特性就用[]byte。
  5. 需要大量字符串处理的时候用[]byte,性能好很多。
发布了69 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/JustKian/article/details/100690601