字符编码
iso8859-1
单字节编码。
最早的编码是iso8859-1,和ascii编码相似。属于单字节编码,最多能表示的字符范围是0-255,应用于英文系列。
iso8859-1是单字节编码,自身不能显示中文,若要显示中文,必须和其他能显示中文的编码配合,如“GBK”,“UTF-8"。
以"中文"为例,这两个字不存在iso8859-1编码,以gb2312编码为例,应该是"d6d0 cec4"两个字符,使用iso8859-1编码的时候则将它拆开为4个字节来表示:“d6 d0 ce c4”(事实上,在进行存储的时候,也是以字节为单位处理的)。
而如果是UTF编码,则是6个字节"e4 b8 ad e6 96 87"。
由于是单字节编码,和计算机最基础的表示单位一致,所以在很多协议上,默认使用该编码。
gb2312/GBK/gb18030
双字节编码。
专门用来表示汉字,是双字节编码,兼容iso8859-1编码。其中gbk编码能够用来同时表示繁体字和简体字,而gb2312只能表示简体字,gbk是兼容gb2312编码的。gb18030又向下兼容gbk。gb18030>gbk>gb2312。
unicode
这是最统一的编码,可以用来表示所有语言的字符,而且是定长双字节(也有四字节的)编码,包括英文字母在内。不兼容iso8859-1编码的,也不兼容任何编码。很多软件内部是使用unicode编码来处理的,比如 Java
utf-8
不定长编码。
考虑到unicode不便于传输和存储。因此而产生了utf编码,utf编码兼容iso8859-1编码,同时也可以用来表示所有语言的字符。
不过,utf编码是不定长编码,每一个字符的长度从1-6个字节不等。另外,utf编码自带简单的校验功能。一般来讲,英文字母都是用一个字节表示,而汉字使用三个字节。
Java对字符编码的处理
getBytes(charset)
将字符串按照charset 编码,并以字节数组形式返回。比如"中文",如果charset为 gbk ,则返回字节数组 [d6 d0 ce c4] 。如果charset为utf8,则返回 [e4 b8 ad e6 96 87] 。如果是iso8859-1,由于无法编码,最后返回 3f 3f ? ?。
String str = "中文"; //这是个unicode
byte[] gbk_bytes = str.getBytes("GBK"); //unicode -> GBK
byte[] utf8_bytes = str.getBytes("UTF-8"); //unicode -> UTF-8
byte[] iso8859_bytes = str.getBytes("ISO-8859-1"); //unicode -> ISO-8859-1
for(byte b : gbk_bytes){
System.out.println(Integer.toHexString(b));
}
System.out.println("------");
for(byte b : utf8_bytes){
System.out.println(Integer.toHexString(b));
}
System.out.println("------");
for(byte b : iso8859_bytes){
System.out.println(Integer.toHexString(b));
}
输出:
ffffffd6
ffffffd0
ffffffce
ffffffc4
------
ffffffe4
ffffffb8
ffffffad
ffffffe6
ffffff96
ffffff87
------
3f
3f
注:String str = “中文”; 这是个 unicode 编码
String(byte bytes[], String charsetName)
指定字符编码的构造函数,将字节数组按照指定的字符编码进行解码,返回字符串。
String str = "中文"; //这是个unicode
byte[] gbk_bytes = str.getBytes("GBK"); //unicode -> GBK
byte[] utf8_bytes = str.getBytes("UTF-8"); //unicode -> UTF-8
byte[] iso8859_bytes = str.getBytes("ISO-8859-1"); //unicode -> ISO-8859-1
System.out.println(new String(gbk_bytes, "GBK"));
System.out.println(new String(utf8_bytes, "UTF-8"));
System.out.println(new String(iso8859_bytes, "ISO-8859-1"));
输出:
中文
中文
??
因为iso8859-1没有中文对应的编码,getBytes返回的就是未知字节数组,所以反向解析的时候,也是解析不出来的,所以返回乱码。
字符编码转换
UTF-8 -> GBK、GBK -> UTF-8转换
public class ConvertDemo {
public static void main(String[] args) throws UnsupportedEncodingException {
String str="中文";
/* UTF-8 -> GBK */
byte[] utf8_bytes = str.getBytes("UTF-8");
System.out.println("转换前UTF-8=" + Arrays.toString(utf8_bytes));
byte[] gbkBytes = convert(utf8_bytes, "UTF-8", "GBK");
System.out.println("转换后GBK=" + Arrays.toString(gbkBytes));
System.out.println("转换后GBK=" + new String(gbkBytes, "GBK"));
/* GBK -> UTF-8 */
byte[] gbk_bytes = str.getBytes("GBK");
System.out.println("转换前GBK=" + Arrays.toString(gbk_bytes));
byte[] utfBytes = convert(gbk_bytes, "GBK", "UTF-8");
System.out.println("转换后UTF-8=" + Arrays.toString(utfBytes));
System.out.println("转换后UTF-8=" + new String(utfBytes, "UTF-8"));
}
public static byte[] convert(byte[] bytes, String fromCharsetName, String toCharsetName) throws UnsupportedEncodingException {
String string = new String(bytes, fromCharsetName);
byte[] bytes1 = string.getBytes(toCharsetName);
return bytes1;
}
}
输出:
转换前UTF-8=[-28, -72, -83, -26, -106, -121]
转换后GBK=[-42, -48, -50, -60]
转换后GBK=中文
转换前GBK=[-42, -48, -50, -60]
转换后UTF-8=[-28, -72, -83, -26, -106, -121]
转换后UTF-8=中文
先按照原先的字符编码将字节数组 解码 成字符串,然后再 getBytes 指定需要转换的字符集进行 编码,返回编码后的字节数组。
不同字符编码间转换
String str = "中文"; //这是个unicode
byte[] gbk_bytes = str.getBytes("GBK"); //unicode -> GBK
byte[] utf8_bytes = str.getBytes("UTF-8"); //unicode -> UTF-8
byte[] iso8859_bytes = str.getBytes("ISO-8859-1"); //unicode -> ISO-8859-1
//UTF->GBK->UTF
String str11 = new String(utf8_bytes,"GBK");
String str12 = new String(str11.getBytes("GBK"),"UTF-8");
System.out.println(str11);
System.out.println(str12);
//UTF->ISO->UTF
String str21 = new String(utf8_bytes,"ISO-8859-1");
String str22 = new String(str21.getBytes("ISO-8859-1"),"UTF-8");
System.out.println(str21);
System.out.println(str22);
//GBK->UTF->GBK
String str31 = new String(gbk_bytes,"UTF-8");
String str32 = new String(str31.getBytes("UTF-8"),"GBK");
System.out.println(str31);
System.out.println(str32);
//GBK->ISO->GBK
String str41 = new String(gbk_bytes,"ISO8859-1");
String str42 = new String(str41.getBytes("ISO8859-1"),"GBK");
System.out.println(str41);
System.out.println(str42);
输出:
涓枃
中文
䏿–‡
中文
����
锟斤拷锟斤拷
ÖÐÎÄ
中文
分析:
String str11 = new String(utf8_bytes,"GBK");
字节数组utf8_bytes=[e4 b8 ad e6 96 87],按照GBK解码,因为GBK固定两位解析,所以,解析结果是 涓枃。
String str12 = new String(str11.getBytes("GBK"),"UTF-8");
将解析结果 涓枃 进行UTF-8编码,得到字节数组为[e4 b8 ad e6 96 87],再进行UTF-8解码,就会得到结果 中文。能转换成功根本原因在于:UTF-8的中文编码位数大于GBK。
同理,UTF->ISO->UTF 也能成功。
String str31 = new String(gbk_bytes,"UTF-8");
字节数组gbk_bytes=[d6 d0 ce c4],按照UTF-8解码,因为UTF-8是变长字节编码,解析会失败,解析结果是 ����。
String str32 = new String(str31.getBytes("UTF-8"),"GBK");
将解析结果 ���� 进行UTF-8编码,解析结果未知,故GBK编码也是乱码。
GBK->ISO->GBK,因为GBK比ISO的字节编码位数大,所以会成功。
结论:
- UTF-8编码可以用GB2312/GBK和ISO8859-1解码后再编回去
- GB2312/GBK编码后只能用ISO8859-1解码后再编回去
setCharacterEncoding()
该函数用来设置http请求或者相应的编码。
对于request,是指提交内容的编码,指定后可以通过getParameter()则直接获得正确的字符串,如果不指定,则默认使用iso8859-1编码,需要进一步处理。
值得注意的是在执行 setCharacterEncoding() 之前,不能执行任何 getParameter()。而且,该指定只对POST方法有效,对GET方法无效。分析原因,应该是在执行第一个getParameter()的时候,java将会按照编码分析所有的提交内容,而后续的getParameter()不再进行分析,所以setCharacterEncoding()无效。而对于GET方法提交表单是,提交的内容在URL中,一开始就已经按照编码分析所有的提交内容,setCharacterEncoding()自然就无效。
原因:iso-8859-1是JAVA网络传输使用的标准字符集,而gb2312是标准中文字符集,当你作出提交表单等需要网络传输的操作的时候,就需要把 iso-8859-1转换为gb2312字符集显示,否则如果按浏览器的gb2312格式来解释iso-8859-1字符集的话,由于两者不兼容,所以会是乱码。
有两种解决方法:
- 一种是在调用 getParameter 之前通过 request.setCharacterEncoding 设置字符编码。
- 另一种是调用new String(str.getBytes(“iso8859-1”), “UTF-8”); 编码后解码。
获取文件编码格式
使用第三方包 detector
public static String getFileCharsetName(File file) throws Exception {
CodepageDetectorProxy detector = CodepageDetectorProxy.getInstance();
/*ParsingDetector可用于检查HTML、XML等文件或字符流的编码,
* 构造方法中的参数用于指示是否显示探测过程的详细信息,为false不显示。
*/
detector.add(new ParsingDetector(false));
/*JChardetFacade封装了由Mozilla组织提供的JChardet,它可以完成大多数文件的编码测定。
* 所以,一般有了这个探测器就可满足大多数项目的要求,如果你还不放心,可以再多加几个探测器,
* 比如下面的ASCIIDetector、UnicodeDetector等。
*/
detector.add(JChardetFacade.getInstance());
detector.add(ASCIIDetector.getInstance());
detector.add(UnicodeDetector.getInstance());
Charset charset = null;
try {
charset = detector.detectCodepage(file.toURI().toURL());
} catch (Exception e) {
e.printStackTrace();
throw e;
}
String charsetName = "GBK";
if (charset != null) {
if (charset.name().equals("US-ASCII")) {
charsetName = "ISO_8859_1";
} else if (charset.name().startsWith("UTF")) {
charsetName = charset.name();// 例如:UTF-8,UTF-16BE.
}else if(charset.name().equals("GB2312")){
charsetName="GBK";
}
}
return charsetName;//返回最终的编码格式
}