java项目中的编码问题
一、编码问题产生的原因
网络中的信息传输使用的是二进制,同一个字,使用不同的编码方式产生的二进制是不同的。
如‘中’这个字的utf-8编码是:111001001011100010101101,gbk编码是:1101011011010000。
如果在网络传输中,浏览器发送来的编码是utf8,而服务器使用gbk来接收,数据就会乱码。
二、java本身的编码方式
java内部有他自己的编码方式,从外部输入的二进制编码,一旦被解码,不管成功还是失败,都会以java内部编码储存。下面介绍java内的编码。
char类型支持unicode编码,这句话严格来说并不准确,char只支持unicode的第0个平面。其他辅助平面的字符,char无法表示。
String字符串本质上是一个char数组,但是string是支持全部的unicode的,那么它是怎么做到呢。
String字符串使用utf-16编码,unicode第0平面的字符只使用一个char表示,其他平面的字符按utf16编码,使用两个char表示。
注:String可以通过char[]或byte[]来构造,但是java中String的utf16编码并不准确。
例:‘𠀃’这个字的unicode编码是0x20003,它的utf16编码是:11011000_01000000_11011100_00000011
在java中通过utf16编码得到的二进制字符串是:111111101111111111011000010000001101110000000011,并不正确。
同样的通过byte[]构造String同样无法成功。
下面是通过char[]和byte[]构造String:
1、char:
/** * 将unicode编码转化成utf16编码字节 * 转码规则: * 1、unicode码0x0000~0xffff范围内的字符,不转化,使用连个byte(一个char)表示 * 2、unicode码0x10000~0x10ffff范围的字符,使用4个byte(2个char)表示 * a、先将unicode减去0x10000 * b、再写成20个bit的二进制码,xxxx_xxxxxxyy_yyyyyyyy * c、前10位前加110110,后10位前加110111,组成一个4个byte的二进制 * @param unicode * @return */ public char[] unicodeToUtf16Char(int unicode) { //1、先减去0x10000 int unicodeCut = unicode - 0x10000; //2、转化成二进制字符串 String binaryStr = Integer.toBinaryString(unicodeCut); //3、二进制字符串补零,20个bit binaryStr = addZero(binaryStr); //4、拼装成4个byte的二进制 String highStr = "110110" + binaryStr.substring(0, 10); String lowStr = "110111" + binaryStr.substring(10); char[] chars = new char[2]; chars[0] = (char)(int)Integer.valueOf(highStr, 2); chars[1] = (char)(int)Integer.valueOf(lowStr, 2); return chars; } /** * 二进制补零 * @param binaryStr * @return */ public String addZero(String binaryStr) { if(binaryStr.length()<=20) { int addZero = 20 - binaryStr.length(); for(int i=0;i<addZero;i++) { binaryStr = "0"+binaryStr; } return binaryStr; }else { return null; } } @Test public void test() { System.out.println(new String(unicodeToUtf16Char(0x20003))); }
测试类打印结果:
2、byte[]
/** * 将unicode编码转化成字节数组 * @param unicode * @return * @throws Exception */ public byte[] unicodeToUtf16Byte(int unicode) throws Exception { //1、先减去0x10000 int unicodeCut = unicode - 0x10000; //2、转化成二进制字符串 String binaryStr = Integer.toBinaryString(unicodeCut); //3、二进制字符串补零,20个bit binaryStr = addZero(binaryStr); //4、拼装成4个byte的二进制 String highStr = "110110" + binaryStr.substring(0, 10); String lowStr = "110111" + binaryStr.substring(10); String utf16BinaryStr = highStr + lowStr; byte[] utf16Bytes; utf16Bytes = new byte[4]; utf16Bytes[0] = valueOfByte(utf16BinaryStr.substring(0, 8), 2); utf16Bytes[1] = valueOfByte(utf16BinaryStr.substring(8, 16), 2); utf16Bytes[2] = valueOfByte(utf16BinaryStr.substring(16, 24), 2); utf16Bytes[3] = valueOfByte(utf16BinaryStr.substring(24, 32), 2); return utf16Bytes; } /** * 二进制补零 * @param binaryStr * @return */ public String addZero(String binaryStr) { if(binaryStr.length()<=20) { int addZero = 20 - binaryStr.length(); for(int i=0;i<addZero;i++) { binaryStr = "0"+binaryStr; } return binaryStr; }else { return null; } } /** * 其他进制字符串转换成byte类型 * @param s 需要转换的字符串 * @param radix 指定字符串的进制 * @return * @throws Exception */ public byte valueOfByte(String s,int radix) throws Exception { int a = Integer.valueOf(s, radix); if(a >255) { throw new Exception("字符串超出八位"); } //负数计算规则:补码+1,再加负号 a = -((a^0b11111111) + 1); return (byte) a; } @Test public void test() throws Exception { byte[] utf16 = unicodeToUtf16Byte(0x20003); System.out.println("打印二进制字符串。。。"); System.out.println(bytesToBinaryString(utf16)); System.out.println(new String(utf16)); }
测试类结果:
打印二进制字符串。。。 11011000010000001101110000000011 �@�
3、String字符串getBytes的特性
String的getBytes函数允许指定编码方式,但是,如果不指定编码,则默认跟随系统或编译器。
在编译器中运行代码,得到的是编译器设置的编码的byte数组;部署到操作系统时,得到操作系统的编码的byte数组。windows默认是gbk。
通常部署到windows上的项目,打印到控制台的编码乱码,既是因为打印到控制的的编码是utf8,而cmd窗口解读用的是gbk。