目录

    背景: 刚做完一个django的数据查询web项目,数据来源于内部API查询,每次查询都需要调用若干API查询数据渲染在前端页面。由于,相关的数据不会经常变动,为了提高前端响应速度、在API不可用时依然能够查询,设计了缓存。API查询到的数据是json格式返回,缓存的数据是MySQL的Unicode编码,数据由此产生了两个来源:API和MySQL,导致了编码的错误。

    1. 编码格式

    1.1 ASCII

    计算机只能处理0和1两种数字,为了让计算机能够处理文本信息(也就是文字字符串),就需要对这些信息进行编码。最早的计算机编码格式是ASCII码,采用8个bit表示一个字母,定义了128个字符,其中33个字符无法显示。8个bit位最多只能表示255个字符,只够英文国家使用,对全世界显然是不够的。

    1.2 GB2312

    为了满足计算机处理中文字符的需要,中国发布了GB2312编码规范,采用两个字节,16个bit表示一个图形字。收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符。

    1.3 GBK

    由于GB2312没有覆盖到全部汉字,一些古汉语字形和特殊的字符,GBK采用两个字节,16个bit表示一个图形字,同时完全兼容GB2312。GBK一共收录了21886个汉字和图形符号

    1.4 Unicode

    各个国家为了计算机能够处理本国语言,各自制定编码规范,不可避免会有各种各样的冲突。为了同一编码,出现了Unicode编码,采用多字节编码。Unicode的标准在不断发展,常见的是采用两个字节,16个bit表示一个字符的,如果是不常见的字符,也可以扩展到多个字节。

    1.5 UTF-8

    为了能表示更多的字符,Unicode采用长字节编码,这样会造成存储和带宽的浪费。UTF-8应运而生,采用边长的编码方式,实际上UFT-8是Unicode规则字库的一种实现形式。如果一个字节的首位是0,那么就表示一个字符,如果一个字节的首位是1,那么继续扩展一个字节编码判断。这种编码方式使UTF-8得到迅速推广。

    1.6 各种编码的使用场景

    ASCII:适用于英文的使用环境 GB2312、GBK:适用于中文字符编码 Unicode:通用,常作为中间编码,转换其他编码 UTF-8:节约存储空间和宽带资源,应用非常广

    2. Python的编码

    Python默认脚本的编码方式是ASCII,如果使用了非ASCII的字符,通常会在第一行或第二行指定编码方式,# -*- coding=utf-8 -*- 或者 #coding=utf-8,当然也可以采用其他字符集,gbk等,但强烈建议保持utf8。

    2.1 字典类型

    字典是可变容器模型,可存储任意类型对象。键必须是唯一的,但值不必。格式如下:

        d = {key1 : value1, key2 : value2 }
    

    在ipython下执行,返回类型:

        In [1] :dic_a = {'name':'tom'}
        In [2] :type(dic_a)
        Out [2] :dict
        In [3] :dic_a['name']
        Out [3] :'tom'
    

    2.2 列表和元组

    列表的数据项不需要具有相同的类型,列表是使用中括号中逗号分割单元定义的,列表的元素允许相同。格式如下:

     list= ['value1', 'value2', num1, num1]
    

    在ipython下执行:

        In [1] :list_a = ['a','b',1,2]
        In [2] :type(list_a)
        Out [2] :list
        In [3] :list_a[0]
        Out [3] :'a'
    

    元组是使用圆括号中逗号分割单元定义,但是元组的内容是不允许改变的。格式如下:

     tuple= ('value1', 'value2', num1, num1)
    

    在ipython下执行:

        In [1] :tuple_a = ('a','b',1,2)
        In [2] :type(tuple_a)
        Out [2] :tuple
        In [3] :tuple_a[0]
        Out [3] :'a'
    

    2.3 Python的字符数据类型

    Python中有两种字符串类型,分别是 str 类型(8 bit的序列)和 unicode类型(每个unit是一个unicode对象)。当Python读取一个文本内容时,保持的对象为str类型。而以Unicode编码的字符串需要用u’’表示。

        In [1] :u'我'
        Out [1] :u'\u6211'
        In [2] :u'我'.encode('gbk')
        Out [2] :'\xce\xd2'
        In [3] :u'我'.encode('utf8')
        Out [3] :'\xe6\x88\x91'
    

    上面编码使用的十六进制显示,使用print可以直接打印出表示的字符。可以看到字符一样,不同的编码存储开销是不一样的。

    2.4 JSON数据

    json字符串实际上是字符串,只不过它是由单引号包裹的,但是必须符合一定的字符规则。这里有四条:

    • 并列的数据之间用逗号(”, “)分隔
    • 映射用冒号(” : “)表示
    • 并列数据的集合(数组)用方括号(“[]”)表示
    • 映射的集合(对象)用大括号(”{}”)表示

    json模块提供了两个函数 json.dumps() 和json.loads() 来编码和解码json数据。使用起来非常简单,需要注意的是dumps之后,tuple会变成list,loads之后不会完全恢复。json模块还提供dump和load函数,应用与需要将json存储到文件、套接字,而当做字符串处理时,使用dumps和loads函数。还有一点需要注意,loads会对字符串进行Unicode的编码,如果需要保持原编码,请使用ast.literal_eval。 在python下执行,返回类型:

        In [1] :import json
        In [2] :obj_a={'name':'tom','age':20,'position':(1,2,3)}
        In [3] :json_a = json.dumps(obj_a)
        In [4] :json_a
        Out [4] :'{"position":[1,2,3],"age":20,"name":"tom"}'
        In [5] :type(json_a)
        Out [5] :str
        In [6] :obj_b=json.loads(json_a)
        In [7] :obj_b
        Out [7] :{u'age': 20, u'name': u'tom', u'position': [1, 2, 3]}
        In [8] :type(obj_b)
        Out [8] :dict
        In [9] :ast.literal_eval(json_a)
        Out [9] : {'age':20,'name':'tom','position':[1,2,3]}
        In [10] :type(ast.literal_eval(json_a))
        Out [10] : dict
    

    同时,Python还提供ord()和chr()两个函数对字母和数字相互转换。

    3. MySQL的字符编码

    3.1 基本概念

    MySQL字符集包括字符集和校对规则两个概念。字符集定义MySQL存储字符串的方式,校对规则定义比较字符串的方式。字符集和校对规则是一对多的关系,MySQL支持30多种字符集的70多种校对规则。

    3.2 校对规则选择

    使用utf8_general_ci的速度快,而使用utf8_unicode_ci比较准确。 utf8_unicode_ci的准确主要体现德语和法语上,对于一般的国内应用场景,建议使用general规则就足够。另外,由于unicode和general对大小写都不敏感,utf8_bin_ci也有一定的应用场景。

    3.3 注意事项

    字符集通常直接选择utf8编码就足够。但是在有Emoji表情存储需求时会出现错误。MySQL的utf8编码最多3个字节,而Emoji表情是4个字节。为了能够存储Emoji,应该选用utf8mb4字符集。

    4. 参考