V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
Windsooon
V2EX  ›  问与答

从零开始理解字符串编码

  •  
  •   Windsooon · 2018-04-19 08:01:57 +08:00 · 2573 次点击
    这是一个创建于 2401 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    10 条回复    2018-05-12 12:02:24 +08:00
    jingniao
        1
    jingniao  
       2018-04-19 08:38:50 +08:00
    其实弄清楚
    字节
    字符
    编码
    解码
    之间的关系,应该好理解好多。
    然后对于各个语言,对字符串,到底是默认 unicode 还是 bytes
    faemon
        2
    faemon  
       2018-04-19 08:48:31 +08:00 via iPhone
    新手弄不清楚的话的确很懵,这篇文章挺好的
    sundev
        3
    sundev  
       2018-04-19 10:19:25 +08:00
    现在基本都是 utf8 一统江湖了吧。
    但是有时候真出现乱码问题,真要解决必须得懂得这些知识,还要熟知各个环节(语言、服务、数据库、文件等)获取到的数据的编码是什么,对能力是很大的考验。
    mortonnex
        4
    mortonnex  
       2018-04-19 10:30:03 +08:00
    牛!
    di94sh
        5
    di94sh  
       2018-04-19 11:14:39 +08:00 via Android
    其实很简单的逻辑,字符组成字符集,比如 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
        6
    hxndg  
       2018-04-19 13:40:15 +08:00
    很不错的文章,提几点建议:
    1去掉那几张巨大而且不相关的图片。
    2加上不同语言中对于字符的默认处理和转换,比方说 python2.7, python3, ruby 等。
    3提及计算机大端小端和网络上的大端小端。
    Windsooon
        7
    Windsooon  
    OP
       2018-04-19 14:50:38 +08:00
    @di94sh 说的没错,主要要理解 Unicode 是一种规范而不是具体的存储方式
    Windsooon
        8
    Windsooon  
    OP
       2018-04-19 14:52:35 +08:00
    @hxndg 谢谢,第一点我会换小一点图片的,哈哈,第二点我会在常用问题增加的,第三点的话我不确定需不需要添加,我怕会让读者更加困惑。
    Sylv
        9
    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
        10
    Windsooon  
    OP
       2018-05-12 12:02:24 +08:00 via iPhone   ❤️ 1
    @Sylv 你说的很有道理,我从你的评论里面也学到很多,原本的文章确实有错误和不严谨的地方。我会在两天内根据你的建议修改好的,你的文章写得很好。谢谢你的回复。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1473 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 23:48 · PVG 07:48 · LAX 15:48 · JFK 18:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.