最近在学习Java并发的时候,《Java并发编程的艺术》上第四章中有个基于线程池的简单Web服务器的例程,就照着书把代码敲了一遍。然后测试的时候发现,会有这个问题。看图。
没错,标题的最后一个汉字显示不正常,我的html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试页面</title>
</head>
<body align="center">
<h1>第一张图片</h1>
<img src="1.jpg" align="center">
<h1>第二张图片</h1>
<img src="2.jpg" align="center" >
<h1>第三张图片</h1>
<img src="3.jpg" align="center" >
</body>
</html>
很简单,但是不凑巧的是,我的标题是奇数,显示就会有问题,经过测试,如果不是奇数个汉字,在后面多加一个汉字,比如“第一张图片片”,都不会出错。
后来网上查找原因,很显然,这是字符集转换之间的问题。html无疑使用utf-8编码的,最后浏览器显示的编码也是utf-8。但是问题在于,我用于搭建服务器的IDE-eclipse默认是GBK编码的。。。。
那按常理,GBK<-->UTF-8互转应该没问题啊,但是为什么奇数有问题,偶数没问题?众所周知,汉字在GBK中的编码是三个字节,而UTF-8中汉字的编码是两个字节。诶?看出问题没有,但是,如果能够保证两者之间正常的转换也不会有问题呀。
先说说问题的解决,很简单,把eclipse的编码改成UTF-8就不会有问题了。如下图:右键java文件,选择properties,最下面Text file encoding
勾选上other,然后选UTF-8。
但是问题还是要深究一下,不然以后遇到类似的问题,也不知道怎么解决。
先看一下我写的测试代码,注释是实时的值。
public class EncoderError {
public static void main(String[] args) throws UnsupportedEncodingException {
String gbk = "我是谁";
byte[] gbkGetGBKBytes = gbk.getBytes();//[-50, -46, -54, -57, -53, -83]
byte[] gbkGetUTF8Bytes = gbk.getBytes("UTF-8");//[-26, -120, -111, -26, -104, -81, -24, -80, -127]
String utf8 = new String(gbkGetUTF8Bytes);//鎴戞槸璋?
byte[] utf8GetGBKBytes = utf8.getBytes();//[-26, -120, -111, -26, -104, -81, -24, -80, 63]
byte[] utf8GetUTF8Bytes = utf8.getBytes("UTF-8");//[-23, -114, -76, -26, -120, -98, -26, -89, -72, -25, -110, -117, -17, -65, -67]
String print = new String(utf8GetGBKBytes,"UTF-8");
System.out.println(print);//我是??
System.out.println(new String(gbk.getBytes("UTF-8"), "UTF-8"));//我是谁
System.out.println(new String(new String(gbk.getBytes("UTF-8")).getBytes(), "UTF-8"));//我是??
}
}
直接新建变量,“我是谁”,GBK编码,6个字节。gbk.getBytes()和gbk.getBytes("UTF-8")分别获得的是GBK(6个字节)和UTF-8(9个细节)编码。接下来注意了,
String utf8 = new String(gbkGetUTF8Bytes);//鎴戞槸璋?
这句,很奇葩,直接用UTF-8的编码,new字符串(用文件默认的GBK编码),这很显然出现乱码:“鎴戞槸璋?”。此时gbkGetUTF8Bytes数组中存了9个字节,在构造GBK编码的字符串的时候,会两个两个字节的构造,直到第九个字节,发现少了一个,这时候处理方法是,舍弃最后一个,也就是-127这个字节,然后就上‘?’字符的编码,也就是63。这时候,从UTF-8的角度讲,这个字符串的编码已经变了。从[-26, -120, -111, -26, -104, -81, -24, -80, -127]变成了[-26, -120, -111, -26, -104, -81, -24, -80, 63],然后我们用下面语句重新按照UTF-8编码,打印字符串
byte[] utf8GetGBKBytes = utf8.getBytes();
String print = new String(utf8GetGBKBytes,"UTF-8");
System.out.println(print);//我是??
就出现了乱码问题,而如果汉字数目是偶数,就不会出现第奇数个字节被舍弃,然后强行加了个63的字节上去,这样,不管GBK和UTF-8怎么变,都不会出现问题。
总结下来就是,问题出在,强行将UTF-8编码的数据用GBK编码,导致数据发生了改变,这时候再变回去UTF-8就变不回去了。
我们再来看一下,书中的简单Web服务器里面是如何对应上上面的过程的。
代码贴一段。这是读取index.html里面的文本并输出到http包中。
br= new BufferedReader(new FileReader(filename));
out = new PrintWriter(socket.getOutputStream());
out.println("HTTP/1.1 200 OK");
out.println("Server: Molly");
out.println("Content-Type: text/html; character=UTF-8");
out.println();
while((line = br.readLine()) != null){
//System.out.println(line);
out.println(line);
}
out.flush();
index.html里面是utf-8编码的,然后被eclipse以GBK的形式读出来,然后println出去,然后浏览器收到字节数据后,以UTF-8显示出来。数据编码由utf-8 --> GBK --> utf-8.
问题就在于
while((line = br.readLine()) != null){
System.out.println(line);
out.println(line);
}
line = br.readLine(),这里将UTF-8的数据强行以GBK编码,然后写出,这等同于最开始的
String utf8 = new String(gbkGetUTF8Bytes);//鎴戞槸璋?
于是,出错了!
这就是问题所在,其实讲了这么多,问题解决很简单,不要用GBK编码即可,主要是对编码的过程有了更深刻的了解。
写的第一篇,很乱,继续学习。