Java编码的那些事儿

3月 29th, 2011 4,474 留下评论 阅读评论

来上海百度实习也才没多久,这是第三个礼拜吧,接手的项目相当紧迫,而且作为一个实习生我是main coder,不由地带动了极大的工作积极性。这个一开始认为很简单很明了的“小”项目,没想到让我学到了很多东西,今天就讲讲关于Java编码的那些事儿。

Java编码

Unicode是全球标准字符集,是Java所为String采用的编码方式,任何字符用2个字节表示。String实例中保存有一个char[]字符数组。string.getByte()方法可以获得到这个字符串实例在指定编码下的字节数组,注意的是不带参数的getByte方法使用OS默认的字符集,比如GB2312(简体中文)。所以要得到Unicode下的字节数组,需要这样:string.getBytes(“unicode”)(此处注意见下文)。如果使用new String(byte[], Charset)构造,可以将已知编码的字节数组重新拼成一个String实例,即用指定的Charset去组合字节为Unicode字符罢了。同理,不带Charset的String构造使用OS默认字符集。

因此,得到UTF-8的字节数组,按以下步骤:

1
2
3
4
String str = "梦";
byte[] bytes = str.getBytes("UTF-8");   "使用UTF-8解码字符串得到的UTF-8字节数组"
String str2 = new String(bytes, "utf8"); "按照当初被解码的方式(utf8)重新组成Java String类"
str.equals(str2) == true;       "使用大小写不同的编码写法,来区别不同API中参数代表的意义"

所以,str = new String(str.getBytes(Charset), Charset) 什么都没有做,除了新建了个String对象。

但是如果你要获取unicode的字节数组,却有非常多的选择,而且很容易出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void echoBytesTest() {
	String s = "梦";
	echoBytes(s, "Unicode");
	echoBytes(s, "UnicodeBig");
	echoBytes(s, "UnicodeLittle");
	echoBytes(s, "UnicodeBigUnmarked");
	echoBytes(s, "UnicodeLittleUnmarked");
	echoBytes(s, "UTF-16");
	echoBytes(s, "UTF-16BE");
	echoBytes(s, "UTF-16LE");
	echoBytes(s, "UTF-8");
}
 
void echoBytes(String s, String encoding) {
	byte[] bytes = s.getBytes(encoding);
	for (byte b : bytes) {
		int i = b & 0xff;
		System.out.print(Integer.toHexString(i) + " ");
	}
	System.out.println();
}

JDK6.0环境下的输出结果如下所示:

Unicode fe ff 68 a6 BE顺序,带BOM 强烈申明不要使用,JDK相关
UnicodeBig fe ff 68 a6 BE顺序,带BOM 推荐使用
UnicodeLittle ff fe a6 68 LE顺序,带BOM 推荐使用
UnicodeBigUnmarked 68 a6 BE顺序,无BOM 推荐使用
UnicodeLittleUnmarked a6 68 LE顺序,无BOM 推荐使用
UTF-16 fe ff 68 a6 BE顺序,带BOM 不推荐使用,可读性不够高
UTF-16BE 68 a6 BE顺序,无BOM 不推荐使用,可读性不够高
UTF-16LE a6 68 LE顺序,无BOM 不推荐使用,可读性不够高
UTF-8 e6 a2 a6 无BOM 此为UTF-8字节串,特以此区别

然后要特别说明的是,getBytes(“Unicode”)这种方式是什么样的结果,却是与JDK相关的,JDK5.0使用LE顺序,JDK6.0使用BE顺序!

有点复杂,看官方解释。

为了在读取字节时能知道所采用的字节序,在传输时采用了一个名为 “Zero Width No-Break Space” 的字符(中文译名作“零宽无间断间隔”)用于限定字节序,开头两个字节为 FE FF(即-2 -1) 时为 Big-Endian,为 FF FE(即-1 -2) 时为 Little-Endian。 详见 RFC2781 3.2 节。这个就是传说中的BOM头。
UTF-8不需要 BOM 来表明字节顺序,但可以用 BOM 来表明编码方式。字符 “Zero Width No-Break Space” 的 UTF-8 编码是 EF BB BF。
详见:BOM_百度百科

自我理解很像体系结构中的大端编址和小端编址对吧。不知道Unicode为什么要存在Big-Endian和Little-Endian两种顺序,反正就是存在着呗。

说说项目

就是因为string.getBytes(“Unicode”)的JDK相关性,然后我因为使用了旧版本的JDK导致和大家同样从SVN签出的代码的运行结果却完全不一致,由于完全信任Java的平台无关,没有一点怀疑是JDK源码的原因,整整花了一天的时候排查bug。

第二个是Spring MVC的@ResponseBody默认返回的字符集是ISO-8859-1的西文字符,导致返回客户端为乱码,修改Spring配置的见Spring官方论坛的解决方案,不过貌似试了几种方法都没有起效…其实最简单的方式就是放弃使用Spring提供的Ajax方式,直接使用HttpServletResponse的getWriter().write()方法写字符串或getOutputStream.write()方法直接写字节。

参考引用

百度百科:http://baike.baidu.com/view/126558.htm#sub5073178

CSDN论坛:http://topic.csdn.net/u/20081009/09/e899898c-591f-4985-ae88-5972475708fb.html

Spring官方论坛:http://forum.springsource.org/showthread.php?t=81858

Categories: Java 标签:, ,
  1. 4月 24th, 2012 18:01

    这个编码问题我还从来没看这么细过,通常都没有使用Spring来处理response过程,所以很少出现过此种情况

  2. noname | #板凳
    5月 17th, 2011 10:28

    记得PHP页面也是以没有BOM的为好,有BOM的话,也是容易各种问题

    • 好像某些情况下页面开始会出现一个乱码字符

  3. 4月 4th, 2011 14:10

    悲剧。。我的暑假也没了~~导师发邮件来了 :cry:

    • 啊啊啊…要求多久时间呀?暑假没有寝室住啊,得住哪儿啊? :awkward: