字符编码与编程语言

来源:互联网 发布:电脑桌面的便签软件 编辑:程序博客网 时间:2024/06/04 18:53

python2.*

Python2.x中对于字符串有两种表示方法,str和unicode,unicode更像是一个复杂类型,通常表示一个Unicode对象。而str则是一个基础类型,它仅仅表示字符数组([]byte)。

因为str仅仅是字符数组,因此,即使你把某一段中文使用UTF-8编码的时候,它的存在形式还是字符数组,当你使用len内置方法去求值的时候,你看到的肯定不是中文汉字的个数。这个时候怎么办呢?我们通常会把它转成unicode对象,然后进行操作,这样得到的结果就回是我们预期的。

Python的默认字符集在几个大版本中有过改变,以下是各个版本的默认字符集列举:

  • Python2.1及以前: latin1
  • Python2.3及之后,Python2.5以前:latin1 (但是会对非ASCII字符集字符提出WARNING)
  • Python2.5及以后:ASCII

几种编码情况分析:
脚本字符编码

指脚本文件本身是用何种字符编码的,默认情况Python解释器认为脚本是ascii码:

# test.pyprint "你好"

上面是test.py脚本,运行 python test.py 会报错

解决方法:

  1. 在第一行或第二行进行编码声明
  2. 格式须符合"coding[:=]\s*([-\w.]+)"
  3. 更详细请参考pep-0263
# coding=utf-8# -*- coding: utf-8 -*-# vim: set fileencoding=utf-8

使用上面三种格式之一,就相当于告诉python解释器使用utf8编码解释脚本文件。

当然也会出现编码格式说明与实际编码格式不一致的情况,这样就会导致问题,一般很少出现

解释器字符编码

解释器字符编码是指解释器内部认为的str类型的字符串的编码,也就是说python解释器会把str类型的字符串当作何种字符编码来处理。默认,python解释器字符编码也是ascii的。可以通过命令查看:

>>> sys.getdefaultencoding()'ascii'

在windows下和Linux下的字符编码显示:

# windows>>> 'A''A'>>> '你好''\xc4\xe3\xba\xc3'>>>
# linux>>> 'A''A'>>> '你好''\xe4\xbd\xa0\xe5\xa5\xbd'
\x意味着接下去的两个字符以十六进制解释为字符,如\xaa其意思即为chr(0xaa),而chr是把数字转换为字符串,如chr(0x41)或chr(65)结果为'A'
  1. 因为ASCII码中只有0-127,所以多出的无法显示为字符,因此只能显示十六进制格式的
  2. 在windows下默认为ANSI,在中文下为gbk(或者更高,因为是向下兼容的,所以以gbk代替),而gbk表示一个中文字符为2个字节;而在Linux下默认为uft-8格式的,‘你好’为6个字节,所以结果不同

输入

输入 : raw_input只接收str类型字符串,不接收unicode类型

>>> s=raw_input(u'你好')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的隐式转换,当一个str类型变量和一个unicode类型变量进行连接操作的时候,或者对一个str对象使用encode方法的时候,Python内部都会尝试将当前的str对象转换成unicode对象,然后在进行操作。那当把str对象转换成unicode对象的时候,采用什么编码呢?问题就在于此。它会采用sys.getdefaultencoding()方法返回的编码方式,很不幸的是,往往返回的是都是ascii。而实际上你的str对象里面保存的是UTF-8编码的字符数组,而Python默认却会使用ascii去转换,这个时候就报出上面的错误了。

只要去掉u就好,则汉字依旧为str,只不过编码为gbk;这个Unicode转换问题还经常发生在输出的时候,在使用print的时候,如果同时输出Unicode和str,例如:
print "%s, %s" % (u'Tom','你好')
往往会发生str到Unicode的类型转化,而如果str中含有汉字的话,会报错,解决方法是输出都使用Unicode字符串

输出

从上面其实可以看出,在命令中输入一个字符,其格式是操作系统的编码格式,而输出呢?也一样,是本地格式
在命令行中, print打印对象的_str_(),不用print显示的是对象的_repr_(),在脚本中后者可用 print repr() 的形式来看

补充:__str__()强调可读性,所以更多服务于用户;__repr__()强调准确性,更多服务于开发者;很多情况下__str__()会直接使用__repr__(),更加详细的区别可参考difference-between-str-and-repr-in-python

先补充个知识:
不同的字符编码集如utf-8、gbk等等的字符串之间相互转换是怎么进行的呢?

  1. 如果单独写两个不同字符集的转换,不同字符编码集多,且不具备扩展性
  2. 如果要是都转成一个共同的,再转的话,就方便很多,每个编码就只负责自己的工作就行
  3. 而unicode几乎涵盖了一切字符串,非常适合做这个桥梁,其相关的两个方法是decode和encode。

强调:Unicode是一种单独的字符串类型,与str不同;所以其实encode和decode就是类型转换;使用encode,转成的都是str对象,可以是utf-8格式,而这些格式都可以用ASCII码来表示,只不过无法表示(127以上)的直接用十六进制数代替了

# windows# test.py# coding = 'utf-8's = '你好'print s.decode('uft8')print s

而结果却是

python test.py你好浣犲ソ

为什么呢?

如果你能理解前面所述的,结果就很容易解释了,s为uft-8格式的,所以直接解码为Unicode可以输出成功,但是如果直接print的话,是用gbk解码,所以就错了,详细解释如下

  • 直接解码为Unicode,字符串为u'\u4f60\u597d'(就是u"你好"的另一种形式),这个表示格式类似于前面的\x,不过是4个字节;其用Unicode来解释,而不是ASCII码,所以能直接输出成功
  • '\xe4\xbd\xa0\xe5\xa5\xbd'这是‘你好’的utf-8,可以试试使用gbk对两个字母分别解码,其结果就是那三个怪异的中文;
  • 最后,因为Linux的默认编码为utf-8,所以不存在这个问题
  • 最后的最后,请不要使用windows下的记事本:一是默认会存为ANSI(gbk),二是就算存为utf-8,也是带BOM的,容易出错

如果使用pycharm的话,结果会有所不同,这是因为pycharm的默认project编码是utf-8;改为gbk后结果与上述相同

encode和decode的补充

似乎有了unicode对象的encode方法和str的decode方法就足够了。奇怪的是,unicode也有decode,而str也有 encode,到底这两个是干什么的。

  1. str的encode其实就是decode为Unicode,然后encode,问题是decode一般使用默认的sys.getdefaultencoding()(一般是ascii),所以会常常出错
  2. 反之亦然,unicode对象使用decode是先使用ASCII(默认值)进行encode,再用指定的编码方式解码为Unicode

这两种方式一般不建议使用

考验

str(s)和unicode(s)是两个工厂方法,分别返回str字符串对象和unicode字符串对象,str(s)等效于s.encode(‘ascii’),unicode(s)等效于s.decode(‘ascii’)

s3 = u"你好"str(s3)s4 = "你好"unicode(s4)

上述这两个都会出错,你知道为什么吗?

  1. 中文字符没有对应的ASCII码,如果你去了u,就可以,其输出结果为s3的gbk字节码
  2. s4类型不是ASCII

在廖雪峰的Python学习网站2.7中字符串与编码的回复中有不少的中文输入输出问题,可以尝试去解决

文件

  • 内置的open()方法打开文件时,read()读取的是str,读取后需要使用正确的编码格式进行decode()。write()写入时,如果参数是unicode,则需要使用你希望写入的编码进行encode(),如果是其他编码格式的str,则需要先用该str的编码进行decode(),转成unicode后再使用写入的编码进行encode()。如果直接将unicode作为参数传入write()方法,Python将先使用源代码文件声明的字符编码进行编码然后写入。
  • 模块codecs提供了一个open()方法,可以指定一个编码打开文件,使用这个方法打开的文件读取返回的将是unicode。写入时,如果参数是unicode,则使用open()时指定的编码进行编码后写入;如果是str,则先根据源代码文件声明的字符编码,解码成unicode后再进行前述操作。

乱码处理方案

  1. 在Python进行开发的时候,对字符串统一使用unicode对象来表示,尤其是带中文的字符串,千万不要使用str类型,这样就从根源上避免了Python隐式从str转成unicode的可能性。对于外部传递进来的参数,尤其是网络调用传入的参数,必须先转成unicode类型再进行后续的操作。这种方式比较干净,纯粹。
    而在Python3.x版本中,把’xxx’和u’xxx’统一成Unicode编码,即写不写前缀u都是一样的
  2. 还有一种方式,就是在Python源码文件的开始处加上下面三行代码
import sysreload(sys)sys.setdefaultencoding("utf-8")

这种方式会改变Python默认从str转成unicode采用的编码方式。只要保证我们的中文字符统一采用UTF-8编码方式,这种方式也能很好解决字符编码问题。但是每个文件总是写上这三行代码看上去会比较dirty。

其他一些获取当前环境下的默认编码的方法

# windowsimport sysimport localedef p(f):    print '%s.%s(): %s' % (f.__module__, f.__name__, f())# 返回当前系统所使用的默认字符编码p(sys.getdefaultencoding)# sys.getdefaultencoding(): ascii# 返回用于转换Unicode文件名至系统文件名所使用的编码p(sys.getfilesystemencoding)# sys.getfilesystemencoding(): mbcs# 获取默认的区域设置并返回元祖(语言, 编码)p(locale.getdefaultlocale)#locale.getdefaultlocale(): ('zh_CN', 'cp936')# 返回用户设定的文本数据编码p(locale.getpreferredencoding)# locale.getpreferredencoding(): cp936# \xba\xba是'汉'的GBK编码print r"'\xba\xba'.decode('mbcs'):", repr('\xba\xba'.decode('mbcs'))#'\xba\xba'.decode('mbcs'): u'\u6c49'

一点补充

如果有一个直接的str字符串,如s = 'id\u003d215903184\u0026index\u003d0\u0026st\u003d52\u0026sid’要装为Unicode,怎么转呢?难道要自己去处理每一个\u吗?
可以使用s.decode('unicode-escape')

>>> s = 'id\u003d215903184\u0026index\u003d0\u0026st\u003d52\u0026sid\u003d95000\u0026i'>>> print(type(s))<type 'str'>>>> s = s.decode('unicode-escape')>>> su'id=215903184&index=0&st=52&sid=95000&i'>>> print(type(s))<type 'unicode'>>>>

C/C++

  • C,C++,Python2 内部字符串使用当前系统默认编码,一般情况下当前系统默认编码是GBK(windows),即简体中文编码。
  • C,C++表示字符的类型有char和wchar,char占一个字节,wchar占两个字节,所有编码方式里只有Unicode编码(即UTF-16大端模式和UTF-16小端模式)的编码单元为两字节,故此Unicode编码使用wchar作为字符串的基本单元,对应的字符串类型为wstring。
  • 其它编码方式都使用字节作为基本编码单元,故此都用char作为字符串的基本单元,对应的字符串类型为string。

有两个函数可以用来在wchar类型字符串和char类型字符串之间进行转换:MultiByteToWideChar 和 WideCharToMultiByte。

int MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr,int cbMultiByte, LPWSTR lpWideCharStr, int cchWideChar);int WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte,  LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar);

codepage取值:
- CP_ACP: 当前系统是Windows ANSI code page。
- CP_MACCP: 当前系统是 mac code page
- CP_UTF8: UTF8编码方式

一些处理方法:

  • C,C++内部字符串默认使用GBK编码,使用GBK编码的话调试时可识别汉字,故此从外部文件读入字符串之后一般都先转化成GBK编码,这样方便调试。
  • 将字符串写到文件时通常会选择unicode编码,
  • 发送到网络时会选择UTF-8编码。(可以避免Unicode字节序的问题)

Java

Java用String类型表示字符串,String由char类型组成,char类型占两个字节,一个char类型变量就可以表示任意字符,甚至汉字。所以下面代码是合法的:

char b='你';System.out.println((int)b);// 20320 == 0x4f60

在java中unicode编码和utf16大端模式编码是一致的


参考

深入理解Python字符集编码
谈谈字符编码的问题
字符编码总结

0 0
原创粉丝点击