从零开始理解字符串编码

2018-04-19 08:01:57 +08:00
 Windsooon

字符串编码可以说新手必碰壁,我以前看了很多文章还是感觉朦朦胧胧,所以这篇文章我从字符串存储开始介绍字符串编码的实现,希望可以帮到各位。一文理解字符串编码

2610 次点击
所在节点    问与答
10 条回复
jingniao
2018-04-19 08:38:50 +08:00
其实弄清楚
字节
字符
编码
解码
之间的关系,应该好理解好多。
然后对于各个语言,对字符串,到底是默认 unicode 还是 bytes
faemon
2018-04-19 08:48:31 +08:00
新手弄不清楚的话的确很懵,这篇文章挺好的
sundev
2018-04-19 10:19:25 +08:00
现在基本都是 utf8 一统江湖了吧。
但是有时候真出现乱码问题,真要解决必须得懂得这些知识,还要熟知各个环节(语言、服务、数据库、文件等)获取到的数据的编码是什么,对能力是很大的考验。
mortonnex
2018-04-19 10:30:03 +08:00
牛!
di94sh
2018-04-19 11:14:39 +08:00
其实很简单的逻辑,字符组成字符集,比如 gbk unicode 等等字符集。然后现在世界上个语言字符收录最全的,应用最广的是 Unicode 字符集。然后实现一个字符集有很多种编码方式,utf-8 utf-16 等等 都是一些实现 unicode 字符集的编码方式 二进制 转到 Unicode

然后在编码里 你要明白你写的语言的字面到底是二进制,还是 Unicode 比如 python

python2 字符串字面上是二进制的 然后你如果转成 Unicode 的 python2 的默认转码方式是 assic 只支持英文,数字,标点,等字符。
所以在处理非英语的时候会出错误,你在转吗的时候指定 utf-8 就好了。
python3 字符串字面上是 Unicode 的 然后 python3 的默认编码方式是 utf-8,所以你转吗的时候可以使用默认编码

最后 二进制 → Unicode 用 decode 解码
Unicode → 二进制 用 encode 编码
hxndg
2018-04-19 13:40:15 +08:00
很不错的文章,提几点建议:
1去掉那几张巨大而且不相关的图片。
2加上不同语言中对于字符的默认处理和转换,比方说 python2.7, python3, ruby 等。
3提及计算机大端小端和网络上的大端小端。
Windsooon
2018-04-19 14:50:38 +08:00
@di94sh 说的没错,主要要理解 Unicode 是一种规范而不是具体的存储方式
Windsooon
2018-04-19 14:52:35 +08:00
@hxndg 谢谢,第一点我会换小一点图片的,哈哈,第二点我会在常用问题增加的,第三点的话我不确定需不需要添加,我怕会让读者更加困惑。
Sylv
2018-05-12 06:16:22 +08:00
文章很不错。

不过挑几个 Python 2 相关的错,或者说是表述不准确的地方:

1--

「 python2 是把字符串直接经过 utf-8 编码保存的」
「出错的原因是因为 python2 当遇到 ASCII 表中没有的字符的时候,默认会把它们使用 utf-8 编码来存储」

其实并不是这样的,Python 2 并没有默认用 utf-8 编码来保存字符串,实际上在终端下输入的字符串的编码是由终端的编码决定的,也就是说终端用的是什么编码,Python 接收到的字符串就是什么编码。你这句话最多只能适用于 Mac 和大多数 Linux 系统等,因为它们终端的默认编码是 utf-8,所以在终端下输入 "你好",Python 接收到的是 utf-8 编码的 '\xe4\xbd\xa0\xe5\xa5\xbd'。但是在 Windows 下就不是这样了:
>>> '你好'
'\xc4\xe3\xba\xc3'

结果并不是 utf-8 编码的 '\xe4\xbd\xa0\xe5\xa5\xbd'。这是因为中文 Windows 系统 cmd 的默认编码是 cp936 (GBK),而不是 utf-8。可以通过 sys.stdin.encoding 来查看这个编码:
>>> import sys
>>> sys.stdin.encoding
'cp936'

所以在这种情况下输入 "你好",Python 接收到的二进制数据会是 '\xc4\xe3\xba\xc3',也就是 "你好" 的 cp936 编码:
>>> u'你好'.encode('cp936')
'\xc4\xe3\xba\xc3'

这个道理和 Python 读取文件内字符串是一样的,文件是什么编码,Python 读取到的字符串就是什么编码。Python 2 并没有默认使用 utf-8 编码来保存或读取字符串,而是由输入端(文件、终端等)的编码决定了 Python 2 获取到的字符串的编码。

2--


>>> '你好'.encode('gb2312')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

出错的原因是因为 python2 当遇到 ASCII 表中没有的字符的时候,默认会把它们使用 utf-8 编码来存储,但是 gbk 编码表无法对 utf-8 编码进行解码,这句话你可能需要读完文章才能理解。


这个例子里出错的原因并不是 gbk 编码表无法对 utf-8 编码进行解码。注意看这里报的错是 UnicodeDecodeError: 'ascii' codec can't decode,说的是 Python 无法用 ascii 编码来解码 '你好',和 gbk (gb2312) 编码没关系。而且 encode 是执行编码操作不是解码操作,你是在用 gb2312 对字符串进行编码,不是你文中说的「 gbk 编码表……进行解码」。

这里出错的真正原因是:你在 Python 2 下对 str 类型的字符串进行了 encode 操作,encode 操作只对 unicode 类型字符串才有意义( Python 3 里甚至不允许对非 unicode 字符串 (bytes) 进行 encode 操作,也不允许对 unicode 字符串 (str) 进行 decode 操作),所以 Python 2 在这里会隐式地尝试将 str 类型的 '你好' decode 为 unicode 类型字符串后再进行你要的 encode,这个隐式的 decode 操作使用的编码是 Python 2 的默认编码—— ascii,Python 的默认编码可以通过以下命令查看:
>>> import sys
>>> sys.getdefaultencoding()
'ascii'

所以执行 '你好'.encode('gb2312') 时相当于执行了:
'你好'.decode('ascii').encode('gb2312')

ascii 无法解码中文,所以报了 UnicodeDecodeError 错,此时还没有真正执行 encode('gb2312'),所以例子和你的解释并不适用。

正确的例子应该是:
>>> '\xe4\xbd\xa0\xe5\xa5\xbd'.decode('gbk')
u'\u6d63\u72b2\u30bd'
>>> u'\u6d63\u72b2\u30bd' == u'你好'
False

不用 '你好'.decode('gbk') 来举例是因为正如上文 1 所说,这段代码在中文 Windows 系统下是没有编码错误的:
>>> '你好'.decode('gbk') == u'你好'
True

而 '\xe4\xbd\xa0\xe5\xa5\xbd'.decode('gbk') 这个例子你会发现 Python 2 是能顺利运行没有报错的,并不像你说的「 gbk 编码表无法对 utf-8 编码进行解码」那样出现 UnicodeDecodeError 错误。甚至这个结果是能直接 print 出来的:
>>> print('\xe4\xbd\xa0\xe5\xa5\xbd'.decode('gbk'))
浣犲ソ

这是因为 '你好' 的 utf-8 编码和 '浣犲ソ' 的 gbk 编码是重合的,都是 '\xe4\xbd\xa0\xe5\xa5\xbd':
>>> u'你好'.encode('utf-8')
'\xe4\xbd\xa0\xe5\xa5\xbd'
>>> u'浣犲ソ'.encode('gbk')
'\xe4\xbd\xa0\xe5\xa5\xbd'

所以 gbk 编码表其实是可以对部分 utf-8 编码的字符串进行解码的,只是解码的结果不是我们想要的,也就是出现了乱码问题。

3--


另外常见编码错误就是使用错误的编码保存字符串,例如使用 ASCII 表保存”你好”,因为 ASCII 表里面没有对应的字符,它不知道如何保存。

>>> '你好'.encode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)


这个例子应该是在 Python 3 下执行的结果,'你好' 得是 unicode 类型字符串对其 encode('ascii') 才会报 UnicodeEncodeError 错误,否则在 Python 2 下应该如 2 的例子一样报的是 UnicodeDecodeError 错误:
>>> '你好'.encode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

也就是相当于执行了:
'你好'.decode('ascii').encode('ascii')

正确的例子应该是:
>>> u'你好'.encode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

--

我以前也写过篇和 Python 2 中文 Unicode 编码问题有关的文章,供你参考:
https://www.v2ex.com/t/163786
Windsooon
2018-05-12 12:02:24 +08:00
@Sylv 你说的很有道理,我从你的评论里面也学到很多,原本的文章确实有错误和不严谨的地方。我会在两天内根据你的建议修改好的,你的文章写得很好。谢谢你的回复。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/448013

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX