来源:https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/#icomments
在计算机用使用0、1来保存数据,存储的单位是字节(8bit/8位),每字节保存的最大数字是256,只保存英文可以,但是加上汉字就需要扩展了。
ASCII编码
总共有128位,用一个字节的低7位表示,0-31是控制字条换行回车删除等,32-126是可打印字符。
ISO-8859-1
ISO组织在ASCII码基础上又制定了一些列标准用来扩展ASCII编码,它们是ISO-8859-1~ISO-8859-15,其中ISO-8859-1涵不盖了大多数西欧字符。ISO-8859-1仍为单字节编码,共能表示256个字符。
GB2312
全称叫《信息交换用汉字编码字符集基本集》,双字节编码,总范围是A1~F7,其中A1~A9是符号区,共包含682个符号。从B0~F7是汉字区,包含6763个汉字。
GBK
全称叫《汉字内码扩展规范》,扩展GB2312,能表示21003个汉字,与GB2312兼容。
GB18030
全称叫《信息交换用汉字编码字符集》,与GB2312兼容。国家标准,但使用中并不广泛。
UTF-16
UTF-16具体定义了Unicode(Universal Code统一码)字符在计算机中存取方法。用两个字节来表示任何字符,共16个bit,所以叫UTF-16。Java以UTF-16作为内存的字符存储模式。
UTF-8
UTF-16统一采用两个字节表示一个字符,虽然方便,但有很大一部分的字符用一个字节就可以表示的现在要用两个字节表示,存储空间放大了一倍。
UTF-8采用了变长技术,规则如下:
1、如果一个字节,最高位(第8位)是0,表示这是一个ASCII字条(00~7F)。可见,所有ASCII编码已经是UTF-8了。
2、如果一个字节,以11开头,连续的1的个数暗示这个字符的字节数,例如:110xxxxx代表字是双字节UTF-8字符的首字节。
3、如果一个字节,以10开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节。
如下代码:打印出编码的16进制
public static void main(String[] args) { String test = "a 北京"; System.out.println(Arrays.toString(test.getBytes())); printHex(test.getBytes()); try { byte[] iso8859 = test.getBytes("ISO-8859-1"); printHex(iso8859); byte[] gb2312 = test.getBytes("GB2312"); printHex(gb2312); byte[] gbk = test.getBytes("GBK"); printHex(gbk); byte[] utf16 = test.getBytes("UTF-16"); printHex(utf16); byte[] utf8 = test.getBytes("UTF-8"); printHex(utf8); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } public static void printHex(byte[] array){ for(byte aByte : array){ System.out.print(Integer.toHexString(aByte & 0xFF) + " "); } System.out.println(); }
输出结果:
[97, 32, -79, -79, -66, -87] 61 20 b1 b1 be a9 61 20 3f 3f 61 20 b1 b1 be a9 61 20 b1 b1 be a9 fe ff 0 61 0 20 53 17 4e ac 61 20 e5 8c 97 e4 ba ac
1。System.out.println(Arrays.toString(test.getBytes()));默认打印出系统默认编码(GBK)的结果
97 32 -79 -79 -66 -87(其中97对应ascii中的a,32对应ascii中的空格)
2. printHex(test.getBytes()); 按16进制打印结果 61 20 b1 b1 be a9
3. "ISO-8859-1"编码将[b1 b1]转换为 3f, [be a9]转换为3f.ISO-8859-1是单字节编码,中文被转化成3f的byte,也就是“?”字符。中文字符经过ISO-8859-1编码会丢失信息,会把不认识的字符吸收掉。
4. "GB2312",英文字母保存为1个字节,汉字保存为两字节 北->[b1 b1],京 -> [be a9]
5."GBK" 编码同GB2312
6."UTF-16",每个字母或汉字都保存为两个字节.
[fe ff 0 61 0 20 53 17 4e ac]结果中: [fe ff]表示 Big Endian。
Big Endian:假设一个字符由两上字节来表示 0xabcd,那么在存储时是按[ab cd] 来存储还是按[cd ab]的顺利来存储。如果按[ab cd]来存储,则称为 big Endian, 如果存储按[cd ab],则称为 Little Endian。
00 61 为字符"a", [00 20] 为空格, [53 17]代表UTF-16中的“北”,[4e ac]为“京”
7."UTF-8"结果:61 20 e5 8c 97 e4 ba ac
其中 61的二进制为( 0110 0001)根据UTF-8的规则,首位为0,表示这是一个ASCII码,一个字节表示,得到“a",同理[20]得到空格。
第三个字节e5二进制为(1110 1001)为111开头,表示这是一个三字节的开始。第四个字节 8c 二进制(1000 1100)表示为一个字节延续,第五个字节 97二进制为(1001 0111)。
e5->1110 1001去除表示字节开始的前四位,得到 --> 1001,
8c-> 1000 1100 去除表示顺序的前两位,得到--> 001100,
97-> 1001 0111 去除表示顺序的前两位,得到--> 010111;
将1001, 001100, 010111组合 [1001, 0011, 0001, 0111]得到16进制[53, 17]正好与UTF-8中的“北”相对应。
同理,“e4 ba ac”代表“京”
在代码打印16进制的方法中
System.out.print(Integer.toHexString(aByte & 0xFF) + " ");
一个Byte值与0xFF后调用Integer的toHexstring方法。原因在于若该byte值为负时,例: -79,Java的byte保存为1个字节,补码的结果为(1011 0001,16进制 0xb1),会强转为Java中的负数的int,java中的int保存为4字节(11111111 11111111 11111111 10110001,16进制为 0xffffffb1).
在此处与0xFF后,会将int中的前三个字节中的1改为0,这样得到的结果就会为0xb1.
Integer中的toHexString()
Integer中的tohexString, toBinaryString, toOctalString 都会调用方法toUnsignedString( int i, int shift),参数 shift的值,toBinaryString为1,toOctalString, toHexString为4.
private static String toUnsignedString(int i, int shift) { char[] buf = new char[32]; int charPos = 32; int radix = 1 << shift; //shift为4,相当于*2^4=8 int mask = radix - 1; //mask=7( 0000 1111) do { // i & mask 得到i的最未端四位二进行数字,例 i=25(0001 1001), i&mask=9(1001) //digits为数组,第9个得到char[9]=‘9‘ buf[--charPos] = digits[i & mask]; // >>>为无符号位右移,高位补0. ps:25(0001 1001)向右移动4位得到1(0000 0001) // >> 为有符号位右移,若为正数,高位补0,若为负数,高位补1. i >>>= shift; } while (i != 0); return new String(buf, charPos, (32 - charPos)); }