文本与字节

理清基本概念

字节

字节(Byte) 是一串二进制序列,例如:“0100 0001”表示英文字母 A,1 Byte = 8 Bit(二进制位);字节序列就是连续的多个字节,同理,下文中出现的二进制序列也是指将字节序列换算成二进制的序列

字符

字符(Character)是一个信息单位;

  1. 不同国家使用不同的语言,不同语言使用不同的文字符号:汉语、英语
  2. 类比汉语中的汉字、标点符号;英语中的字母、数字、符号等

编码

  1. 为什么要进行编码?
  • 计算机唯一可以存储的是比特(位),因此想要在计算机上处理信息,就必须要把他们按位存储。
  1. 如何编码?
  • 文本—>数字,需要构建一种系统为每个字母赋予一个唯一的编码
  • 数字和标点符号也算作文本的一种形式,他们也需要拥有自己的编码
  • 这种系统成为字符编码集。
  • 把文本看做是由字母、数字和标点符号组成的数据流

进制转换

在数字后面加上不同的字母来表示不同的进位制。B(Binary)表示二进制,O(Octal)表示八进制,D(Decimal)或不加表示十进制,H(Hexadecimal)表示十六进制。

二进制转换为十进制

方法:二进制数从低位到高位(即从右往左)计算,第0位的权值是2的0次方,第1位的权值是2的1次方,第2位的权值是2的2次方,依次递增下去,把最后的结果相加的值就是十进制的值了。
:::tips
例:将二进制的(101011)B转换为十进制的步骤如下:
1. 第0位 1 x 2^0 = 1;
2. 第1位 1 x 2^1 = 2;
3. 第2位 0 x 2^2 = 0;
4. 第3位 1 x 2^3 = 8;
5. 第4位 0 x 2^4 = 0;
6. 第5位 1 x 2^5 = 32;
7. 读数,把结果值相加,1+2+0+8+0+32=43,即(101011)B=(43)D。
:::

十进制转换为二进制

方法:除2取余法,即每次将整数部分除以2,余数为该位权上的数,而商继续除以2,余数又为上一个位权上的数,这个步骤一直持续下去,直到商为0为止,最后读数时候,从最后一个余数读起,一直到最前面的一个余数。
:::tips
例:将十进制的(43)D转换为二进制的步骤如下:
1. 将商43除以2,商21余数为1;
2. 将商21除以2,商10余数为1;
3. 将商10除以2,商5余数为0;
4. 将商5除以2,商2余数为1;
5. 将商2除以2,商1余数为0;
6. 将商1除以2,商0余数为1;
7. 读数,因为最后一位是经过多次除以2才得到的,因此它是最高位,读数字从最后的余数向前读,101011,即(43)D=(101011)B。
:::

十进制转换为十六进制

方法1:除16取余法,即每次将整数部分除以16,余数为该位权上的数,而商继续除以16,余数又为上一个位权上的数,这个步骤一直持续下去,直到商为0为止,最后读数时候,从最后一个余数起,一直到最前面的一个余数。
:::tips
例:将十进制的(796)D转换为十六进制的步骤如下:
1. 将商796除以16,商49余数为12,对应十六进制的C;
2. 将商49除以16,商3余数为1;
3. 将商3除以16,商0余数为3;
4. 读数,因为最后一位是经过多次除以16才得到的,因此它是最高位,读数字从最后的余数向前读,31C,即(796)D=(31C)H。
:::

方法2:使用间接法,先将十进制转换成二进制,然后将二进制又转换成十六进制;

方法:取四合一法,即从二进制的小数点为分界点,向左(向右)每四位取成一位,接着将这四位二进制按权相加,然后,按顺序进行排列,小数点的位置不变,得到的数字就是我们所求的十六进制数。如果向左(向右)取四位后,取到最高(最低)位时候,如果无法凑足四位,可以在小数点最左边(最右边),即整数的最高位(最低位)添0,凑足四位。
例:将二进制的(11010111)B转换为十六进制的步骤如下:
1. 0111 = 7;
2. 1101 = D;
3. 读数,读数从高位到低位,即(11010111)B=(D7)H。

Ref:
二、八、十、十六进制转换(图解篇) - 听风吹雨 - 博客园

ASCII

ASCII (American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套计算机编码系统。它主要用于显示现代英语。

  1. 标准 ASCII 以1字节(8 Bit) 为单位对字符进行编码
  2. 只能表示 256 种符号,包括大小写英文字母 a-zA-z、数字 0-9、标点符号以及 33 个非打印字符(不能显示或者打印出来),比如回车、Tab 字符
  3. 举例:

image.png
注:20h 代表空格符,它的作用是将单词或句子隔开
image.png
大写字母和一些附加的标点符号
小写字母和一些附加的标点符号

  1. 举个例子:

Hello, you!
转换成 ASCII 码,用十六进制数表示:
48 65 6C 6F 2C 20 79 6F 75 21
这段编码中,除了普通的字符,逗号(编码 2C),空格(编码 20)和感叹号(编码 21)容易遗漏,需要额外注意。
I am 12 years old.
它的 ASCII 码表示为:
49 20 61 6D 20 31 32 20 79 65 61 72 73 20 6F 6C64 2E
在这段编码串种,这里数字 12 被表示为十六进制数 31h 和 32h,也就是数字 1 和数字 2 的组合。所以说,当数字 12 以文本流的身份出现时,不应该表示为十六进制数 01h 和 02h,应为这些编码在 ASCII 码表中表示其他的意思。

  1. 然而亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右,简体中文常见的编码方式是 GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示 256 x 256 = 65536 个符号。

image.png

Unicode

Unicode是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码,使得计算机可以用更为简单的方式来呈现和处理文字

编码方式

  1. Unicode编码方式为: U+hhhh

(1)其中h代表一个16进制数字,理论上需要占用2个字节的空间,但基于各种扩展考虑用4个字节来表示一个字符。
(2)本质上就是给每一个字符一个正整数,这个正整数的范围在 0 到 1,114,111,可以容纳100多万个符号。这些正整数被称作码点,即 code points。
(3)Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

  • 英语大写字母 A 的码点是 65,用十六进制表示为 U+0041
  • 汉字 严 的码点是 20005,用十六进制表示为 U+4E25
  • 表情 😂 的码点是 128514,用十六进制表示为 U+1F602
  1. Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称为UTF)

UTF-8

  1. UTF-8是Unicode标准的一种实现方式,不是唯一,其他实现方式还有 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。

编码规则

UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8 的编码规则很简单,只有二条:

  1. 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
  2. 对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

为什么出现上述规则

  1. 方便指挥计算机的运行

目前的计算机只能存储和处理二进制序列,因为字节是计算机数据结构的重要组成单位,因此也可以视为字节序列;而人类读写文本最方便的是字符串序列;因此,为了方便人类与计算机的对话,必需约定某种二进制序列到字符串序列的转换规则,即编码标准,包括ASCII,UTF-8等。比如在UTF-8标准中,‘’11101000 10110100 10011101” 代表了中文“贝”, “11100101 10100011 10110011” 代表了 “壳”, 遗憾的是ASCII并不支持中文编码,即无法用ASCII方式将中文翻译成二进制序列。

  1. 标准的制定不是一蹴而就的

由于计算机的发展并不是一开始就面向全世界的,编码规范也是经历了各种版本的各种修改。
ASCII对中文不适用,那是自然啦,毕竟当初仅仅是在美国使用。全球化之后,计算机技术作为重要的输出,ASCII显得力不从心了,因为它规定了一个字符必须由 8 bit(1 Byte) 来表示,即最多表示256个字符, 面对大千世界的无数字符,ASCII标准无法胜任,然后就有了后来的Unicode。

编码无处不在

要想跟计算机交流,必须经过编码这一关。从用键盘敲出一个字符开始,编码工作就开始了!我们能看到屏幕上的字符只是强大的UI系统为了方便人的认知,背后计算机所记下的都是一串串的二进制序列。想想早期计算机的穿孔纸带,那种交互才是噩梦。所以不要以为机器懂你输入的字符理所当然,背后经过了多少人的努力和计算机不知疲倦地帮你翻译。
开发过程中出现编码问题的地方可以分为以下几种:
1)终端或编辑器
这里出现的编码问题最有迷惑性。我们在终端或者编辑器里输入一个字符,显示出来也是跟我们认知一样的字符;但在其背后已经被翻译成了计算机语言,即经过了编码,成为了字节序列。这也是我们经常debug时遇到的困扰:明明在终端显示正常,怎么就执行不通过!
2)源码
源码无疑会被计算机存储下来,用任何一种编辑器进行开发时都会经过编码才会成为计算机理解的二进制序列。所有总会有默认或指定的编码格式。
Python2文件开头的

# coding=utf-8

就是告诉解释器,源码文件中的所有字符都是以utf-8格式编码的。(下文中所有未注明编码格式的python2代码均指定了utf-8编码)
3)数据
同源码文件,数据文件也会涉及编码,但普通的文本文件没有编码声明标准,因此要想使用非默认编码也就必须要在程序中指定编码格式。
4)变量
我们遇到问题最多的情况应该就是发生在变量的编码上。从编码角度来看,变量的值来源可以使源码,也可以是文件,也就是变量可能与源码文件或数据文件有关。我们遇到的大部分编码问题都会出现在变量上,而且不易觉察。

最佳实践

既然有了统一的编码标准,那么大家都依照同一个标准,然后将标准交给语言底层实现不就可以了?即我们只想处理字符(因为unicode与字符是一一对应的,也可以认为是unicode),不要让我们困扰于编码问题。
image.png
因此Unicode 三明治原则就被提了出来:
1)要尽早地将输入的字节序列解码成字符串;
2)中间过程不要进行编解码,专心处理字符串;
3)输出时要尽量晚地编码成字节序列。
Python 3中很巧妙地遵循了这个原则(本部分代码均为python 3环境):
1) python 3 默认UTF-8 编码, 不再需要显式地指定源码的编码格式就可以支持所有字符,而不仅仅限于英文。

import sys
print(sys.getdefaultencoding())

Python 3 中,u’xxx’ 表示的不再是unicode类型,而是str(其实python 3中已没有unicode类). 而且str 的长度即字符的数目,更贴近“所见即所得”,方便认知。而且要想处理字节序列,还是有bytes可以用的。用 bytes 的时候才需要注意编码问题,因此bytes实例化必须要有encoding参数来指定编码格式。

BOM

在某些可以指定编码格式的编辑器里经常可以看到如 “有BOM UTF-8 编码” 和 “无 BOM UTF-8编码”(如notepad++)的编码格式。这里的BOM是什么意思呢?为什么UTF-8会有不止一种编码?
BOM,即字节序标记(byte-order mark),位于字节流的开头,用于标记此字节流的字节序。
BOM源于在UTF-16中,2字节作为"一组"来存储,存在大尾和小尾两种字节序,因此需要BOM来指定是哪种字节序。
对于UTF-8,是以1 字节为“一组”来存储的,也就不存在大小尾的问题,但它仍然支持了BOM,UTF-8中BOM占字节流开头的三个字节。这里BOM的作用仅仅用来标明这是一个UTF-8字节序列。

参考

https://mp.weixin.qq.com/s/Hugy5lt_hyzUef3ttM7gBQ
https://houmin.cc/posts/3d78c93b/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

留白YoLo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值