一、Java
编码是怎么回事?
对于使用中文以及其他非拉丁语系语言的开发人员来说,经常会遇到字符集编码问题。对于Java
语言来说,在其内部使用的是UCS2
编码(2
个字节的Unicode
编码)。这种编码并不属于某个语系的语言编码,它实际上是一种编码格式的世界语。在这个世界上所有可以在计算机中使用的语言都有对应的UCS2
编码。
正是因为Java
采用了UCS2
,因此,在Java
中可以使用世界上任何国家的语言来为变量名、方法名、类起名,如下面代码如下:
class
中国
{
public
String 雄起()
{
return
"
中国雄起
"
;
}
}
中国 祖国
=
new
中国();
System.out.println(祖国.雄起());
哈哈,是不是有点象“中文编程”。实际上,也可以使用其他的语言来编程,如下面用韩文和日文来定义个类:
class
수퍼맨
{
public
void
ス�`パ�`マン() { }
}
实际上,由于Java
内部使用的是UCS2
编码格式,因为,Java
并不关心所使用的是哪种语言,而只要这种语言在UCS2
中有定义就可以。
在UCS2
编码中为不同国家的语言进行了分页,这个分页也叫“代码页”或“编码页”。中文根据包含中文字符的多少,分了很多代码页,如cp935
、cp936
等,然而,这些都是在UCS2
中的代码页名,而对于操作系统来说,如微软的windows
,一开始的中文编码为GB2312
,后来扩展成了GBK
。其实GBK
和cp936
是完全等效的,用它们哪个都行。
二、Java
编码转换
上面说了这么多,在这一部分我们做一些编码转换,看看会发生什么事情。
先定义一个字符串变量:
String gbk = "
中国"; //
“中国”在Java
内部是以UCS2
格式保存的
用下面的语言输出一定会输出中文:
System.out.println(gbk);
实现上,当我们从IDE
输入“中国”时,用的是java
源代码文件保存的格式,一般是GBK
,有时也可是utf-8
,而在Java
编译程序时,会不由分说地将所有的编码格式转换成utf-8
编码,读者可以用UltraEdit
或其他的二进制编辑器打开上面的“中国.class
”,看看所生成的二进制是否有utf-8
的编码(utf-8
和ucs2
之间的转换非常容易,因为utf-8
和ucs2
之间是用公式进行转换的,而不是到代码页去查,这就相当于将二进制转成16
进制一样,4
个字节一组)。如“中国”的utf-8
编码按着GBK
解析就是“涓
��
”。如下图所示。
如果使用下面的语言可以获得“中国”的utf-8
字节,结果是6
(一个汉字由3
个字节组成)
System.out.println(gbk.getBytes("utf-8").length);
下面的代码将输出“涓
��
”。
System.out.println(new String(gbk.getBytes("utf-8"), "gbk"));
由于将“中国“的utf-8
编码格式按着gbk
解析,所以会出现乱码。
如果要返回中文的UCS2
编码,可以使用下面的代码:
System.out.println(gbk.getBytes("unicode")[2]);
System.out.println(gbk.getBytes("unicode")[3]);
前两个字节是标识位,要从第3
个字节开始。还有就是其他的语言使用的编码的字节顺序可能不同,如在C#
中可以使用下面的代码获得“中国“的UCS2
编码:
String s = "
中";
MessageBox.Show(ASCIIEncoding.Unicode.GetBytes(s)[0].ToString());
MessageBox.Show(ASCIIEncoding.Unicode.GetBytes(s)[1].ToString());
使用上面的java
代码获得的“中“的16
进制UCS2
编码为4E2D
,而使用C#
获得的相应的ucs2
编码为2D4E
,这只是C#
和Java
编码内部使用的问题,并没有什么关系。但在C#
和Java
互操作时要注意这一点。
如果使用下面的java
编码将获得16
进制的“中”的GBK
编码:
System.out.println(Integer.toHexString(0xff & xyz.getBytes("gbk")[0]));
System.out.println(Integer.toHexString(0xff & xyz.getBytes("gbk")[1]));
“中”的ucs2
编码为2D4E
,GBK
编码为D6D0
读者可访问如下的url
自行查验:
http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP936.TXT
当然,感兴趣的读者也可以试试其他语言的编码,如“人类”的韩语是“
인간의
”,如下面的代码将输出“
인간의
”的cp949
和ucs2
编码,其中cp949
是韩语的代码页。
String korean
=
"
인간의
"
;
//
共三个韩文字符,我们只测试第一个“인”
System.out.println(Integer.toHexString(
0xff
&
korean.getBytes(
"
unicode
"
)[
2
]));
System.out.println(Integer.toHexString(
0xff
&
korean.getBytes(
"
unicode
"
)[
3
]));
System.out.println(Integer.toHexString(
0xff
&
korean.getBytes(
"
Cp949
"
)[
0
]));
System.out.println(Integer.toHexString(
0xff
&
korean.getBytes(
"
Cp949
"
)[
1
]));
上面代码的输出结果如下:
c7
78
c0
ce
也就是说“
인
”的
ucs2
编码为
C778
,
cp949
的编码为
C0CE
,要注意的是,在
cp949
中,
ucs2
编码也有
C0CE
,不要弄混了。读者可以访问下面的
url
来验证:
http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP949.TXT
st1":*{behavior:url(#ieooui) }
Java支持的编码格式
三、属性文件
Java
中的属性文件只支持iso-8859-1
编码格式,因此,要想在属性文件中保存中文,就必须使用UCS2
编码格式("uxxxx
),因此,出现了很多将这种编码转换成可视编码和工具,如Eclipse
中的一些属性文件编辑插件。
实际上,"uxxxx
编码格式在java
和C#
中都可以使用,如下面的语句所示:
String name= ""u7528"u6237"u540d"u4e0d"u80fd"u4e3a"u7a7a" ;
System.out.println(name);
上面代码将输出“用户名不能为空”的信息。将
"uxxxx
格式显示成中文非常简单,那么如何将中文还原成
"uxxxxx
格式呢?下面的代码完成了这个工作:
st1":*{behavior:url(#ieooui) }
String ss
=
"
用户名不能为空
"
;
byte
[] uncode
=
ss.getBytes(
"
Unicode
"
);
int
x
=
0xff
;
String result
=
""
;
for
(
int
i
=
2
; i
<
uncode.length; i
++
)
{
if
(i
%
2
==
0
) result
+=
"
\\u
"
;
String abc
=
Integer.toHexString(x
&
uncode[i]);
result
+=
abc.format(
"
%2s
"
, abc).replaceAll(
"
"
,
"
0
"
);
}
System.out.println(result);
st1":*{behavior:url(#ieooui) }
上面的代码将输出如下结果:
\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a
好了,现在可以利用这个技术来实现一个属性文件编辑器了。
四、Web
中的编码问题
大家碰到最多的编码问题就是在
Web
应用中。先让我们看看下面的程序:
<!--
main.jsp
-->
<%
@ page language
=
"
java
"
pageEncoding
=
"
utf-8
"
%>
<
html
>
<
head
>
</
head
>
<
body
>
<
form
action
="servlet/MyPost"
method
="post"
>
<
input
type
="text"
name
="user"
/>
<
p
/>
<
input
type
="submit"
value
="提交"
/>
</
form
>
</
body
>
</
html
>
下面是个
Servlet
:
package servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public
class
MyPost extends HttpServlet
{
public
void
doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
String user
=
request.getParameter(
"
user
"
);
System.
out
.println(user);
}
}
st1":*{behavior:url(#ieooui) }
如果中main.jsp
中输入中文后,向MyPost
提交,在控制台中会输出“ä
¸å½
”,一看就是乱码。如果将IE
的当前编码设成其他的,如由utf-8
改为gbk
,仍然会出现乱码,只是乱得不一样而已。这是因为客户端提交数据时是根据浏览器当前的编码格式来提交的,如浏览器当前为gbk
编码,就以gbk
编码格式来提交。
这本身是不会出现乱码的,问题就出在Web
服务器接收数据的时候,HttpServletRequest
在将客户端传来的数据转成ucs2
码上出了问题。在默认情况下,是按着iso-8859-1
编码格式来转的,而这种编码格式并不支持中文,所以也就无法正常显示中文了,解决这个问题的方法是用和客户端浏览器当前编码格式一致的编码来转换,如果是utf-8
,则在doPost
方法中应该用以下的语句来处理:
request.setCharacterEncoding("utf-8");
为了对每一个Servlet
都起作用,可以将上面的语句加到filter
里。
另外,我们一般使用象
MyEclipse
一样的
IDE
来编写
jsp
文件,这样的工具会根据
pageEncoding
属性将
jsp
文件保存成相应的编码格式,但如果要使用象记事本一样的简单的编辑器来编写
jsp
文件,如果
pageEncoding
是
utf-8
,而在默认时,记事本会将文件保存成
iso-8859-1
(
ascii
)格式,但在
myeclipse
里,如果文件中有中文,它是不允许我们保存成不支持中文的编码格式的,但记事本并不认识
jsp
,因此,这时在
ie
中就无法正确显示出中文了。除非用记事本将其保存在
utf-8
格式。如下图: