编码问题是跨系统交流的基本
下面有两个例子
- 在linux下用java创建了一个文件(这里默认代码里没有指定编码),里面包括英文和中文,然后在windows下同样用java读取这个文件,并输出,结果中文出现了乱码
- android手机和电脑的两个java程序进行信息交流,中文都是乱码。
网页大部分是用utf8编码的,在html头几行有charset的信息,在对下载下来的网页进行解析时,要注意编码,谷歌百度在对搜索结果的解析时也是用utf8的
java中的编码
字符串在java中统一用unicode表示( 即UTF-16 LE) 。如果源码文件是GBK编码, 操作系统(windows)默认的环境编码为GBK,那么编译时, JVM将 按照GBK编码将字节数组解析成字符,然后将字符转换为unicode格式的字节数组,作为内部存储,即String中有一个value
属性是char数组,用于保存字符串内容,使用Unicode编码,java中的char类型为2个字节,对应UTF-16。
java编译器采用utf8,即class文件的存储是用utf8,因为相对于utf16,utf8在处理英文占用内存小,而程序大部分都是英文。IDE设置的编码方式用于存取java源文件,对于在不同系统平台上共享代码很重要。jvm运行时的编码方式是utf16,即jvm用utf8从class文件读取程序后再转化为utf16编码的字符串,因为utf16是2个字节,统一的长度更方便jvm申请数组、计算字符串长度、执行索引操作等操作。
//Unicode只是java的内部存储形式。当调用字符串的getBytes时,默认还是会使用系统的默认字符集获取字节数组。如默认字符集是UTF-8,则下面字符串得到的其实是字符串UTF-8编码的字节数组
"字符串".getBytes()
查看getByte和String中保存的char数组之间的差异
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException {
String s = new String("字符");
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
System.out.println(bytes2hex(s.getBytes("UTF-8"))); //UTF-8编码 e5ad97e7aca6
char[] charArr = (char[])field.get(s);
System.out.println(char2hex(charArr)); //Unicode编码 5b577b26
}
//将byte数组转为16进制表示
public static String bytes2hex(byte[] bytes)
{
StringBuilder sb = new StringBuilder();
String tmp = null;
for (byte b : bytes)
{
// 将每个字节与0xFF进行与运算
tmp = intToHex(0xFF & b);
sb.append(tmp);
}
return sb.toString();
}
//将char数组转为16进制表示
public static String char2hex(char[] charArr)
{
StringBuilder sb = new StringBuilder();
String tmp = null;
for (char c : charArr)
{
tmp = intToHex(c);
sb.append(tmp);
}
return sb.toString();
}
public static String intToHex(int i) {
// 借助于Integer再转化为16进制
String tmp = Integer.toHexString(i);
if (tmp.length() == 1)// 每个字节8位,转为16进制标志,2个16进制位
{
tmp = "0" + tmp;
}
return tmp;
}
当打印字符串时,JVM 根据操作系统本地的语言环境,将unicode转换为GBK,然后操作系统将GBK格式的内容显示出来。
当源码文件是UTF-8, 我们需要通知编译器源码的格式,javac -encoding utf-8 … , 编译时,JVM按照utf-8 解析成字符,然后转换为unicode格式的字节数组, 那么不论源码文件是什么格式,同样的字符串,最后得到的unicode字节数组是完全一致的,显示的时候,也是转成系统默认字符集来显示(跟OS环境有关)
Unicode
Unicode 是一种字符集,Unicode 的学名是 “Universal Multiple-Octet Coded Character Set”,简称为 UCS。UCS 可以看作是 “Unicode Character Set” 的缩写。2016-06-21 颁发的 Unicode 9.0 共收录 128,237 个字。
unicode的 2 字节形式通常称作 UCS-2。然而,受制于 2 字节数量的限制,UCS-2 只能表示最多 65536 个字符。Unicode 的 4 字节形式被称为 UCS-4 或 UTF-32,能够定义 Unicode 的全部扩展,最多可定义 100 万个以上唯一字符。
UCS-4 是一个更大的尚未填充完全的 31 位字符集,加上恒为 0 的首位,共需占据 32 位,即 4 字节。理论上最多能表示 2^31 个字符,完全可以涵盖一切语言所用的符号。
UCS 只是规定如何编码,并没有规定如何传输、保存这个编码。例如 “汉” 字的 UCS 编码是 6C49,我可以用 4 个
ascii 数字来传输、保存这个编码;也可以用 utf-8 编码 3 个连续的字节 E6 B1 89 来表示它。关键在于通信双方都要认可。UTF-8、UTF-7、UTF-16 都是被广泛接受的方案。UTF 是 “UCS Transformation Format” 的缩写。
编码转换
网上流传着一种错误的方法:
GBK => UTF-8: new String(s.getBytes(“GBK”) , "UTF-8);
,这种方式是错误的
在 tomcat 下,却可以使用 new String(s.getBytes(“iso-8859-1”) ,“GBK”) 的原因:
tomcat 默认使用 iso-8859-1 编码。如果原本字符串是 GBK 的,tomcat 传输过程中,将 GBK 转成 iso-8859-1 了。 而 iso-8859-1 是单字节编码的,这种转换不会对原来的字节数组做任何改变。之前用 GBK 编码转成 iso-8859-1 后编码内容完全没变, 所以s.getBytes(“iso-8859-1”)
实际上还是原来 GBK 的编码内容。
LANG=C
linux下的LANG=C是最早最简单的C语言环境(标准ASCII码)
当不加LANG=C的时候使用机器或者登陆到机器的客户端自动设定的语言环境,一般多见的是zh_CN.UTF-8或者en_US.GBK,不论哪一种他们的字母表都是aA交错顺序。在路径匹配中也就是[a-z]包含了英文字母的大小写。
Properties文件
properties配置文件使用的是unicode码。不可以直接在里面写中文,JDK提供了native2ascii这个方法。
-
只转换特定字符
在控制台中可以输入汉字回车后,就可以看到转义的字符了。Ctrl+C退出。
-
转换properties文件
native2ascii allMessages_zh_CN.input.properties allMessages_zh_CN.properties
- 反向单一properties文件
native2ascii -reverse allMessages_zh_CN.properties allMessages_zh_CN.txt
使用java程序转换
public static String str2unicode(final String gbString) {
char[] utfBytes = gbString.toCharArray();
String unicodeBytes = "";
for (int byteIndex = 0; byteIndex < utfBytes.length; byteIndex++) {
String hexB = Integer.toHexString(utfBytes[byteIndex]);
if (hexB.length() <= 2) {
hexB = "00" + hexB;
}
unicodeBytes = unicodeBytes + "\\\\u" + hexB;
}
return unicodeBytes;
}
参考资料
Java 正确的做字符串编码转换
LANG=C有何具体作用?
Unicode(UTF-8, UTF-16)令人混淆的概念
unicode编码是占用几个字节?
java使用unicode为默认编码是什么意思