在编程的过程当中,常常会遇到莫名其妙的乱码问题。很多人选择出了问题直接在网上找答案,把别人的例子照搬过来,这是快速解决问题的一个好办法。然而,作为一个严谨求实的开发者,如果不从源头上彻底理解乱码产生的机制,并由此寻求解决问题的根本路径,那么永远不能从码农的阴影中摆脱出来。下面就来一起了解一下计算机编码问题的来龙去脉。

ASCII

众所周知,计算机中的所有数据,不论是文字、图片、视频、还是音频文件,本质上最终都是按照类似 01010101 的二进制形式存储的。然而,计算机中的字符,并不能完全以这种方式来表示。由于计算机最初是由美国人发明的,因而最初的计算机编码使用的也是美国人的标准,即ASCII( American Standard Code for Information Interchange,美国信息交换标准代码)。ASCII码一共规定了128个字符的编码,比如大写的字母A是65(二进制01000001),符号@的编码是64(二进制01000000)。这128个符号中, 0~31及127(共33个)是控制字符或通信字符,32–126 分配给了能在键盘上找到并且能打印出来的字符。所有ASCII编码表示的内容,只占用了一个字节的后面7位,最高位统一规定为0。

后来为了能够表示欧洲地区除了英文字母以外的其它字母,出现了扩展的ASCII编码。 扩展的ASCII包含原有的128个字符,又增加了128个字符,总共是256个。编码时最高位为1,这样就可以与ASCII码完全兼容。可以表示诸如音标æ(编码145,二进制10010001)以及法语中的字母é(编码为130,二进制10000010)等字符。

这个编码能表示音标和欧洲大多数非英语系字母,但是它并不是国际标准,在不同的国家, 128 到 255对应的字符并不完全相同,这就产生了各种不同的扩展ASCII编码。比如 ISO8859-1 字符集,也就是 Latin-1,加入了西欧常用字符,包括德法两国的字母。ISO8859-2 字符集,也称为 Latin-2,收集了东欧字符。 ISO8859-3 字符集,也称为 Latin-3,收集了南欧字符,等等。

这样的编码方式够吗?显然不够,比如汉字,就无法用ASCII表示。扩展的ASCII 也远远不够。

GBK

中国人为了能够正常使用计算机这一伟大方明,做出了多方面的努力。GB2312就是这一努力的成果, 该标准于1980年发布,1981年5月1日开始实施。它标志着我国在使用电子计算机方面迈出了重要的一步。GB2312 编码共收录了6763个汉字,同时还兼容 ASCII。这一字符编码基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率,对一些古汉语和繁体字 GB2312 没法处理。后来就在GB2312的基础上创建了一种叫 GBK 的编码,于1995年正式发布。GBK 不仅收录了GB 2312 中的全部汉字、非汉字符号,同时还收录了日韩语中出现的汉字,如韩国著名围棋手李世乭中的乭 GBK编码是0x8168(0x表示16进制)。这里可以查询汉字对应的GBK编码。

GBK编码一般用两个字节表示一个字符,如果是英文字母,则使用一个字符,与ASCII编码相同,因此,GBK 也是兼容 ASCII 编码的,但并不与任何扩展的ASCII编码兼容。这可以从它的编码序列看出来。

GBK 采用双字节表示,总体编码范围为 0x8140-0xFEFE(1000000101000000-1111111011111110),首字节在 0x81-0xFE 之间,尾字节在 0x40-0xFE之间。可以看出首字节最高位都为1,这样一来,如果尾字节后的字节最高位为0,那么就可以解析为一个ASCII编码字符,否则就是一个连续的二字节字符。

Unicode

世界上存在着多种语言,有没有一种编码方式能够囊括所有语言中的字符呢?答案是有。Unicode编码正是为了满足这种需求制定的。Unicode是一个很大的集合,目前的规模可以容纳100多万个符号。每个符号的编码都不一样,这么多的字符,想要以二进制形式表示,就需要比较多的字节才能够一一对应。标准的Unicode采用4个字节表示一个字符串。这个四字节的二进制代码,称为这个字符的码点。比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+ 4E6D表示汉字"乭 "。访问unicode.org可以查询具体的符号对应表。

使用4个字节表示一个字符的方法显然不够科学,因为很多英文字母只需要一个字节就可以表示了,偏要用四个字节表示就会造成很大的浪费。于是就出现了UTF-8 编码。

Unicode只是规定了字符如何编码,并没有规定如何存储和传输。 UTF-8编码就是Unicode编码的一种实现方式,它规定可以使用1~4个字节表示一个字符,根据所要表现的字符不同而变化字节长度,英文字母就用1个字节表示,汉字就用2-3个字节表示。

那么问题来了,由于计算机中的字符串是连续的0101的编码,如何既能够表示一个字符在Unicode编码表中的码点,又能够让计算机明白这个连续编码串中的一个字节就是一个英文字母,而不与他前面的编码串构成两个或三个字节表示的字符。UTF-8 的编码的设计者巧妙的解决了这个问题。

英文字符这些原本就可以用ASCII码表示的字符用UTF-8表示时就只需要一个字节的空间,和ASCII是一样的。对于多字节(n个字节)的字符,第一个字节的前n为都设为1,第n+1位设为0,后面字节的前两位都设为10。剩下的二进制位全部用该字符的Unicode码填充。

Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
-----------------------+---------------------------------------------
0000 0000~0000 007F | 0xxxxxxx
0000 0080~0000 07FF | 110xxxxx 10xxxxxx
0000 0800~0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx

0001 0000~0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

这样的编码方式很好理解,如果一个字节当中第一位是0,那么这个字节就对应着一个字符,如果第一位是1,那么看他后面连续有多少个1,就表示这个字符占用了多少个字节。例如,“我”的Unicode码点是0x6211,二进制110001000010001,落在第三行的范围内(0000 0800~0000 FFFF),因此"我"需要三个字节,格式是"1110xxxx 10xxxxxx 10xxxxxx"。然后,从"我"的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了"我"的UTF-8编码是"11100110 10001000 10010001 ",转换成十六进制就是E68891,这才是最终存储在计算机中的二进制编码。

这里指出一个误区,网络上有很多在线utf8编码转换工具,声称可以把汉字转换成UTF-8 编码,其实大多数工具只是把汉字转换成了与之对应的unicode码点,并不是真正在存储和传输过程中的utf-8编码。这里可以查询汉字对应的utf-8编码和unicode编码,可以看出这两者是不同的。

除了UTF-8之外,Unicode的实现方式还有UTF-16 ,UTF-32 。 UTF-16 使用2~4个字节表示一个字符,UTF-32 则使用标准的4个字节表示一个字符,与其Unicode码点一一对应。无论采用哪种表现形式,同一字符所对应的Unicode码点都是一样的,只不过在存储和传输的时候,把码点做了不同的转换。

PYTHON字符编码

下面开始讲讲Python中的编码问题。

Python的默认编码是ASCII,这跟它的诞生背景有关,Python的诞生时间是1989年,Unicode于 1994年才正式公布,在Python诞生之初并无Unicode可用,只能选择ASCII。后来做了多方改进,才使得它适用于非英语系的用户。

如果不做修改,Python将使用ASCII为所有代码编码,包括注释。

>>> import sys

>>> sys.getdefaultencoding()

'ascii'

在编写python代码时如果不指定文件的编码方式,将默认使用ASCII编码。所以如果在代码中出现中文,将会报错

#stringtest.py

print '你好'

C:\Python27\python.exe D:/MyGit/demo/test/test.py
File "D:/MyGit/demo/test/test.py", line 1
SyntaxError: Non-ASCII character '\xe4' in file D:/MyGit/demo/test/test.py on line 1, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

 

如果想在代码中使用中文,则一定要在代码开头(第一行或第二行)声明此文件的编码方式,比如编码方式设为UTF-8

# -*- coding: utf-8 -*-

或者

#!/usr/bin/python

# -*- coding: utf-8 -*-

其中第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释。

这样,在代码中就可以使用中文了。

(完)

python语言中的编码问题的更多相关文章

  1. python语言中的编码问题(续)

    上文提到了python开发中非常重要的两处设置. 一个是编解码器的默认设置defaultencoding >>> import sys >>> sys.getdef ...

  2. Python语言中各种进制相互转换

    目录 Python语言中各种进制相互转换 将二进制.八进制.十进制的数分别转换成十进制的方法 将十进制转换成二进制.八进制.十六进制 Python语言中各种进制相互转换 本文参考自https://ww ...

  3. Python语言中的关键字(自己做的读书笔记)

    电脑配置:联想笔记本电脑 windows8系统 Python版本:2.7.8 本文章撰写时间:2015.1.1 作者:陈东陈 阅读说明: 1.本文都是先解释,后放图片: 2.文中斜体部分要么为需要输入 ...

  4. 聊聊python 2中的编码

    为什么需要编码: 计算机可以存储和处理二进制,那么从文字到计算机可以识别的二进制之间需要对应的关系,于是便有了ASCII,ASSCII使用7位字符,由于1byte=8bit,所以最高位补一个0,使用8 ...

  5. Python 2 中的编码

    在 Python 尤其是 Python2 中,编码问题是困扰开发者尤其初学者的一大问题.什么 Unicode/UTF-8/str ,又是 decode/encode 的,搞得人头都大了.其实不然,这有 ...

  6. 了解 Python 语言中的时间处理

    python 语言对于时间的处理继承了 C语言的传统,时间值是以秒为单位的浮点数,记录的是从1970年1月1日零点到现在的秒数,这个秒数可以转换成我们日常可阅读形式的日期和时间:我们下面首先来看一下p ...

  7. python 2 和python 3 中的编码对比

    在 Python 中,不论是 Python2 还是 Python3 中,总体上说,字符都只有两大类: 通用的 Unicode 字符: (unicode 被编码后的)某种编码类型的字符,比如 UTF-8 ...

  8. day06 python 3中的编码

    #python2 和 python3 的一些区别 ''' #python2 print('aaa') print'aaa' range() xrange()生成器 raw_input() #pytho ...

  9. Python语言中的按位运算

    (转)位操作是程序设计中对位模式或二进制数的一元和二元操作. 在许多古老的微处理器上, 位运算比加减运算略快, 通常位运算比乘除法运算要快很多. 在现代架构中, 情况并非如此:位运算的运算速度通常与加 ...

随机推荐

  1. 「译」JUnit 5 系列:环境搭建

    原文地址:http://blog.codefx.org/libraries/junit-5-setup/ 原文日期:15, Feb, 2016 译文首发:Linesh 的博客:环境搭建 我的 Gith ...

  2. 如何理解MySQL中auto_increment?

    1.auto_increment用于主键自动增长.比如从1开始增长,当把第一条数据删除,再插入第二条数据时,主键值为2,不是1.

  3. windows下的命令行工具babun

    什么是babun babun是windows上的一个第三方shell,在这个shell上面你可以使用几乎所有linux,unix上面的命令,他几乎可以取代windows的shell.用官方的题目说就是 ...

  4. MEF学习

    一.   什么是MEF MEF(Managed Extensibility Framework)是一个用于创建可扩展的轻型应用程序的库. 应用程序开发人员可利用该库发现并使用扩展,而无需进行配置. 扩 ...

  5. [ 技术人员创业Tips ] 1:抓住优质客户(上)

    写一篇技术以外的内容,可能会得罪一些人,轻拍,此外本文写的比较随意,写到哪里算哪里,轻拍. IT业不知道从什么时候起特别流行谈创业,似乎不谈创业就落伍,我不评价这种风气的好坏,只提一些自己的一些经验和 ...

  6. ASP.NET Web API WebHost宿主环境中管道、路由

    ASP.NET Web API WebHost宿主环境中管道.路由 前言 上篇中说到ASP.NET Web API框架在SelfHost环境中管道.路由的一个形态,本篇就来说明一下在WebHost环境 ...

  7. ADO.NET 核心对象简介

    ADO.NET是.NET中一组用于和数据源进行交互的面向对象类库,提供了数据访问的高层接口. ADO.NOT类库在System.Data命名空间内,根据我们访问的不同数据库选择命名空间,System. ...

  8. Java线上应用故障排查之一:高CPU占用

    一个应用占用CPU很高,除了确实是计算密集型应用之外,通常原因都是出现了死循环. 以我们最近出现的一个实际故障为例,介绍怎么定位和解决这类问题. 根据top命令,发现PID为28555的Java进程占 ...

  9. CodeSmith模板代码生成实战详解

    前言 公司项目是基于soa面向服务的架构思想开发的,项目分解众多子项目是必然的.然而子项目的架子结构种类也过多的话,就会对后期的开发维护产生一锅粥的感觉.为了尽可能的在结构层避免出现这种混乱的现象,我 ...

  10. 让DIV中文字换行显示

    1. <style> div { white-space:normal; word-break:break-all; word-wrap:break-word; } </style& ...