时间的法外狂徒

导航

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)));
    }
View Code

 测试类打印结果:

 

 

 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));
    }
View Code

测试类结果:

打印二进制字符串。。。
11011000010000001101110000000011
�@�

 3、String字符串getBytes的特性

String的getBytes函数允许指定编码方式,但是,如果不指定编码,则默认跟随系统或编译器。

在编译器中运行代码,得到的是编译器设置的编码的byte数组;部署到操作系统时,得到操作系统的编码的byte数组。windows默认是gbk。

通常部署到windows上的项目,打印到控制台的编码乱码,既是因为打印到控制的的编码是utf8,而cmd窗口解读用的是gbk。

 

posted on 2020-08-13 16:50  抄手砚  阅读(470)  评论(0编辑  收藏  举报