前言
计算机只认识0和1,所以计算机的运算、存储、传输 本质都是对1堆 2进制的操作。
进制
太极生两仪两仪生八卦,为了表示2个通电和断电两种状态,发明了二进制,于是通电是1,断电是0 。
计算机只认识二进制(0,1),我们平时接触到8进制、10进制、16进制是二进制的3种表现形式,其出现目的是为了方便人类更加方便、简短地表示二进制。
单位
既然计算机的运算、存储、传输都使用的是二进制数据,那么我们如何对这些数据大小进行定量呢?
例如:流量还有多少M、硬盘容量有1T、计算机8G内存等、宽带是200M、千兆网络等。
计算机中表示对于二进制大小的常见单位有一下
字符编码
计算机只认识0和1,那么我们在计算机中写得代码、文章、发送的微信消息是如何转换成二进制进行运算、存储、和传输呢?
在计算机底层运算、存储、传输使用的都是二进制数据。那么计算机是如何把人类文明中的文字,转换成二进制的?
这就需要我们去设计1张存储了人类文字和计算机二进制对应关系的字符集、编码和解码机制。
我们可以在Python中查看字符在ASCLL码表对应的码位。
>>> ord("A") 65 >>> chr(65) 'A' >>>
ASCII (American Standard Code for Information Interchange): 美国信息交换标准代码编码
全世界第一台计算机是在美国诞生,诞生之初计算机只能支持英语,也就是说只能支持 符号、字母、数字,不支持:汉语、日语、汉语、泰语等,由于计算机本质上全部都是二进制操作,所以当时美国人就做了一张字符 和 二进制的对照表(ascii编码)。
什么是ASCII字符集、ASCII编码、ASCII码
ASCII编码使用8位来表示所有欧洲国家可以在计算机中使用的的字符(英文+数字+控制符+标点)。
因为1位有2种可能性即可以表示2个字符,那么8位有多少种可能性呢?
可以想象一下给你8个灯泡组成1排使用灯泡亮和暗的特性,可以展示出多少种可能?是的2**8=256。即ascii字符集最多可以表示256个字符。
这就是ASCII字符集的由来。
有了ASCII字符集之后下一步就应该考虑如何把字符集中收录的字符存储到计算机里面?
美国人把CodePoint(码位/码点)类似数据库索引列直接转换成二进制,存储在计算机里。我们把这些二进制信息称为ASCII码。把这种机制叫做ASCII编码。
综上所述美国人发明了字符集利用字符集中的码位编码就得到了ASCILL码。
ASCII字符集合Unicode的联系和区别
如果Unicode也按照ascci码这种机制进行编码,也可以完成数据的内存计算+存储+传输。
但是需要继续兼容ascci并且还得尽可能在编码之后使用更少的空间(utf-8)。所以Unicode不能直接存储字符对应的二进制,还得加点机制那就是(码点)
Unicode为全世界已知语言的所有字符都分配了一个码位(相当于是一个身份证ID),码位本质上也是个二进制。
unicode字符集
ASCII编码虽然解决了美国人信息交换的问题,由于ascii码只使用了8位表示了欧美国家的256字符,无法解决全世界信息交换的问题。
后来能让计算机支持全世界的文字就搞出来了一个unicode(字符集)俗成万国码。
Unicode最初使用了16位(2个字节)表示世界所有文字。16**2=65525个字符。也就是ucs2。
00000000 00000000
..................
11111111 11111111
后来随着计算机的普及16位仍然不够用后来扩展到了32位(4个字节)32**2=4294967296个字符。也就是ucs4(使用表情要使用ucs4)
00000000 00000000 00000000 00000000
......................................
11111111 11111111 11111111 11111111
无论是ucs2还是usc4都兼容可ASCII码,前1/3个字节使用0表示即可。
ucs2兼容ascii字符集
00000000 00000000
.................
00000000 11111111
ucs4兼容ascii字符集
00000000 00000000 00000000 00000000 ......................................
00000000 00000000 00000000 11111111
utf-8编码
Unicode发展到ucs4解决了全球信息交换的问题,但是美国人肯定不乐意,因为我原来使用1个字节就可以完成数据交换,但是现在需要4个,我的硬盘和带宽需要白白浪费3倍。
于是utf-8编码出现了。
utf-8是一种可变长的编码方法,体现在如果是英文字符可以继续按照8位1个字节进行编码。中文需要3个字节。
这种可变长是怎么实现的呢?
第一步:
utf-8把unicode(ucs4)字符集中的码位 划分为4个范围。
码位范围(十六进制) 转换模板
0000 ~ 007F 0XXXXXXX 根据字符在ucs4中码位,选择区间1,编码后 占用1个字节就行
0080 ~ 07FF 110XXXXX 10XXXXXX 根据字符在usc4中码位,选择区间2,编码后 占用2个字节就行
0800 ~ FFFF 1110XXXX 10XXXXXX 10XXXXXX 根据字符在usc4中码, 选择区间3,编码后 占用3个字节(汉字在区间3)
10000 ~ 10FFFF 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX 根据字符在usc4中码, 选择区间4,编码后 占用4个字节(表情包在这个区间)
例如: "B" 对应的unicode码位为 0042,那么他应该选择的一个模板。 "ǣ" 对应的unicode码位为 01E3,则应该选择第二个模板。 "武" 对应的unicode码位为 6B66,则应该选择第三个模板。 "沛" 对应的unicode码位为 6C9B,则应该选择第三个模板。 "齐" 对应的unicode码位为 9F50,则应该选择第三个模板。 注意:一般中文都使用第三个模板(3个字节),这也就是平时大家说中文在utf-8中会占3个字节的原因了。
第二步:
例如汉字:"武" 在ucs4字符集中的码位为6B66,则二进制为 0110101101100110
根据模板转换: 6 B 6 6 0110 1011 0110 0110 ---------------------------- 1110XXXX 10XXXXXX 10XXXXXX 使用第三个模板 11100110 10XXXXXX 10XXXXXX 第一步:取二进制前四位0110填充到模板的第一个字节的xxxx位置 11100110 10101101 10XXXXXX 第二步:挨着向后取6位101101填充到模板的第二个字节的xxxxxx位置 11100110 10101101 10100110 第二步:再挨着向后取6位100110填充到模板的第三个字节的xxxxxx位置 最终,"武"对应的utf-8编码为 11100110 10101101 10100110
最终,"武"对应的utf-8码为 11100110 10101101 10100110
package main import ( "fmt" "strconv" "unicode/utf8" ) func main() { //1.go语言中的字符串本质上是utf-8编码之后的字节序列 var name string="张根" fmt.Println(name) //utf-8的第三个模板:1110XXXX 10XXXXXX 10XXXXXX //张 utf-8码=根据张字符在Unicode的码位codepoint进行utf-8编码所得 1110【0101】 10【111100】 10【100000】 fmt.Println(strconv.FormatInt(int64(name[0]),2),strconv.FormatInt(int64(name[1]),2),strconv.FormatInt(int64(name[2]),2)) // //根 utf-8码=根据张字符在Unicode的码位codepoint进行utf-8编码所得 11100110 10100000 10111001 fmt.Println(strconv.FormatInt(int64(name[3]),2),strconv.FormatInt(int64(name[4]),2),strconv.FormatInt(int64(name[5]),2)) //2.获取字符串的长度其实就是获取utf-8编码之后字节的长度。即6个字节 fmt.Printf("字节长度%d\n",len(name)) //3.字符串转换成---》1个字节切片 byteSlice:=[]byte(name) fmt.Println(byteSlice) //4.字节切片转换成----》1个字符串 togetByteSlice:=[]byte{229,188,160,230,160,185} togetString:=string(togetByteSlice) fmt.Println(togetString) //5.rune也是golang里的1种数据类型:它表示的的是字符串在Unicode(ucs4)字符集中对应的码位 runeSet:=[]rune(name) //[24352 26681] fmt.Println(runeSet) //张 在Unicode字符集中的码位:二进制:10111110 0100000 十六进制:5f20 //utf-8编码的机制:(101)(111100)(100000)--->1110XXXX 10XXXXXX 10XXXXXX --->1110【0101】 10【111100】 10【100000】 fmt.Println(strconv.FormatInt(int64(runeSet[0]),2)) fmt.Println(strconv.FormatInt(int64(runeSet[0]),16)) //根 在Unicode字符集的的码位:二进制:11010000 0111001 十六进制:6839 fmt.Println(strconv.FormatInt(int64(runeSet[1]),2)) fmt.Println(strconv.FormatInt(int64(runeSet[1]),16)) //6.rune切片转换成字符串 runeList:=[]rune{24352,26681} fmt.Println(string(runeList)) //张根 //7.获取字符串的字面长度 牛魔王 3个长度 nickName:="牛魔王" runeLenth:=utf8.RuneCountInString(nickName) fmt.Println(runeLenth) }
Python2和Python3字符编码区别
Python2发明与utf-8之前,所以 python2的 字符串 例如 a="张根"
在解释器执行时 会默认以bytes(字节)的形式(assic编码后的数据)存储在内存中。
这就大家经常说到的Python2的编码问题。
python3 Unicode数据 例如 u"张根"
在解释器执行时,Unicode数据 以 Unicode的形式存储在内存。
python3程序中字符串 例如 a="张根",在解释器执行时,都是以Unicode的形式存储在内存中的;