|
| 1 | +# Chapter4. Text versus Bytes |
| 2 | + |
| 3 | +[TOC] |
| 4 | + |
| 5 | +## Text and Bytes |
| 6 | + |
| 7 | +### String Sequence |
| 8 | + |
| 9 | +**String is a sequence of unicode characters.** |
| 10 | + |
| 11 | +每一个 Unicode char 有一个 identity: code point,code point is a number from 0 to 111411. |
| 12 | + |
| 13 | +### Byte Sequence |
| 14 | + |
| 15 | +```python |
| 16 | +ss = b"nihao, i'm wansho" # 仅支持 ASCII 字符 |
| 17 | +ss = bytes(u"你好", encoding="utf-8") |
| 18 | +``` |
| 19 | + |
| 20 | +### 区别 |
| 21 | + |
| 22 | +```Python |
| 23 | +# in python3 |
| 24 | +ss = "1" # unicode |
| 25 | +s = b"1" # byte |
| 26 | + |
| 27 | +ss == s |
| 28 | +# false |
| 29 | +``` |
| 30 | + |
| 31 | + |
| 32 | + |
| 33 | +| | unicode sequence 定义 | byte sequence定义 | |
| 34 | +| ------- | --------------------- | --------------------------------- | |
| 35 | +| Python2 | `u("你好")` | ```"nihao" ``` | |
| 36 | +| Python3 | `"你好"` | `bytes("你好", encoding="utf-8")` | |
| 37 | + |
| 38 | +**Tips**: |
| 39 | + |
| 40 | +* Python2 的字符串**默认为 Byte 类型**,如果输入非 ASCII 字符,则自动编码为 byte |
| 41 | + |
| 42 | + ```python |
| 43 | + ss = "你好" |
| 44 | + print(ss) # \xe4\xbd\xa0\xe5\xa5\xbd |
| 45 | + ``` |
| 46 | + |
| 47 | +* Python3 中的字符串默认为 Unicode Sequence |
| 48 | + |
| 49 | +### Python3 中定义 byte sequence |
| 50 | + |
| 51 | +### Encode and Decode |
| 52 | + |
| 53 | +### 框架 |
| 54 | + |
| 55 | + |
| 56 | + |
| 57 | +**Tips**: |
| 58 | + |
| 59 | +* An encoding is an algorithm is an algorithm that converts code points to byte sequences and vice versa. |
| 60 | + |
| 61 | +### Basic Encoders and Decoders |
| 62 | + |
| 63 | +Python 中集成了 100+ 个 codec,实现 text 到 byte 的互相转换。常见的 codec 有:`utf-8, utf-16, gb2312` |
| 64 | + |
| 65 | +每一个 codec 都有一个名字,通常还有一些别名,例如:`utf_8` 对应的别名有 `utf8, utf-8, U8`。 |
| 66 | + |
| 67 | +**decode 出现乱码的问题** |
| 68 | + |
| 69 | +decode 出现乱码,大概率是因为 decode 和 encode 的 codec 不匹配,那么 encoded byte 对于 decode 来说就是噪声,而恰巧负责 deocde 的 codec 能够 decode random noise,所以才会 decode 出一些乱码。 |
| 70 | + |
| 71 | +### Python 源码默认的 Encoder |
| 72 | + |
| 73 | +| | Encoder | |
| 74 | +| ------- | ------- | |
| 75 | +| Python2 | ASCII | |
| 76 | +| Python3 | UTF-8 | |
| 77 | + |
| 78 | +这也是为什么,Python2 的源码,在文件开头需要加入 `# -*- coding: utf-8 -*-` 指定编码器。 |
| 79 | + |
| 80 | +Python3 允许 non-ASCII identifiers in source code,任何 Unicode 字符,都可以作为变量,也就是说,**中文也可以作为变量**! |
| 81 | + |
| 82 | +### 如何判断一个 byte sequence 的编码器: chardet |
| 83 | + |
| 84 | +结论:无法判断。 |
| 85 | + |
| 86 | +许多通信协议和文本的格式,例如 HTTP 和 XML,会在 headers 中明确指出其内容是如何 encode。大多数时候,我们能否判断 byte sequence 并不是 ASCII,因为其中有很多符号的 value > 127,但是我们可以通过经验,察觉到这些 byte sequence 是怎么编码得来的。 |
| 87 | + |
| 88 | +例如,如果 `\x00` 很常见,那么其很可能是 16bit 或 32bit 编码得来的,而不是 ASCII,因为 `\x00` 在 ASCII 中是一个 bug,当 byte sequence 中有较多的 b`\x20\x00` 时,那么这个符号很可能是编码规则 `utf-16le` 编码得到的空格。 |
| 89 | + |
| 90 | +所以,虽然理论上无法判断,但是我们还是可以通过经验来大概猜出来 byte sequence 的编码方式。package `chardet` 就是用来 detect codec 的。 |
| 91 | + |
| 92 | +### 大端/小端 — 有 BOM/无BOM |
| 93 | + |
| 94 | +#### 大端/小端 |
| 95 | + |
| 96 | +大端小端是指机器对于数据的存储方式,是先存储有效位,还是先存储无效位。 |
| 97 | + |
| 98 | +如果是先存储有效位,则是大端,如果先存储无效位,则是小端。 |
| 99 | + |
| 100 | +拿 C 语言的数据类型 int 类型举例,一个 int 类型在内存中占 4 个字节,例如 `int num = 1;`,其在内存中,应该存储为:`0x00 00 00 01`那么这 4 个字节,是 `00` 在前,还是 `01` 在前?如果 `01` 在前,即其在内存中的存储顺序为:`01000000`,则为小端(大多数 x86 机器都是小端),如果 1 在内存中的存储顺序为 `00000001`,则为大端。 |
| 101 | + |
| 102 | +**如何判断一个机器是大端还是小端** |
| 103 | + |
| 104 | +用 char 指针访问一个多字节的数值 1,例如 int 类型,然后访问该数在内存中的第一个字节,如果第一个字节的值为 1,则说明其在内存中存储的顺序为 `0100……`,为小端;如果第一个字节的值为 0,则说明其在内存中存储的顺序为 `00……01`,为大端。 |
| 105 | + |
| 106 | +```c |
| 107 | +#include<stdio.h> |
| 108 | +/** |
| 109 | + * 用于判断机器是大端机器还是小端机器 |
| 110 | +*/ |
| 111 | +void print_int(char* p){ |
| 112 | + // 一个字节一个字节的按照内存顺序打印内容 |
| 113 | + int len = sizeof(int); // 4 个字节 |
| 114 | + int i; |
| 115 | + for(i = 0; i < len; i++){ |
| 116 | + printf("%d", *p); |
| 117 | + p += 1; |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +void main(){ |
| 122 | + int num = 1; // int 占用4个字节 |
| 123 | + // 获取 num 的地址,然后转成 char* 指针类型,这样就保证了一个一个字节的访问 |
| 124 | + char* p = (char*)(&num); |
| 125 | + print_int(p); |
| 126 | + // 打印结果为 1000,其最先输出的是有效数字端,为小端 |
| 127 | + return; |
| 128 | +} |
| 129 | +``` |
| 130 | +
|
| 131 | +#### BOM (Byte Order Mark) / UTF-8 BOM 的由来 |
| 132 | +
|
| 133 | +从大端和小端我们可以知道,内存存储顺序,对于不同的机器是不一样的。Python 在对 Unicode 字符进行 encode 进而转换成 byte 时,不同的机器,其产生的 byte 顺序是不一样的。这就导致了数据在存储和传输的时候,出现即使是相同的编码规则,也解析出乱码的问题。 |
| 134 | +
|
| 135 | +为了解决机器带来的大端小端的问题,很多编码器会预先获取机器大小端顺序,然后在进行编码的时候,将顺序规则写入编码得到的 byte,这就是 BOM 的**由来**。由于只有在 encoder 为双字节编码的时候才会有顺序之分,所以照说 `utf-8` 生成的字节不需要加 BOM,但是很多 Windows 的应用(Notepad,Excel)会在编码的时候,将 BOM 加入 utf-8 编码的文件中,尤其是 Excel,其依赖于 BOM 来识别 utf-8 文件。由于 Windows 系统会在 utf-8 编码时,往文件中写入 BOM,所以 utf-8 编码才会衍生出:`utf-8 和 utf-8 BOM` 两种编码。 |
| 136 | +
|
| 137 | +## Unicode Sandwich |
| 138 | +
|
| 139 | +Python 采用 Unicode 模型来进行文本的处理。实际上,该模型的思想适用于大都数的系统。在系统输入时,将 byte decode 为 Unicode,在系统输出时,再将 Unicode encode 进行存储或传输。 |
| 140 | +
|
| 141 | + |
| 142 | +
|
| 143 | +注意: |
| 144 | +
|
| 145 | +* 当我们在读写文件的时候,如果没有指定编码,那么 Python 会默认使用系统的编码,类 Unix 系统的默认编码都是 utf-8,而 Windows 系统的默认编码并不是 utf-8. |
| 146 | +* 由于不同的系统,默认的编码规则不同,所以我们应该在代码文件中明确指定编码 |
| 147 | +
|
| 148 | +## Tips for 文件读写代码 |
| 149 | +
|
| 150 | +见注释: |
| 151 | +
|
| 152 | +```python |
| 153 | +ss = "hello万朔" |
| 154 | +# 1. encoding 是为了指定存储的编码格式,编码后存储的文件,可以节省存储资源。 |
| 155 | +# 2. 读和写的 encoding 要一致 |
| 156 | +# 3. encoding 不能省略,否则 Python 会使用当前操作系统默认的编码,而不同的操作系统,其默认编码可能不同 |
| 157 | +fw = open("test.txt", "w", encoding="utf-8") |
| 158 | +print(fw.write(ss)) # write 和 read 会返回读取的长度,打印 7,表明写入了 7 个 Unicode 字符 |
| 159 | +fw.close() |
| 160 | +
|
| 161 | +import os |
| 162 | +print(os.stat("test.txt").st_size) # 返回 11,表示 test.txt 的内容(大小)为 11 个字节,也就是说 test.txt 为 11Byte,而实际上,该文件在 Windows 上显示也是 11 个字节大小。 |
| 163 | +fr = open("test.txt", "rb") # rb 为 read binary,该模式下读取的内容为字节流,也就是说,如果我们想要分析一个文件的字节组成,可以,另外,注意 binary 模式下,不需要指定 encoder |
| 164 | +print(fr.read()) |
| 165 | +# b'hello\xe4\xb8\x87\xe6\x9c\x94',就像结果一样,确实有 11 个字节,并且显示的确实是 byte 字节格式 |
| 166 | +``` |
| 167 | + |
| 168 | +注意: |
| 169 | + |
| 170 | +* 如果我们想要探究一个文件的字节,在简单的情境下,可以使用 `binary` 模式进行读取,但是如果上升到了业务,就不需要自己动手造轮子了,应该使用现成的轮子 **Chardet**,Chardet 是探究文件 Byte 的利器。 |
| 171 | + |
| 172 | +## byte in RE |
| 173 | + |
| 174 | +Python3 中,可以强制指定进行 byte 的匹配,这样只能匹配到 ASCII。 |
| 175 | + |
| 176 | +```python |
| 177 | +import re |
| 178 | +re.compile(rb"\d") # b 代表进行 byte匹配 |
| 179 | +``` |
| 180 | + |
| 181 | +## 本章学习感悟 |
| 182 | + |
| 183 | +本章学习的知识,让我把 |
| 184 | + |
| 185 | +* C 语言的 char* 指针 |
| 186 | +* 计组的字节/大端小端 |
| 187 | +* Python 的字符和字节 / BOM 和无 BOM |
| 188 | + |
| 189 | +等知识串在了一起。 |
0 commit comments