IO流之DataOutputSteam和DataInputSteam原理研究

编码知识预备

  • ASCII码
    1、共计128字符;
    2、8bit即单字节字符;
    3、最高位前面统一规定为0;
    4、例如:65(二进制0100 0001)是A。
  • 非ASCII码
    1、什么是非ASCII码?
    英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。
  • Unicode
    1、什么是Unicode?
    Unicode当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字”严”。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。
    2、存在定位问题
    Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。
    例如:“汉”字的Unicode编码是0x6C49,二进制为 01101100 01001001。也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。
  • UTF-8
    1、和Unicode的关系
    UTF-8是Unicode的实现方式之一。
    2、规则
    1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
    2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
十六进制 二进制
0000 0000-0000 007F 0xxxxxxx
0000 0080-0000 07FF 110xxxxx 10xxxxxx
0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

3、例子
“严”
4E25
1001110 00100101
1110xxxx 10xxxxxx 10xxxxxx
填充后
11100100 10111000 10100101
111001001011100010100101
十进制的:14989477

DataOutputSteam和DataInputSteam原理解析

  • 概念介绍
    1、DataOutputSteam和DataInputSteam是包装流,必须要搭配使用才行,先写出,再读入;
    2、使用这两个流主要为了使用它writeByte、writeShort、writeChar、writeUTF以及读取的方法;
    3、使用了装饰器模式和桥接模式(这里不做介绍,感兴趣的可以去研究一下)
  • 主要实现原理剖析
    1、writeLong
    public final void writeLong(long v) throws IOException {
        // 从高位开始,从最高字节开始写入,共8个字节
        // (byte)(v >>> 56) 取最高位字节,存入字节缓存数组
        // 一定是按照顺序进行写入和读取的,“顺序”
        writeBuffer[0] = (byte)(v >>> 56);
        writeBuffer[1] = (byte)(v >>> 48);
        writeBuffer[2] = (byte)(v >>> 40);
        writeBuffer[3] = (byte)(v >>> 32);
        writeBuffer[4] = (byte)(v >>> 24);
        writeBuffer[5] = (byte)(v >>> 16);
        writeBuffer[6] = (byte)(v >>>  8);
        writeBuffer[7] = (byte)(v >>>  0);
        out.write(writeBuffer, 0, 8);
        incCount(8);
    }

2、writeChar

    // char自动类型转为int
    // char占两个字节
    public final void writeChar(int v) throws IOException {
        // 取高位字节
        // 思路:转为进二进制,然后分高低字节进行写入
        // 注意:读取的也要按照顺序
        out.write((v >>> 8) & 0xFF);
        // 取地位字节
        out.write((v >>> 0) & 0xFF);
        incCount(2);
    }

3、writeUTF

static int writeUTF(String str, DataOutput out) throws IOException {
        int strlen = str.length();
        int utflen = 0;
        int c, count = 0;

        /* use charAt instead of copying String to char array */
        // 这里是计算出写入字符串所需的字节数(判断标准是UTF-8的上面有)
        // 根据此计算出下方bytearr字节缓存区的大小
        for (int i = 0; i < strlen; i++) {
            c = str.charAt(i);
            if ((c >= 0x0001) && (c <= 0x007F)) {
                utflen++;
            } else if (c > 0x07FF) {
                utflen += 3;
            } else {
                utflen += 2;
            }
        }

        if (utflen > 65535)
            throw new UTFDataFormatException(
                "encoded string too long: " + utflen + " bytes");

        byte[] bytearr = null;
        if (out instanceof DataOutputStream) {
            DataOutputStream dos = (DataOutputStream)out;
            if(dos.bytearr == null || (dos.bytearr.length < (utflen+2)))
                dos.bytearr = new byte[(utflen*2) + 2];
            bytearr = dos.bytearr;
        } else {
            bytearr = new byte[utflen+2];
        }

        // 在写入字符串之前,写写入这个字符串的长度;所以上方的计算写入数组初始化大小的时候都加了2
        bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
        bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF);

        // 进行写入,单个字符一个个写入,
        int i=0;
        for (i=0; i<strlen; i++) {
           c = str.charAt(i);
           if (!((c >= 0x0001) && (c <= 0x007F))) break;
           bytearr[count++] = (byte) c;
        }

        // 根据UTF-8的规则,进行判断1,2,3,个字节的写入,转换为Unicode的后
        // 进行单个字节写入
        for (;i < strlen; i++){
            c = str.charAt(i);
            if ((c >= 0x0001) && (c <= 0x007F)) {
                bytearr[count++] = (byte) c;

            } else if (c > 0x07FF) {
                bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
                bytearr[count++] = (byte) (0x80 | ((c >>  6) & 0x3F));
                bytearr[count++] = (byte) (0x80 | ((c >>  0) & 0x3F));
            } else {
                bytearr[count++] = (byte) (0xC0 | ((c >>  6) & 0x1F));
                bytearr[count++] = (byte) (0x80 | ((c >>  0) & 0x3F));
            }
        }
        out.write(bytearr, 0, utflen+2);
        return utflen + 2;
    }

总结

  • 在使用时一定要注意写入和读取的顺序
  • 写入字符串的会先写入字符串的长度,以便的读取的使用

猜你喜欢

转载自blog.csdn.net/weixin_39723544/article/details/80551711