字符编码问题
java:
1.java使用jchardet检测文本文件(字节流)的编码方式 wings里面使用的就是这个。
编码教程
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,编码技术上都是兼容的。
参见写的很不错的这篇
抓取的网页是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。
|
|
关于beautifulsoup的总结
以后用python的Beautiful Soup去解析中文网页的话:
1.如果本身网页的编码自己标称的,和本身其中文字符所用编码完全符合的话,即没有部分字符超出了其所标称的编码,比如标称为GBK,网页所有的内容,都的确是GBK编码,没有超出的其他字符(比如属于GB18030的编码),那么,是可以通过:
|
|
而得到真实的网页的编码的。
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,也是可以的,即:
|
|
参见python中文字符乱码(GB2312,GBK,GB18030相关的问题)
可能遇到的坑: beautifulsoup在输出文本时默认以UTF-8的方式编码,无论原文是否以它进行编码的.即即使你用了正确的编码格式,如gb18030,但是beautifulsoup默认是使用utf-8输出,所以依旧乱码。
参见requests和BeautifulSoup中文编码转换心得
chardet代码
|
|
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会报错
如何获得系统的默认编码?
|
|
该段程序在英文WindowsXP上输出为:ascii。
在某些IDE中,字符串的输出总是出现乱码,甚至错误,其实是由于IDE的结果输出控制台自身不能显示字符串的编码,而不是程序本身的问题。
如在UliPad中运行如下代码:
|
|
会提示: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处理,出口处,再转成目标编码(当然,有例外,处理逻辑中要用到具体编码的情况)
|
|
IDE和控制台报错,原因是print时,编码和IDE自身编码不一致导致
输出时将编码转换成一致的就可以正常输出
|
|
处理顺序
|
|
3.
在python中,使用unicode类型作为编码的基础类型,编解码要以其为中间形式过渡,即进行str
和unicode
之间的转换。
解码然后再编码的过程,即str
->unicode
->str
的过程。中间得到的叫做unicode对象。
这里需要强调的是unicode是一种字符编码方法,是 “与存储无关的表示”,而utf8是一种以unicode进行编码的计算机二进制表示,或者说传输规范。gbk,gb2312,gb18030, utf8等属于不同的字符集,转换编码就是在它们中的任意两者间进行。
具体的转换,比如直接将一个字符串encode成另一种字符集表示,注意此处是字符串,即type
为str
的,引号前没有加u
前缀的
|
|
则实际上会先以默认编码进行decode
,即decode('ascii')
(假设系统默认的是),开头声明了utf8,s
的编码就是utf8
,ascii解码不了utf8
的字符会报错。那就改默认编码,
|
|
这样把默认编码改成utf8,decode
的时候就以默认编码utf8来进行,能够成功运行.
或者decode
时指定类型,
|
|
对于type
为unicode
的,即加了u
前缀的字符串,如上所说,直接encode
即可
|
|
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 字符串编码总结