Java
字符编码转换过程说明
常见问题
JVM
JVM
启动后,
JVM
会设置一些系统属性以表明
JVM
的缺省区域。
user.language,user.region,file.encoding
等。
可以使用
System.getProperties()
详细查看所有的系统属性。
如在英文操作系统
(
如
UNIX)
下,可以使用如下属性定义强制指定
JVM
为中文环境
-Dclient.encoding.override=GBK -Dfile.encoding=GBK -Duser.language=zh -Duser.region=CN
.java--
>
.class
编译
说明:一般
javac
根据当前
os
区域设置
,
自动决定源文件的编码
.
可以通过
-encoding
强制指定
.
错误可能
:
1 gbk
编码源文件在英文环境下编译
,javac
不能正确转换
.
曾见于
java/jsp
在英文
unix
下
.
检测方法
:
写
\u4e00
格式的汉字,绕开
javac
编码
,
再在
jvm
中
,
将汉字作为
int
打印,看值是否相等;或直接以
UTF-8
编码打开
.class
文件,看看常量字符串是否正确保存汉字。
文件读写
外部数据如文件经过读写和转换两个步骤,转为
jvm
所使用字符。
InputStream/OutputStream
用于读写原始外部数据,
Reader/Writer
执行读写和转换两个步骤。
1
文件读写转换由
java.io.Reader/Writer
执行;输入输出流
InputStream/OutputStream
处理汉字不合适
,
应该首选使用
Reader/Writer
,如
FileReader/FileWriter
。
2 FileReader/FileWriter
使用
JVM
当前编码读写文件
.
如果有其它编码格式
,
使用
InputStreamReader/OutputStreamWriter
3 PrintStream
有点特殊,它自动使用
jvm
缺省编码进行转换。
读取
.properties
文件
.propeties
文件由
Properties
类以
iso8859-1
编码读取,因此不能在其中直接写汉字,需要使用
JDK
的
native2ascii
工具转换汉字为
\uXXXX
格式。命令行:
native2ascii –encoding GBK inputfile outputfile
读取
XML
文件
1 XML
文件读写同于文件读写,但应注意确保
XML
头中声明如
<? xml version=”1.0” encoding=”gb2312” ?>
与文件编码保持一致。
2 javax.xml.SAXParser
类接受
InputStream
作为输入参数,对于
Reader
,需要用
org.xml.sax.InputSource
包装一下,再给
SAXParser
。
3
对于
UTF-8
编码
XML
,注意防止编辑器自动加上
\uFFFE BOM
头
, xml parser
会报告
content is not allowed in prolog
。
字节数组
1
使用
new String(byteArray,encoding)
和
String.getBytes(encoding)
在字节数组和字符串之间进行转换
也可以用
ByteArrayInputStream/ByteArrayOutputStream
转为流后再用
InputStreamReader/OutputStreamWriter
转换。
错误编码的字符串
(iso8859-1
转码
gbk)
如果我们得到的字符串是由错误的转码方式产生的,例如:对于
gbk
中文,由
iso8859-1
方式转换,此时如果用调试器看到的字符串一般是
的样子,长度一般为文本的字节长度,而非汉字个数。
可以采用如下方式转为正确的中文:
text = new String( text.getBytes(“iso8859-1”),”gbk”);
JDBC
转换过程由
JDBC Driver
执行,取决于各
JDBC
数据库实现。对此经验尚积累不够。
1
对于
ORACLE
数据库,需要数据库创建时指定编码方式为
gbk
,否则会出现汉字转码错误
2
对于
SQL Server 2000
,最好以
nvarchar/nchar
类型存放文本,即不存在中文
/
编码转换问题。
3
连接
Mysql
,将
connectionString
设置成
encoding
为
gb2312
:
String connectionString = "jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=gb2312";
WEB/Servlet/JSP
1
对于
JSP
,确定头部加上
<%@ page contentType="text/html;charset=gb2312"%>
这样的标签。
2
对于
Servlet
,确定
设置
setContentType (“text/html; charset=gb2312”)
,以上两条用于使得输出汉字没有问题。
3
为输出
HTML head
中加一个
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
,让浏览器正确确定
HTML
编码。
4
为
Web
应用加一个
Filter
,确保每个
Request
明确调用
setCharacterEncoding
方法
,
让输入汉字能够正确解析。
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletRequest;
/**
* Example filter that sets the character encoding to be used in parsing the
* incoming request
*/
public class SetCharacterEncodingFilter
implements Filter {
public SetCharacterEncodingFilter()
{}
protected boolean debug = false;
protected String encoding = null;
protected FilterConfig filterConfig = null;
public void destroy() {
this.encoding = null;
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// if (request.getCharacterEncoding() == null)
// {
// String encoding = getEncoding();
// if (encoding != null)
// request.setCharacterEncoding(encoding);
//
// }
request.setCharacterEncoding(encoding);
if ( debug ){
System.out.println( ((HttpServletRequest)request).getRequestURI()+"setted to "+encoding );
}
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
this.encoding = filterConfig.getInitParameter("encoding");
this.debug = "true".equalsIgnoreCase( filterConfig.getInitParameter("debug") );
}
protected String getEncoding() {
return (this.encoding);
}
}
web.xml
中加入:
<filter>
<filter-name>LocalEncodingFilter</filter-name>
<display-name>LocalEncodingFilter</display-name>
<filter-class>com.ccb.ectipmanager.request.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>gb2312</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>false</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>LocalEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
5
用于
Weblogic
(
vedor-specific
):
其一
:
在
web.xml
里加上如下脚本
:
<context-param>
<param-name>weblogic.httpd.inputCharset./*</param-name>
<param-value>GBK</param-value>
</context-param>
其二(可选)在
weblogic.xml
里加上如下脚本
:
<charset-params>
<input-charset>
<resource-path>/*</resource-path>
<java-charset-name>GBK</java-charset-name>
</input-charset>
</charset-params>
SWING/AWT/SWT
对于
SWING/AWT
,
Java
会有些缺省字体如
Dialog/San Serif
,这些字体到系统真实字体的映射在
$JRE_HOME/lib/font.properties.XXX
文件中指定。排除字体显示问题时,首先需要确定
JVM
的区域为
zh_CN
,这样
font.properties.zh_CN
文件才会发生作用。对于
font.properties.zh_CN ,
需要检查是否映射缺省字体到中文字体如宋体。
在
Swing
中,
Java
自行解释
TTF
字体,渲染显示;对于
AWT,SWT
显示部分交由操作系统。首先需要确定系统装有中文字体。
1
汉字显示为
”
□
”
,一般为显示字体没有使用中文字体,因为
Java
对于当前字体显示不了的字符,不会像
Windows
一样再采用缺省字体显示。
2
部分不常见汉字不能显示,一般为显示字库中汉字不全,可以换另外的中文字体试试。
3
对于
AWT/SWT
,首先确定
JVM
运行环境的区域设置为中文,因为此处设计
JVM
与操作系统
api
调用的转换问题,再检查其它问题。
JNI
JNI
中
jstring
以
UTF-8
编码给我们,需要我们自行转为本地编码。对于
Windows
,可以采用
WideCharToMultiByte/MultiByteToWideChar
函数进行转换,对于
Unix
,可以采用
iconv
库。
这里从
SUN jdk 1.4
源代码中找到一段使用
jvm String
对象的
getBytes
的转换方式,相对简单和跨平台,不需要第三方库,但速度稍慢。函数原型如下:
/* Convert between Java strings and i18n C strings */
JNIEXPORT jstring
NewStringPlatform(JNIEnv *env, const char *str);
JNIEXPORT const char *
GetStringPlatformChars(JNIEnv *env, jstring jstr, jboolean *isCopy);
JNIEXPORT jstring JNICALL
JNU_NewStringPlatform(JNIEnv *env, const char *str);
JNIEXPORT const char * JNICALL
JNU_GetStringPlatformChars(JNIEnv *env, jstring jstr, jboolean *isCopy);
JNIEXPORT void JNICALL
JNU_ReleaseStringPlatformChars(JNIEnv *env, jstring jstr, const char *str);
附件
jni_util.h,jni_util.c
TUXEDO/JOLT
JOLT
对于传递的字符串需要用如下进行转码
new String(ls_tt.getBytes("GBK"),"iso8859-1")
对于返回的字符串
new String(error_message.getBytes("iso8859-1"),"GBK");
jolt
的系统属性
bea.jolt.encoding
不应该设置
,
如果设置
,JSH
会报告说错误的协议
.
JDK1.4/1.5
新增部分
字符集相关类
(Charset/CharsetEncoder/CharsetDecoder)
jdk1.4
开始,对字符集的支持在
java.nio.charset
包中实现。
常用功能:
1
列出
jvm
所支持字符集:
Charset.availableCharsets()
2
能否对看某个
Unicode
字符编码,
CharsetEncoder.canEncode()
Unicode Surrogate/CJK EXT B
Unicode
范围一般所用为
\U0000-\UFFFF
范围,
jvm
使用
1
个
char
就可以表示,对于
CJK EXT B
区汉字,范围大于
\U20000
,则需要采用
2
个
char
方能表示,此即
Unicode Surrogate
。这
2
个
char
的值范围
落在
Character.SURROGATE
区域内,用
Character.getType()
来判断。
jdk 1.4
尚不能在
Swing
中正确处理
surrogate
区的
Unicode
字符,
jdk1.5
可以。对于
CJK EXT B
区汉字,目前可以使用的字库为
”
宋体
-
方正超大字符集
”,
随
Office
安装。
常见问题
在
JVM
下,用
System.out.println
不能正确打印中文,显示为
???
System.out.println
是
PrintStream
,它采用
jvm
缺省字符集进行转码工作,如果
jvm
的缺省字符集为
iso8859-1
,则中文显示会有问题。此问题常见于
Unix
下,
jvm
的区域没有明确指定的情况。
在英文
UNIX
环境下
,
用
System.out.println
能够正确打印汉字,但是内部处理错误
可能是汉字在输入转换时,就没有正确转码:
即
gbk
文本
à
(iso8859-1
转码
)
à
jvm char(iso8859-1
编码汉字
)
à
(iso8859-1
转码
)
à
输出。
gbk
汉字经过两次错误转码,原封不动的被传递到输出,但是在
jvm
中,并未以正确的
unicode
编码表示,而是以一个汉字字节一个
char
的方式表示,从而导致此类错误。
GB2312-80
,
GBK
,
GB18030-2000
汉字字符集
GB2312-80
是在国内计算机汉字信息技术发展初始阶段制定的,其中包含了大部分常用的一、二级汉字,和
9
区的符号。该字符集是几乎所有的中文系统和国际化的软件都支持的中文字符集,这也是最基本的中文字符集。其编码范围是高位
0xa1
-
0xfe
,低位也是
0xa1-0xfe
;汉字从
0xb0a1
开始,结束于
0xf7fe
;
GBK
是
GB2312-80
的扩展,是向上兼容的。它包含了
20902
个汉字,其编码范围是
0x8140-0xfefe
,剔除高位
0x80
的字位。其所有字符都可以一对一映射到
Unicode 2.0
,也就是说
JAVA
实际上提供了
GBK
字符集的支持。这是现阶段
Windows
和其它一些中文操作系统的缺省字符集,但并不是所有的国际化软件都支持该字符集,感觉是他们并不完全知道
GBK
是怎么回事。值得注意的是它不是国家标准,而只是规范。随着
GB18030-2000
国标的发布,它将在不久的将来完成它的历史使命。
GB18030-2000(GBK2K)
在
GBK
的基础上进一步扩展了汉字,增加了藏、蒙等少数民族的字形。
GBK2K
从根本上解决了字位不够,字形不足的问题。它有几个特点,
它并没有确定所有的字形,只是规定了编码范围,留待以后扩充。
编码是变长的,其二字节部分与
GBK
兼容;四字节部分是扩充的字形、字位,其编码范围是首字节
0x81-0xfe
、二字节
0x30-0x39
、三字节
0x81-0xfe
、四字节
0x30-0x39
。
UTF-8/UTF-16/UTF-32
UTF
,即
Unicode Transformer Format
,是
Unicode
代码点
(code point)
的实际表示方式,按其基本长度所用位数分为
UTF-8/16/32
。它也可以认为是一种特殊的外部数据编码,但能够与
Unicode
代码点做一一对应。
UTF-8
是变长编码,每个
Unicode
代码点按照不同范围,可以有
1-3
字节的不同长度。
UTF-16
长度相对固定,只要不处理大于
\U200000
范围的字符,每个
Unicode
代码点使用
16
位即
2
字节表示,超出部分使用两个
UTF-16
即
4
字节表示。按照高低位字节顺序,又分为
UTF-16BE/UTF-16LE
。
UTF-32
长度始终固定,每个
Unicode
代码点使用
32
位即
4
字节表示。按照高低位字节顺序,又分为
UTF-32BE/UTF-32LE
。
UTF
编码有个优点,即尽管编码字节数不等,但是不像
gb2312/gbk
编码一样,需要从文本开始寻找,才能正确对汉字进行定位。在
UTF
编码下,根据相对固定的算法,从当前位置就能够知道当前字节是否是一个代码点的开始还是结束,从而相对简单的进行字符定位。不过定位问题最简单的还是
UTF-32
,它根本不需要进行字符定位,但是相对的大小也增加不少。
关于
GCJ JVM
GCJ
并未完全依照
sun jdk
的做法,对于区域和编码问题考虑尚不够周全。
GCJ
启动时,区域始终设为
en_US
,编码也缺省为
iso8859-1
。但是可以用
Reader/Writer
做正确编码转换。