0
点赞
收藏
分享

微信扫一扫

Java 处理字符编码

注:在上一篇涉及到Windows和C语言关于字符编码的问题,我们来看一下Java对于字符编码的处理方式;

至于UTF-8.UTF-16,UTF-32的含义,不再重复;Unicode是一个标准,如何存储这个信息,是编码方案处理的方式:UTF就是其中一种。

为了更好的兼容语言和跨平台,Java String 保存的就是字符的Unicode码

它以前使用UCS-2编码方案来存储Unicode,后来发现BMP范围内字符不够用

但是出于内存类型和兼容性的考虑,并没有提升到UCS-4(固定4字节编码)

而是采用了UTF-16

实践:char类型可以看作是16bits字节,如果是在BMP范围内,2字节可以保存的就没有问题。若是BMP之外的字符,他的计算就会有问题。

length()返回的就是16bits作为单位的数量,而不是实际字符的个数。

charAt()返回的也自然就是16bits作为单位;

java编译的时候还是不会处理 0xFFFF之外的Unicode字面量变量;

所以你输入法无法直接打印出来的,知道Unicode,你可以手动计算出来UTF-16(4字节)

把前后两个字节都单独作为一个Unicode,一块赋值给String即可

例子:

Unicode:0x1D11E

查询计算得到UTF-16:变成两个Unicode

D834  DD1E ----" \uD834\uDD1E" 就可以了


注:---------超出0xFFFF------------

按照规则,

Unicode编码0x10000-0x10FFFF的UTF-16编码有两个WORD,

第一个WORD的高6位是110110,

第二个WORD的高6位是110111。

可见,

第一个WORD的取值范围(二进制)是11011000 00000000到11011011 11111111,即0xD800-0xDBFF。

第二个WORD的取值范围(二进制)是11011100 00000000到11011111 11111111,即0xDC00-0xDFFF。

上面所说的从U+D800到U+DFFF的码位(代理区),

就是为了将一个WORD(2字节)的UTF-16编码与两个WORD的UTF-16编码区分开来。

再回到Windows下,记事本中有一个另存为Unicode编码,还是有点歧义。其实是按照UTF-16的方案来保存。

在BMP范围内的UTF-16编码值和Unicode数值是相等的。

可以了解一下高位优先(Unicode big endian)

问题:世界上还存在很多非Unicode的字符,那是另一套编码标准。

毕竟Unicode的空间需求更大,比ANSI大,所以还是有必要存在的

可以建立一个转换平台,将其他标准的字符统一处理成Unicode

WIndows内置函数,全是自动转换为Unicode,所以建议直接使用Unicode,否则会调用这些可能存在bug的转换程序,耗时耗力。

这个转换其实就是维护了不同的编码标准的映射关系,微软称之为Code Page

cp936 是 GBK的Code Page;cp65001是UTF-8的Code Page

应该也有反向映射表 Unicode->GBK

基本流程:

GBK转UTF-8.

第一步,在GBK的编码标准中,找到GBK字符编码,

第二步,在GBK的Code Page 中查到Unicode数值‘

第三步,根据Unicode计算或者查UTF-8的Code Page得到UTF-8的编码实现

注:UTF-8是8bits的,对重要的一步就是第一步,得到正确的Unicode;

这就是转码丢失问题的本质:Code Page的选择有问题

例子:JSP是使用ISO-8859-1去解码HTTP请求参数,就是来使用这一种Code Page

new String(s.getBytes("iso-8859-1"),"UTF-8");

第一步就是根据iso-8859-1找到Unicode数值,然后转为UTF-8的方案

ISO-8859-1是按照字节编码的,不同于ASCII,ISO-8859-1对于0~255空间的每一位都进行了编码,所以任意一个字节都能在它的Code Page找到对应的Unicode数值;

因为有了Unicode,相互之间转化都是没有丢失的;这就是ISO-8859-1的思路注:欧美程序员直接使用JSP解码好的String,其他国家的语言,需要转化为原始字节流,再用对应的Code Page找到解码自己的符号

Java String

构造方法:就是将各种编码数据转为Unicode数值,但是使用UTF-16的编码方案存储,想获得Unicode,就需要对String 进行UTF-16解码即可

 //GBK(大陆使用)编码 你好
byte[] gbkData = {
(byte) 0xc4,
(byte) 0xe3,
(byte) 0xba,
(byte) 0xc3
};
//BIG5(台湾使用)编码 你好
byte[] big5Data = {
(byte) 0xa7,
(byte) 0x41,
(byte) 0xa6,
(byte) 0x6e
};
//String 构造就是将不同编码数据转为Unicode
//使用GBK Code Page 来查找对应字符的Unicode;
String str1 = new String(gbkData,"GBK");
//使用GBK Code Page 来查找对应字符的Unicode;
String str2 = new String(big5Data,"BIG5");

//查看Unicode是否一样?
showUnicode(str1);
showUnicode(str2);

}
public void showUnicode(String str){
for(int i=0;i<str.length();i++){
System.out.printf("\\u%x",(int)str.charAt(i));
}
System.out.println();
}

结果如下截图

Java 处理字符编码_java

自然一定是一样的,有错误,只有一个可能:Code Page需要重新维护了

Java String

构造方法:就是将各种编码数据转为Unicode数值;

而问题的关键就是Code Page,需要针对你传递过来的编码数值,指定对字符集,使用该字符集的Code Page,就一定可以解码出来正确一致的Unicode数值。

问题:如果知道Unicode数值,使用String怎么转换到其他编码,而不是UTF-16?

Ans:根据你指定的字符集,就回去Code Page查询Unicode对应的该字符集的编码值。如果该字符集没有该Unicode,这个是很常见,毕竟其他的字符集都比较小。那么就会转换失败;而且转成啥,谁也无法确定。那自然就永久丢失Unicode数值。无法复原

Unicode是一个桥梁,编码之间先找到这个值,然后使用Code Page查找对应的关系

例子:

GBK转为BIG5

第一步:拿到GBK的字符编码:byte[]

byte[] gbkData = {
(byte) 0xc4,
(byte) 0xe3,
(byte) 0xba,
(byte) 0xc3
};

第二步:根据Code Page查找Unicode数值:\u的数值

String str1 = new String(gbkData,"GBK");

第三步:将标准的Unicode通过目标编码集的Code Page找到对应的编码数值:byte[]

byte[] testbig5 = str1.getBytes("big5");
//打印测试一下
System.out.println(new String(testbig5,"big5"));
// 也可以用16进制打印一下
public void showBytes(byte[] data){
for (byte b:data){
// 0x 16进制
System.out.printf("0x%x\n",b);
}
System.out.println();
}

问题:ISO-8859-1有啥特殊的地方,解码比较好

ans:它本身的库还是比较大的。能够查到很多Unicode值;

第一步:拿到某个编码的数据byte

byte[] data = {***,***,***};

第二步:使用ISO-8859-1的Code Page解码

String IsoStr = new String(data,"ISO-8859-1");

第三步:如果你拿着ISO-8859-1解码好的数值,直接显示出现问题,

System.out.println(ISOStr);

那么就可以

第四步:反查ISO-8859-1的Code Page

byte[]   UnicodeData = ISOStr.getBytes("ISO-8859-1");

第五步:打印一下byte[],你会发现它还原到data了

   showBytes(UnicodeData);
public void showBytes(byte[] data){
for (byte b:data){
// 0x 16进制
System.out.printf("0x%x\n",b);
}
System.out.println();
}

第六步:不使用ISO-8859-1,询问数据来源处,数据的编码是啥,使用对应的字符集解码

如果不知道,那就解不了;但是原始数据还在;

这就是ISO-8859-1的特殊之处。

也就是即使得到了错误的Unicode,通过ISO-8859-1也能反查到之前的原始数据,再选择别的重新解码​

对比:

ASCII 虽然也是每个字节对应一个字符,但是它只对0~127进行了编码

也就是每个字节最大为0x7F ;当字节0xe4这样大于0x7F,ASCII就找不到对应的字节;(也就是Code Page 查不到字符对应的Unicode)

针对这种情况,Java确定了一个默认值,找不到就使用字符?,也就是

\u fffd 

当Unicode反查的时候也找不到时,就是用默认值 0x3f;

所以就可以解释java的一些输出结果了

注:尽量别使用Windows记事本,因为它会在UTF-8文件最前面加一个3bits的BOM头

很多程序不兼容这个情况

但是在Windows环境中编译,尤其是默认环境:简体中文,GBK字符集解码

你就会看到GBK 在Code Page 有很多字符找不到对应的Unicode

经典报错:编码GBK的不可映射字符

就是查不到Code Page对应关系嘛

追加:单个中文编译失败,偶数个中文就会成功

ANS:Java String内部使用时Unicode,编译的时候就会对

字面量值进行转码,数字不要,字符串需要。

从源文件转换为Unicode

没有指定encoding,就会编译器默认使用GBK解码

UTF8:一个中文一般需要三个字节去编码(8bits*3=24bits)

GBK:  一个中文一般需要两个字节去编码(8bits*2=16bits)

所以,奇偶性会影响结果:

简单来说,如果两个字符,UTF-8需要6个字节,以GBK方式,6个字节按照顺序,可以解码三个字符;

如果是1个字符,UTF-8是3个字节。以GBK解码,那么就多出来一个字节,Java就会用默认值?

例子:

中国两个字符的

UTF-8的编码方案是 e4 b8 ad e5 9b bd

这个编码顺序被GBK解码后是  6d93 e15e 6d57(这三个字符一定是别的字符)

经过GBK解码后,编译后,这3个Unicode会在class文件中

其中JVM中使用的是Unicode,输出是根据字符集转换传递给客户端

这个字符集就是系统区域设置的编码,也就是属于本地化。

这三个Unicode 中 e15e 没有对应的字符。所以不同平台不同字体下会显示不同。

拓展:GBK编码保存的字符,然后使用UTF-8解码,基本上都无法通过编译。

因为UTF-8的编码很有规律,随意组合的字节是不会符合UTF-8的编码规则。

Code Page自然找不到合理的映射。


注:如果想要编译器正确转换为Unicode,必须正确直接告诉编译器源文件的编码。(老老实实的,小动作不要)


举报

相关推荐

0 条评论