字符编码
首先必须了解一下计算机中字符编码的概念。众所周知,计算机只认识二进制的内容,而人阅读则依赖于字符的内容,于是才有了诸如ASCII、utf-8、gbk和unicode等字符编码的产生。每一种字符编码都有一张映射表,分别记录了二进制内容 和 字符 之间的映射关系。将字符内容转为二进制内容的过程称为编码,而反之称为解码。每个字符编码都有明确支持字符的范围,比如ASCII和ISO-8859-1都不支持中文字符。
(ASCII部分映射表)
Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,并且规定所有字符固定长度为两个字节(byte)。
UTF-8的特点是对不同范围的字符使用不同长度的编码,如对字母和数字只占一个字节,而汉字占了2-4个字节的(不定长)。虽然据说UTF-8并不完全支持所有汉字,但大多数情况下它还是我们开发系统的首选。
(Unicode二进制 和 UTF-8二进制 之间的转换关系)
Java中的字符编码
(2)String.length() 返回的长度是字符的数量,因为String的内容保存在char[],而一个汉字也算一个字符。
Java中的String与byte[]之间的转换
//String -> byte[]
String str = "你好!bo";
byte[] utf8_bytes = str.getBytes("UTF-8");
byte[] other_bytes = str.getBytes(); //使用JDK默认的字符集编码
//byte[] -> String
String str1 = new String(utf8_bytes,"UTF-8");
String str2 = new String(other_bytes); //使用JDK默认的字符集编码
注意对于JDK默认的字符集编码,若在JDK参数中没有明确指定,则window默认为GBK,linux默认为UTF-8
这里还有一个地方是值得思考的。String本质上是由char[]实现的,而char在JVM内存里表现为unicode编码。当我们在进行String与byte[]之间的转换时,其实就是Unicode编码与其他编码的之间的格式转换(具体是Unicode编码的二进制内容与其他编码的二进制内容之间的转换),我想JVM肯定是有对应的转换关系的,毕竟计算机只能认得二进制的内容。
过程如下(不一定正确):
当我们使用String.getBytes(String charsetName)方法时,其实是将String字符串表示的二进制内容(Unicode编码)转化为charsetName编码对应的二进制内容。
当我们使用new String(byte[] bytes, String charsetName) 方法时,也应该是将bytes里的二进制内容按charsetName编码与Unicode编码的映射关系转换为Unicode编码的二进制内容,这样JVM便自动将转换好的二进制内容(Unicode编码)组装为char[]进而构造成String
乱码问题产生的原因
编码和解码使用的字符编码集不一致
String str = "你好!bo";
byte[] utf8_bytes = str.getBytes("UTF-8");
String test = new String(utf8_bytes,"iso-8859-1"); //编码解码不一致导致乱码
test = new String(test.getBytes("iso-8859-1"),"UTF-8"); //矫正回正确字符
使用了错误的字符编码集进行编码
byte[] bytes = "你好".getBytes("iso-8859-1");
这时得到的bytes是没有办法还原回正确的字符的,因为在ios-8859-1映射表里根本找不到该字符对应的二进制内容。可以进行如下测试,得到的结果为 ??
String str = new String(bytes,"iso-8859-1")
注意这种错误是不可恢复的,因为得到的bytes没有保存完整的内容,可以说这个过程是有损且不可逆的。