字符编码

字符编码问题

java:

1.java使用jchardet检测文本文件(字节流)的编码方式 wings里面使用的就是这个。

2.深度剖析Java的字符编码

3.java自动探测文件的字符编码

4.借助JCharDet获取文件字符集

编码教程

1.

  • ASCII是最基本的,最早的,用的最广泛的,字符编码

    • 但是只支持普通的基本字符,即常见英文字母,数字,下划线等
  • 简体中文的编码

    • 发展路径以此是:GB2312 < GBK < GB18030
    • 对应的所包含的字符个数,也是以此增加
  • 繁体中文的编码

    • 最常见的是BIG5
  • 西欧字符编码

    • 最常用的就是ISO8895-1

      • ISO8859是从1到15,是一系列的编码
      • ISO8859-1是属于ISO8859系列编码其下的,但是用的最广泛的的
  • 统一了世界上所有字符的Unicode

    • Unicode,可以叫做大一统的编码,包含了世界上所有的字符

    • 但是Unicode只是字符编码集

      • 字符(编码)集,只表示包含了哪些字符
      • 字符编码,表示了用何种方式去表示此字符集
      • Unicode这个字符集,可以有多种字符编码表示出来,最常见的包括UTF-8,UTF-16,UTF-32等等
      • 最最常用的是UTF-8

网页中的编码,一般是通过charset指定的;很多中文网页,charset用的GB2312;但是部分网页,所声明的编码和实际编码不同:虽然charset指定的是GB2312,但是实际上用的是(包含了更多字符的)GBK;导致的情况是:如果你按照GB2312去解码,由于部分字符是属于GBK的,而非GB2312,则会出现解码失败。解决办法是:尝试用GBK,或包含更多字符的GB18030去解码,则一般都可以正常解码的。

参见字符编码简明教程

2.

当你通过浏览器,打开某个网站,即某个url地址的时候,你所能正常看到网页的内容,各种文字,都可以正常显示,且没有显示乱码。此过程,涉及到,浏览器帮你正确解析HTML源码。

流程如下所示:

1.浏览器访问对应的url地址,并获取对应的html(或者,以及,其他的css,javascript等)网页源码

2.浏览器识别解析HTML源码内容

其中包含了,解析html的头部(header),找到对应的charset=xxx这部分的内容,然后把根据xxx所指示的字符编码类型,去解码对应的html内容,显示对应的文字,以保证不是乱码,可以正确的显示文字信息;

所以爬虫在抓取了网页的源代码后,也要跟浏览器一样去对网页进行解码。

注:有个别的html网页本身做的不规范,导致本身自己声明的是某种编码类型,但是实际上,并不全是该类型的编码,导致你去按照其所声明的编码去解析,有时候仍会出现乱码。比如,其html中明明写的是charset=gb2312,但是实际上,其部分字符是属于GBK的,导致你按照gb2312去解码,有些字符仍会是乱码,而安装gbk去解码,就全都正常显示了,没有乱码了。即实际上应该是charset=gbk。

python编码问题

乱码问题是我原本用的某种编码方式,如果要进行解码,也应该要用对应的解码格式去解码,或者用兼容性更大的,比如gb2312,也可以用gbk或gb18030去解码,但不能用utf-8去解码,虽然他能表达的编码更多。

GB2312,GBK,GB18030,编码技术上都是兼容的。

参见写的很不错的这篇

python解析网页源代码返回乱码问题

抓取的网页是gb2312的,然后直接输出到console里面是乱码,原因是console采用的是utf-8编码的,解决方案

1.run Configuration,修改console的编码,从UTF-8改为GBK(或GB2312,或GB18030)。

2.修改代码获得GB2312的html后,去decode为Unicode,然后直接输出Unicode字符串到UTF-8的Eclipse+PyDev的console中,

则比如也是可以正常显示无乱码的。(内部会自动将输出的Unicode转换为当前console的UTF-8编码的字符串的)。

3.基于上面那种方式,(不改Eclipse+PyDev的console的字符编码,仍保持旧的UTF-8的配置),但是在转换为Unicode后,再故意转为输出目标(此处的Eclipse+PyDev的console)的编码,即UTF-8。

1
2
3
uniCodehtml = html.decode("GBK") //此处只是转化为了unicode,并不是utf-8
utf8html = uniCodehtml.encode("UTF-8")
print utf8html
关于beautifulsoup的总结

以后用python的Beautiful Soup去解析中文网页的话:

1.如果本身网页的编码自己标称的,和本身其中文字符所用编码完全符合的话,即没有部分字符超出了其所标称的编码,比如标称为GBK,网页所有的内容,都的确是GBK编码,没有超出的其他字符(比如属于GB18030的编码),那么,是可以通过:

1
2
3
page = urllib2.urlopen(url)
soup = BeautifulSoup(page) #此处不需要传递参数,BeautifulSoup也完全可以自己去解析网页内容所使用的编码
print soup.originalEncoding

而得到真实的网页的编码的。

2.讨论中文乱码问题之前,先解释关于中文字符编码:

时间上的发展先后是,GB2312,GBK,GB18030,编码技术上都是兼容的。

从所包含的中文字符个数来说,算是:GB2312 < GBK < GB18030,也因此,才可能会出现上面所说的,标称GB2312的,部分字符用到了GBK里面的,或者是标称GBK的,部分字符用到了GB18030里面的,所以被人家编码工具解析错误,退回认为编码是最基本的windows-2152了。

所以:

(1)如果是网页标称为GB2312,但是部分字符用到了GBK的了,那么解决办法就是在调用BeautifulSoup,传递参数fromEncoding=”GBK”

(2)如果是网页标称为GBK,但是部分字符用到了GB18030的了,那么解决办法就是在调用BeautifulSoup,传递参数fromEncoding=”GB18030”。

(3)实际上由于GB18030从字符数上都涵盖了GB2312和GBK,所以如果是上述两种任意情况,即只要是中文字符出现乱码,不管是标称GB2312中用到了GBK,还是标称GBK中用到了GB18030,那么都直接传递GB18030,也是可以的,即:

1
soup = BeautifulSoup(page, fromEncoding="GB18030")

参见python中文字符乱码(GB2312,GBK,GB18030相关的问题)

可能遇到的坑: beautifulsoup在输出文本时默认以UTF-8的方式编码,无论原文是否以它进行编码的.即即使你用了正确的编码格式,如gb18030,但是beautifulsoup默认是使用utf-8输出,所以依旧乱码。

参见requests和BeautifulSoup中文编码转换心得

chardet代码
1
2
3
4
5
>>> import urllib
>>> rawdata = urllib.urlopen('http://www.google.cn/').read()
>>> import chardet
>>> chardet.detect(rawdata)
{'confidence': 0.98999999999999999, 'encoding': 'GB2312'}
encode & decode

字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(encode)成另一种编码。

decode的作用是将其他编码的字符串转换成unicode编码,如str1.decode(‘gb2312’),表示将gb2312编码的字符串str1转换成unicode编码。

encode的作用是将unicode编码转换成其他编码的字符串,如str2.encode(‘gb2312’),表示将unicode编码的字符串str2转换成gb2312编码。

因此,转码的时候一定要先搞明白,字符串str是什么编码,然后decode成unicode,然后再encode成其他编码。

代码中字符串的默认编码与代码文件本身的编码一致。

如:s=’中文’。如果是在utf8的文件中,该字符串就是utf8编码,如果是在gb2312的文件中,则其编码为gb2312。这种情况下,要进行编码转换,都需 要先用decode方法将其转换成unicode编码,再使用encode方法将其转换成其他编码。

通常,在没有指定特定的编码方式时,都是使用的系统默 认编码创建的代码文件。如果字符串是这样定义:s=u’中文’,则该字符串的编码就被指定为unicode了,即python的内部编码,而与代码文件本身的编码无关。因此,对于这种情况做编码转换,只需要直接使用encode方法将其转换成指定编码即可。

如果一个字符串已经是unicode了,再进行解码则将出错,因此通常要对其编码方式是否为unicode进行判断:

isinstance(s, unicode) #用来判断是否为unicode,用非unicode编码形式的str来encode会报错

如何获得系统的默认编码?

1
2
3
4
#!/usr/bin/env python
#coding=utf-8
import sys
print sys.getdefaultencoding()

该段程序在英文WindowsXP上输出为:ascii。

在某些IDE中,字符串的输出总是出现乱码,甚至错误,其实是由于IDE的结果输出控制台自身不能显示字符串的编码,而不是程序本身的问题。

如在UliPad中运行如下代码:

1
2
s=u"中文"
print s

会提示:UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 0-1: ordinal not in range(128)。这是因为UliPad在英文WindowsXP上的控制台信息输出窗口是按照ascii编码输出的(英文系统的默认编码是 ascii),而上面代码中的字符串是Unicode编码的,所以输出时产生了错误。

将最后一句改为:print s.encode(‘gb2312’),则能正确输出“中文”两个字。

若最后一句改为:print s.encode(‘utf8’),则输出:\xe4\xb8\xad\xe6\x96\x87,这是控制台信息输出窗口按照ascii编码输出utf8编码的字符串的结果。

unicode(str,’gb2312’)与str.decode(‘gb2312’)是一样的,都是将gb2312编码的str转为unicode编码。

使用str.class可以查看str的编码形式。

特别推荐这篇:

python新手必碰到的问题—encode与decode,中文乱码

2.

处理流程,可以这么使用,把python看做一个水池,一个入口,一个出口

入口处,全部转成unicode, 池里全部使用unicode处理,出口处,再转成目标编码(当然,有例外,处理逻辑中要用到具体编码的情况)

1
2
3
4
5
6
7
8
9
读文件
外部输入编码,decode转成unicode
处理(内部编码,统一unicode)
encode转成需要的目标编码
写到目标输出(文件或控制台)

IDE和控制台报错,原因是print时,编码和IDE自身编码不一致导致

输出时将编码转换成一致的就可以正常输出

1
2
3
4
>>> print u'中文'.encode('gbk')
����
>>> print u'中文'.encode('utf-8')
中文

处理顺序

1
2
3
1. Decode early
2. Unicode everywhere
3. Encode later

引用自PYTHON-进阶-编码处理小结

3.

在python中,使用unicode类型作为编码的基础类型,编解码要以其为中间形式过渡,即进行strunicode之间的转换。
解码然后再编码的过程,即str->unicode->str的过程。中间得到的叫做unicode对象。

这里需要强调的是unicode是一种字符编码方法,是 “与存储无关的表示”,而utf8是一种以unicode进行编码的计算机二进制表示,或者说传输规范。gbk,gb2312,gb18030, utf8等属于不同的字符集,转换编码就是在它们中的任意两者间进行。

具体的转换,比如直接将一个字符串encode成另一种字符集表示,注意此处是字符串,即typestr的,引号前没有加u前缀的

1
2
3
# coding: utf8
s='美丽'
s.encode('gbk')

则实际上会先以默认编码进行decode,即decode('ascii')(假设系统默认的是),开头声明了utf8,s的编码就是utf8,ascii解码不了utf8的字符会报错。那就改默认编码,

1
2
3
4
5
6
7
8
# coding: utf8
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
s='美丽'
s.encode('gbk')

这样把默认编码改成utf8,decode的时候就以默认编码utf8来进行,能够成功运行.

或者decode时指定类型,

1
2
3
4
5
# coding: utf8
import sys
s='美丽'
s.decode('utf8').encode('gbk')

对于typeunicode的,即加了u前缀的字符串,如上所说,直接encode即可

1
2
3
4
5
# coding: utf8
import sys
s = u'美丽'
s.encode('gbk')

java编码

1.

JVM里面的任何字符串资源都是Unicode,就是说,任何String类型的数据都是Unicode编码。没有例外。既然只有一种编码,那么,我们可以这么说,JVM里面的String是不带编码的。String相当于 char[]。
JVM里面的 byte[] 数据是带编码的。比如,Big5,GBK,GB2312,UTF-8之类的。
一个GBK编码的byte[] 转换成 String,其实就是从GBK编码向Unicode编码转换。
一个String转换成一个Big5编码的byte[],其实就是从Unicode编码向Big5编码转换。所以,Unicode是所有编码转换的中间介质。所有的编码都有一个转换器可以转换到Unicode,而Unicode也可以转换到其他所有的编码。这样构成了一个总线结构。

最容易出问题的地方是浏览器和服务器JVM之间.我们把浏览器编码叫做 Browser_Charset,把JVM编码叫做JVM_Charset(通常等于服务器系统编码)。
当浏览器的数据过来的时候,是一个带有Browser_Charset的byte[]。
如果用户处理程序需要一个String类型的数据,那么JVM会好心好意地把这个byte[]转换成String。使用的转换器是 JVM_Charset -> Unicode。
注意,如果这个时候,Browser_Charset 和 JVM_Charset并不相等。那么,这个自动转换是错误的。
为了弥补这个错误。我们需要做两步工作。
(1) Unicode -> JVM_Charset,把这个String 转换回到原来的 byte[]。
(2) Browser_Charset -> Unicode,把这个还原的byte[]转换成 String。

这个效果,和直接从HTTP Request取得byte[],然后执行 (2) Browser_Charset -> Unicode 的效果是一样的。如果在Request里面设置了CharacterEncoding,那么POST Data参数就不需要自己手工转换了,web server的自动转换就是正确的。URL的参数编码还涉及到URL编码,需要考虑的问题多一些,没有这么简单。

JVM把数据发到浏览器的时候。也需要考虑编码问题。可以在Response里面设置。另外,HTML Meta Header里面也可以设置编码,提醒Browser选择正确编码。

2.

JAVA中字符的表达

JAVA 中有char、byte、String这几个概念。char 指的是一个UNICODE字符,为16位的整数。byte 是字节,字符串在网络传输或存储前需要转换为byte数组。在从网络接收或从存储设备读取后需要将byte数组转换成String。String是字符串,可以看成是由char组成的数组。String 和 char 为内存形式,byte是网络传输或存储的序列化形式。

编码方式的简介

String序列化成byte数组或反序列化时需要选择正确的编码方式。如果编码方式不正确,就会得到一些0x3F的值。

J2SE中相关的函数

String是unicode,所以new String()得到的是unicode,str.getBytes()得到的是带编码格式的bytes。两种bytes互相转换需要通过unicode。然后,对于console里面的输出,他跟输出到文本是一样的,console和文件一样,且关联系统的编码。console里面的编码如果与之前编码的不一致的话,就会出现乱码现象。

String str =”英”;
//取得GB2312编码的字节
byte[] bytesGB2312 = str.getBytes(“GB2312”);
//取得平台缺省编码的字节(solaris为ISO8859_1,windows为GB2312)
byte[] bytesDefault = str.getBytes();
//用指定的编码将字节转换成字符串
String newStrGB = new String(bytesGB2312, “GB2312”);

//用平台缺省的编码将字节转换成字符串(solaris为ISO8859_1,windows为GB2312)
String newStrDefault = new String(bytesDefault);
//用指定的编码从字节流里面读取字符
InputStream in = xxx;
InputStreamReader reader = InputStreamReader( in, “GB2312”);
char aChar = reader.read();

JSP、数据库的编码

1.JSP中的编码
(1) 静态声明:
CHARSET有两个作用:
JSP文件的编码方式:在读取JSP文件、生成JAVA类时,源JSP文件中汉字的编码
JSP输出流的编码方式:在执行JSP时,往response流里面写入数据的编码方式
(2) 动态改变:在往response流里面写数据前可以调用response.setContentType(),设定正确的编码类型。
(3) 在TOMCAT中,由Request.getParameter() 得到的参数,编码方式都是ISO8859_1。所以如果在浏览器输入框内输入一个汉字“英”,在服务器端就得到一个ISO8859_1编码的(0x00,0xD3,0x00,0xA2)。所以通常在接收参数时转码:
String wrongStr = response.getParameter(“name”);
String correctStr = new String(wrongStr.getBytes(“ISO8859_1”),”GB2312”);
在最新的SERVLET规范里面,也可以在获取参数之前执行如下代码:
request.setCharacterEncoding(“GB2312”);

2.数据库的编码

(1) 数据库使用UTF-16
如果String中是UNICODE字符,写入读出时不需要转码
(2) 数据库使用ISO8859_1
如果String中是UNICODE字符,写入读出时需要转码
写入:String newStr = new String(oldStr.getByte(“GB2312”), “ISO8859_1”);
读出:String newStr = new String(oldStr.getByte(“ISO8859_1”),”GB2312”);

3.

String newStr = new String(oldStr.getBytes(), “UTF-8”);

java中的String类是按照unicode进行编码的,当使用String(byte[] bytes, String encoding)构造字符串时,encoding所指的是bytes中的数据是按照那种方式编码的,而不是最后产生的String是什么编码方式,换句话说,是让系统把bytes中的数据由encoding编码方式转换成unicode编码。如果不指明,bytes的编码方式将由jdk根据操作系统决定。

当我们从文件中读数据时,最好使用InputStream方式,然后采用String(byte[] bytes, String encoding)指明文件的编码方式。不要使用Reader方式,因为Reader方式会自动根据jdk指明的编码方式把文件内容转换成unicode 编码。

当我们从数据库中读文本数据时,采用ResultSet.getBytes()方法取得字节数组,同样采用带编码方式的字符串构造方法即可。

ResultSet rs;
bytep[] bytes = rs.getBytes();
String str = new String(bytes, “gb2312”);

不要采取下面的步骤。

ResultSet rs;
String str = rs.getString();
str = new String(str.getBytes(“iso8859-1”), “gb2312”);

这种编码转换方式效率底。之所以这么做的原因是,ResultSet在getString()方法执行时,默认数据库里的数据编码方式为 iso8859-1。系统会把数据依照iso8859-1的编码方式转换成unicode。使用str.getBytes(“iso8859-1”)把数据还原,然后利用new String(bytes, “gb2312”)把数据从gb2312转换成unicode,中间多了好多步骤。

从HttpRequest中读参数时,利用reqeust.setCharacterEncoding()方法设置编码方式,读出的内容就是正确的了。

以上参见JAVA 字符串编码总结