Java IO流之DataInputStream和DataOutputStream分析

简介

DataOutputStream数据输出流允许应用程序将基本Java数据类型写到基础输出流中,而DataInputStream数据输入流允许应用程序以机器无关的方式从底层输入流中读取基本的Java类型.

DataInputStream介绍

1.构造方法

public DataInputStream(InputStream in) { super(in);}
  • 有参构造方法传入的基础输入流.读取数据实际上从基础输入流in读取.

2.内部变量

private byte bytearr[] = new byte[80];
private char chararr[] = new char[80];
  • 字节数组,会将数据先读取到字节数组里面缓冲.
  • 字符数组,会将数据先读取到字符数组里面缓冲.

3.内部方法

public final int read(byte b[]) throws IOException {}
public final int read(byte b[], int off, int len) throws IOException {}
public final void readFully(byte b[]) throws IOException {}
public final void readFully(byte b[], int off, int len) throws IOException {}
public final int skipBytes(int n) throws IOException {}
public final boolean readBoolean() throws IOException {}
public final byte readByte() throws IOException {}
public final int readUnsignedByte() throws IOException {}
public final short readShort() throws IOException {}
public final int readUnsignedShort() throws IOException {}
public final char readChar() throws IOException {}
public final int readInt() throws IOException {}
public final long readLong() throws IOException {}
public final float readFloat() throws IOException {}
public final double readDouble() throws IOException {}
public final String readUTF() throws IOException {}
  • read(byte b[])---从数据输入流读取数据存储到字节数组b中.
  • read(byte b[],int off,in len)---从数据输入流中读取数据存储到数组b里面,位置从off开始,长度为len个字节.

  • readFully(byte b[])---从数据输入流中循环读取b.length个字节到数组b中.

  • readFully(byte b[],int off,in len )---从数据输入流中循环读取len个字节到字节数组b中.从b的off位置开始

  • skipBytes(int b)---跳过n个字节.

  • readBoolean()---从数据输入流读取布尔类型的值.

  • readByte()---从数据输入流中读取一个字节.

  • readUnsignedByte()---从数据输入流中读取一个无符号的字节,返回值转换成int类型.

  • readShort()---从数据输入流读取一个short类型数据.

  • readUnsignedShort()---从数据输入流读取一个无符号的short类型数据.

  • readChar()---从数据输入流中读取一个字符数据

  • readInt()---从数据输入流中读取一个int类型数据.

  • readLong()---从数据输入流中读取一个long类型的数据.

  • readFloat()---从数据输入流中读取一个float类型的数据.

  • readDouble()---从数据输入流中读取一个double类型的数据.

  • readUTF()---从数据输入流中读取用UTF-8格式编码的UniCode字符格式的字符串.

DataOutputStream介绍

1.构造方法

public DataOutputStream(OutputStream out) { super(out); }
  • 有参构造,参数传入的基础输出流,将数据实际写到基础输出流中.

2.内部变量

protected int written;
private byte[] bytearr = null;
  • written---写到数据输出流的字节数.
  • bytearr---将数据读取到字节数组中缓冲.

3.内部方法

private void incCount(int value) {}
public synchronized void write(int b) throws IOException {}
public synchronized void write(byte b[], int off, int len) throws IOException {}
public void flush() throws IOException {}
public final void writeBoolean(boolean v) throws IOException {}
public final void writeByte(int v) throws IOException {}
public final void writeShort(int v) throws IOException {}
public final void writeChar(int v) throws IOException {}
public final void writeInt(int v) throws IOException {}
public final void writeLong(long v) throws IOException {}
public final void writeFloat(float v) throws IOException {}
public final void writeDouble(double v) throws IOException {}
public final void writeBytes(String s) throws IOException {}
public final void writeChars(String s) throws IOException {}
public final void writeUTF(String str) throws IOException {}
public final int size() {}
  • intCount(int value)---数据输出流增加的字节数.
  • write(int b)---将int类型的b写到数据输出流中.
  • write(byte b[],int off, int len)---将字节数组b中off位置开始,len个长度写到数据输出流中.
  • flush()---刷新数据输出流.
  • writeBoolean()---将布尔类型的数据写到数据输出流中,底层是转化成一个字节写到基础输出流中.
  • writeByte(int v)---将一个字节写到数据输出流中(实际是基础输出流).
  • writeShort(int v)---将一个short类型的数据写到数据输出流中,底层将v转换2个字节写到基础输出流中.
  • writeChar(int v)---将一个charl类型的数据写到数据输出流中,底层是将v转换成2个字节写到基础输出流中.
  • writeInt(int v)---将一个int类型的数据写到数据输出流中,底层将4个字节写到基础输出流中.
  • writeLong(long v)---将一个long类型的数据写到数据输出流中,底层将8个字节写到基础输出流中.
  • writeFloat(flloat v)---将一个float类型的数据写到数据输出流中,底层会将float转换成int类型,写到基础输出流中.
  • writeDouble(double v)---将一个double类型的数据写到数据输出流中,底层会将double转换成long类型,写到基础输出流中.
  • writeBytes(String s)---将字符串按照字节顺序写到基础输出流中.
  • writeChars(String s)---将字符串按照字符顺序写到基础输出流中.
  • writeUTF(String str)---以机器无关的方式使用utf-8编码方式将字符串写到基础输出流中.
  • size()---写到数据输出流中的字节数.

案例

public class DataOutAndInStreamDemo {
  public static void main(String[] args) throws IOException {
    DataOutputStream dos = new DataOutputStream(new FileOutputStream("D:\\java.txt"));
    dos.writeUTF("α");
    dos.writeInt(1234567);
    dos.writeBoolean(true);
    dos.writeShort((short)123);
    dos.writeLong((long)456);
    dos.writeDouble(99.98);
    DataInputStream dis = new DataInputStream(new FileInputStream("D:\\java.txt"));
    System.out.println(dis.readUTF());
    System.out.println(dis.readInt());
    System.out.println(dis.readBoolean());
    System.out.println(dis.readShort());
    System.out.println(dis.readLong());
    System.out.println(dis.readDouble());
    dis.close();
    dos.close();
  }
}

运行结果

α
1234567
true
123
456
99.98

源码

1.DataInputStream源码分析

public class DataInputStream  extends FilterInputStream implements DataInput{
  // 有参数构造方法,传入底层的输入流
  public DataInputStream(InputStream in) {
    super(in);
  }

  // 字节数组
  private byte bytearr[] = new byte[80];
  // 字符数组
  private char chararr[] = new char[80];

  // 将数据从数据输入流中读取到字节数组b中
  public final int read(byte b[]) throws IOException {
    return in.read(b, 0, b.length);
  }

  // 将数据从数据输入流中读取到字节数组b中,b的起始位置是off,长度是len
  public final int read(byte b[], int off, int len) throws IOException {
    return in.read(b, off, len);
  }

  // 从数据输入流中读取数据,填充字节数组b.
  public final void readFully(byte b[]) throws IOException {
    readFully(b, 0, b.length);
  }

  // 将数据从数据输入流读取到字节数组b中,起始位置是off,长度len.
  // 此方法会循环从输入流读取len个字节.
  public final void readFully(byte b[], int off, int len) throws IOException {
    if (len < 0)
      throw new IndexOutOfBoundsException();
    int n = 0;
    while (n < len) {
      int count = in.read(b, off + n, len - n);
      if (count < 0)
        throw new EOFException();
      n += count;
    }
  }

  // 跳过n个字节
  public final int skipBytes(int n) throws IOException {
    int total = 0;
    int cur = 0;

    while ((total < n) && ((cur = (int) in.skip(n - total)) > 0)) {
      total += cur;
    }

    return total;
  }

  // 从数据输入流中读取布尔类型.
  public final boolean readBoolean() throws IOException {
    int ch = in.read();
    if (ch < 0)
      throw new EOFException();
    return (ch != 0);
  }

  // 从数据输入流中读取一个字节
  public final byte readByte() throws IOException {
    int ch = in.read();
    if (ch < 0)
      throw new EOFException();
    return (byte) (ch);
  }

  // 从数据输入流中读取一个无符号的字节
  public final int readUnsignedByte() throws IOException {
    int ch = in.read();
    if (ch < 0)
      throw new EOFException();
    return ch;
  }

  // 从数据输入流中读取一short类型数据
  public final short readShort() throws IOException {
    int ch1 = in.read();
    int ch2 = in.read();
    if ((ch1 | ch2) < 0)
      throw new EOFException();
    return (short) ((ch1 << 8) + (ch2 << 0));
  }

  // 从数据输入里中读取一个无符号short类型数据
  public final int readUnsignedShort() throws IOException {
    int ch1 = in.read();
    int ch2 = in.read();
    if ((ch1 | ch2) < 0)
      throw new EOFException();
    return (ch1 << 8) + (ch2 << 0);
  }

  // 从数据输入流中读取一个字符数据
  public final char readChar() throws IOException {
    int ch1 = in.read();
    int ch2 = in.read();
    if ((ch1 | ch2) < 0)
      throw new EOFException();
    return (char) ((ch1 << 8) + (ch2 << 0));
  }

  // 从数据输入流中读取一个int类型数据
  public final int readInt() throws IOException {
    int ch1 = in.read();
    int ch2 = in.read();
    int ch3 = in.read();
    int ch4 = in.read();
    if ((ch1 | ch2 | ch3 | ch4) < 0)
      throw new EOFException();
    return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
  }

  private byte readBuffer[] = new byte[8];

  // 从数据输入流中读取一个long类型的数据
  public final long readLong() throws IOException {
    readFully(readBuffer, 0, 8);
    return (((long) readBuffer[0] << 56) + ((long) (readBuffer[1] & 255) << 48) + ((long) (readBuffer[2] & 255) << 40)
        + ((long) (readBuffer[3] & 255) << 32) + ((long) (readBuffer[4] & 255) << 24) + ((readBuffer[5] & 255) << 16)
        + ((readBuffer[6] & 255) << 8) + ((readBuffer[7] & 255) << 0));
  }

  // 从数据输入流中读取一个float类型的数据
  public final float readFloat() throws IOException {
    return Float.intBitsToFloat(readInt());
  }

  // 从数据输入流中读取一个double类型的数据
  public final double readDouble() throws IOException {
    return Double.longBitsToDouble(readLong());
  }

  private char lineBuffer[];

  // 从数据输入流中读取utf编码的数据
  public final String readUTF() throws IOException {
    return readUTF(this);
  }

  public final static String readUTF(DataInput in) throws IOException {
    int utflen = in.readUnsignedShort();
    byte[] bytearr = null;
    char[] chararr = null;
    // 先要判断in是否数据输入流类型
    if (in instanceof DataInputStream) {
      DataInputStream dis = (DataInputStream) in;
      if (dis.bytearr.length < utflen) {
        dis.bytearr = new byte[utflen * 2];
        dis.chararr = new char[utflen * 2];
      }
      chararr = dis.chararr;
      bytearr = dis.bytearr;
    } else {
      bytearr = new byte[utflen];
      chararr = new char[utflen];
    }

    int c, char2, char3;
    int count = 0;
    int chararr_count = 0;
    // 从输入流中读取字节到bytearr数组中.长度是utflen
    in.readFully(bytearr, 0, utflen);
    // 根据字节范围不会超过127,超过的情况下,说明字节数组里面存储超过了一个字节.
    while (count < utflen) {
      c = (int) bytearr[count] & 0xff;
      if (c > 127)
        break;
      count++;
      // 一个字节的情况,将c转换成字符存储到字符数组中.
      chararr[chararr_count++] = (char) c;
    }
    // 数据输出流使用utf-8编码写出数据时,会进行编码.
    // 需要根据utf-8编码规则,判断对应每个"字符"c占用几个字节.
    // 1个字节的编码格式:0xxxxxxx
    // 2个字节的编码格式:110x xxxxx 10xx xxxx
    // 3个字节的编码格式:1110 xxxx 10xx xxxx 10xx xxxx
    // 4个字节的编码格式:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    while (count < utflen) {
      c = (int) bytearr[count] & 0xff;
      // 需要先判断utf有几个字节.
      // 将字节数组的总第一个字节右移4位,0000xxxx.那么xxxx,最大值就是1111,10进制是15
      // 最小是0000,十进制就是0.根据字节数组里面的第一个字节的前四位可以看出"UTF"占用了几个字节
      switch (c >> 4) {
        case 0:
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
        case 7:
          /* 0xxxxxxx */
          // utf编码是一个字节,0xxxxxxx右移4位,00000xxx,
          // 最小值是0,最大值是111,对应十进制范围就是0-7
          count++;
          chararr[chararr_count++] = (char) c;
          break;
        case 12:
        case 13:
          /* 110x xxxx 10xx xxxx */
          // utf编码是两个字节的情况,将首字节110xxxxx右移4位,是110x,
          // 最小值是1100,最大值是1100,对应十进制是12,13
          count += 2;
          if (count > utflen)
            throw new UTFDataFormatException("malformed input: partial character at end");
          char2 = (int) bytearr[count - 1];
          if ((char2 & 0xC0) != 0x80)
            throw new UTFDataFormatException("malformed input around byte " + count);
          chararr[chararr_count++] = (char) (((c & 0x1F) << 6) | (char2 & 0x3F));
          break;
        case 14:
          /* 1110 xxxx 10xx xxxx 10xx xxxx */
          // utf编码是三个字节的情况下,将首字节1110xxxx右移4位,是1110
          // 十进制是14
          count += 3;
          if (count > utflen)
            throw new UTFDataFormatException("malformed input: partial character at end");
          char2 = (int) bytearr[count - 2];
          char3 = (int) bytearr[count - 1];
          if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
            throw new UTFDataFormatException("malformed input around byte " + (count - 1));
          chararr[chararr_count++] = (char) (((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0));
          break;
        default:
          // 此外四个字节或者首字节是10开头的情况,不做转化
          /* 10xx xxxx, 1111 xxxx */
          throw new UTFDataFormatException("malformed input around byte " + count);
      }
    }
    // The number of chars produced may be less than utflen
    return new String(chararr, 0, chararr_count);
  }
}

2.DataOutputStream源码分析

public class DataOutputStream {
  // 写到数据输出流中字节数
  protected int written;
  // 数据输出流中的字节数组
  private byte[] bytearr = null;

  // 构造方法.参数传入的是基础输出流
  public DataOutputStream(OutputStream out) {
    super(out);
  }

  // 数据输出流的字节数增加,value是增加的字节数
  private void incCount(int value) {
    int temp = written + value;
    if (temp < 0) {
      temp = Integer.MAX_VALUE;
    }
    written = temp;
  }

  // 将int类型数据b写到数据输出流中(实际是基础输出流)
  public synchronized void write(int b) throws IOException {
    out.write(b);
    incCount(1);
  }

  // 将字节数组b,off索引位置开始,len个长度写到数据输出流中(实际是基础输出流).
  public synchronized void write(byte b[], int off, int len) throws IOException {
    out.write(b, off, len);
    incCount(len);
  }

  // 刷新数据输出流
  public void flush() throws IOException {
    out.flush();
  }

  // 将boolean类型的数据写到数据输出流中(实际是基础输出流).
  public final void writeBoolean(boolean v) throws IOException {
    out.write(v ? 1 : 0);
    incCount(1);
  }

  // 将字节数据写到数据输出流中(实际是基础输出流).
  public final void writeByte(int v) throws IOException {
    out.write(v);
    incCount(1);
  }

  // 将short类型数据写到数据输出流中(实际是基础输出流).
  // short占两个字节,将高8位和低8位分别存储
  public final void writeShort(int v) throws IOException {
    out.write((v >>> 8) & 0xFF);
    out.write((v >>> 0) & 0xFF);
    incCount(2);
  }

  // 将字符类型的数据写到数据输出流中(实际是基础输出流).
  // char占两个字节,将高8位和低8位分别存储
  public final void writeChar(int v) throws IOException {
    out.write((v >>> 8) & 0xFF);
    out.write((v >>> 0) & 0xFF);
    incCount(2);
  }

  // 将int类型的数据写到数据输出流中((实际是基础输出流))
  // int占4个字节.所以将4个字节分别存储
  public final void writeInt(int v) throws IOException {
    out.write((v >>> 24) & 0xFF);
    out.write((v >>> 16) & 0xFF);
    out.write((v >>> 8) & 0xFF);
    out.write((v >>> 0) & 0xFF);
    incCount(4);
  }

  private byte writeBuffer[] = new byte[8];

  // 将long类型数据写到数据输出流(实际是基础输出流).
  // long是8个字节,将8个字节分别存储.
  public final void writeLong(long v) throws IOException {
    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);
  }

  // 将float类型的数据写到数据输出流中(实际是基础输出流).
  public final void writeFloat(float v) throws IOException {
    writeInt(Float.floatToIntBits(v));
  }

  // 将double类型的数据写到数据输出流中(实际是基础输出流).
  public final void writeDouble(double v) throws IOException {
    writeLong(Double.doubleToLongBits(v));
  }

  // 将字符串转换成字节写到数据输出流中(实际是基础输流).
  public final void writeBytes(String s) throws IOException {
    int len = s.length();
    for (int i = 0; i < len; i++) {
      out.write((byte) s.charAt(i));
    }
    incCount(len);
  }

  // 将字节数据转换成字符写到数据输出流中(实际是基础输出流).
  public final void writeChars(String s) throws IOException {
    int len = s.length();
    for (int i = 0; i < len; i++) {
      int v = s.charAt(i);
      out.write((v >>> 8) & 0xFF);
      out.write((v >>> 0) & 0xFF);
    }
    incCount(len * 2);
  }

  // 将字符串以编码方式UTF方式写到数据输出流中(实际是基础输出流).
  public final void writeUTF(String str) throws IOException {
    writeUTF(str, this);
  }

  // 将字符串以UTF编码写到数据输出流中.
  //在utf-8编码里面一个字符占用1-6个字节,但是5,6字节经规范不会出现,一般情况下是1-3个字节
  //单字节可编码的Unicode范围:\u0000~\u007F(0~127)
  //双字节可编码的Unicode范围:\u0080~\u07FF(128~2047)
  //三字节可编码的Unicode范围:\u0800~\uFFFF(2048~65535)
  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 */
    //计算存储字符串的字节的长度
    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];
    }
    //数组前两位用于存储字节的长度
    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编码字节的存储
    for (; i < strlen; i++) {
      c = str.charAt(i);
      //一个的字节的情况
      if ((c >= 0x0001) && (c <= 0x007F)) {
        bytearr[count++] = (byte) c;
       //三个字节的情况
       //utf三个字节编码方式:1110xxxx 10xxxxxx 10xxxxxx
        //通过位运算和0xE0(11100000),0X80(10000000)取得utf-8三个字节编码的其余位置
      } 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 {
        //两个字节的情况
        //utf-8两个字节的编码方式:110xxxxxx 10xxxxxx
        //0xC0(11000000),0x80(10000000)和位运算取得utf-8两个字节的编码的其余11位
        bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
        bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
      }
    }
    out.write(bytearr, 0, utflen + 2);
    return utflen + 2;
  }

  // 写到数据输出流的总字节数
  public final int size() {
    return written;
  }
}

注意点:

1.数据输入流和输出流以机器无关的方式进行写入和读取数据,实际上数据输出流是将java的基本类型,底层存储的时候以字节形式进行存储.而数据输入流读取后数据后,会将字节的进行拼接.得到java基本类型.

例如:

public final void writeShort(int v) throws IOException {
        out.write((v >>> 8) & 0xFF);
        out.write((v >>> 0) & 0xFF);

    }

 public final short readShort() throws IOException {
        int ch1 = in.read();
        int ch2 = in.read();
        if ((ch1 | ch2) < 0)
            throw new EOFException();
        return (short)((ch1 << 8) + (ch2 << 0));
    }

当数据输出流使用write写出一个short类型的数据,由于short类型占2字节,即16位,分别移动8,0位,然后和0xFF(二进制是1111 1111)进行逻辑"&"运算分别得到2个8位.进行&0xFF操作,不会改变原先的数值(例如1100 0011&0xFF还是得到原先的11000011),比如(v>>>8)&0xFF是为了取得v的高8位.而(v>>>0)&0xFF是为了取得低8位.然后两个字节分别存储.

2.关于方法writeUTF(String str, DataOutput out)和readUTF().

在分析writeUTF()源码之前,需要了解Unicode编码与UTF-8编码方式的转换.UTF-8编码方式是一种变长的编码方式.可以使用1-4个字节表示一个符号,根据不同的符号而变化字节的长度.可以看到UTF-8编码的方式规则的编码里面,一个字节开头是0,剩余7位,所以Unicode编码是00000000-0000007F,原因是7F的二进制是1111111,是7位二进制里面最大值,超过此值,就不在UTF-8编码的一个字节范围内.

所以Unicode编码里面临界值范围的来源,是UTF-8编码方式已经含有编码标识的0,110,1110,10等,UTF-8编码的1~4个字节剩余的有效位数,1个字节是7位,2个字节是11位,3个字节是16位,4个字节是21位.

字节数  Unicode编码范围(十六进制)UTF-8的编码方式
1个字节    0000 0000-0000 007F0xxxxxxx
2个字节   0000 0080-0000 07FF110xxxxx 10xxxxxx

3个字节

   0000 0800-0000 FFFF1110xxxx 10xxxxxx 10xxxxxx
4个字节   0001 0000-0010 FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

再看writeUTF()的源码比较清楚了.

1.先计算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;
      }
    }
  bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
  bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF);

从此段代码来看,不处理4个字节的情况.会将所有UTF-8数据的总长度存储在bytearr的字节数组的前两个位置.

2.判断UTF-8数据是几个字节,然后分别存储在字节数组bytearr里面.

bytearr[count++] = (byte) (0xC0 | ((c >>  6) & 0x1F));
bytearr[count++] = (byte) (0x80 | ((c >>  0) & 0x3F));

例如两个字节的情况.根据UTF-8编码规则,两个字节编码方式是110xxxxx 10xxxxxx除了本身110和10,剩余有效位数是11位,通过(c>>6)&0x1F(二进制是11111)获取前5位,然后按照UTF-8编码原则.编码占两个字节的情况,第一个字节编码方式是110x xxxx,那么通过0xC0(二进制是1100 0000)和((c>>6)&0x1F)逻辑"&"操作得到UTF编码的第一个字节.同理第二个字节也是一样的运算.

同样数据输出流的方法readUTF()读取对应的UTF数据.

1.由于存储UTF-8数据的时候,前两个字节存储的是UTF数据的长度,所以读取前两个字节.根据前面的长度,读取对应长度字节.

int utflen = in.readUnsignedShort();
in.readFully(bytearr, 0, utflen)

2.UTF-8占用一个字节的时,UTF-8编码规则,单字节以0开头,剩余7位(二进制最大值是1111111,十进制是127),所以单字节最大不会超过127

 while (count < utflen) {
      c = (int) bytearr[count] & 0xff;
      if (c > 127) break;
      count++;
      chararr[chararr_count++]=(char)c;
}

3.&0xff(二进制是11111111)操作先取得首字节.然后将c右移动4位,根据首字节的前四位可以得到对应UTF-8占用几个字节.

字节数        UTF-8编码 c = (int) bytearr[count] & 0xff  c>>4
一个字节   0000 0000-0000 007F000xxxxx(1-7)
两个字节   0000 0080-0000 07FF0000110x(12,13)
三个字节   0000 0800-0000 FFFF00001110(14)
四个字节   0001 0000-0010 FFFF00001111(15)
c = (int) bytearr[count] & 0xff;
switch (c >> 4) {
                   ...}

4.对应的writeUTF()中对于Unicode编码转换成UTF-8,需要将UTF-8编码转换成Unicode编码.例如UTF-8两个字节的情况.

即UTF-8编码方式的是110xxxxx 10xxxxxx ,由于UTF-8编码占用两个字节情况,除了本身110,10,还剩11位.所以通过下面的操作得到对应的11位.

chararr[chararr_count++]=(char)(((c & 0x1F) << 6) |(char2 & 0x3F));

二进制有效位数是11位,所以要通过运算反推原先的11位,c&0x1F(二进制是11111)得到UTF编码占用两个字节中第一个字节中后面5位(UTF编码占用两个字节的第一个字节编码是110xxxxx,也就是去掉110,拿到xxxxx),然后进行位运算<<6得到xxxxx000000,char2是第二个字节和0x3F(二进制111111)逻辑"&"运算得到后6位,最终进行逻辑"|"得到有效的11位.

总结:关于writeUTF()和readUTF()方法,前者就是Unicode编码转换成UTF-8编码,readUTF()方法将对应的UTF-8编码转换,获取其中有效的位数,转换成Unicode编码.

  • 25
    点赞
  • 202
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
`DataOutputStream` 是 Java 中一种基于字节的输出,它继承自 `FilterOutputStream` 类,实现了 `DataOutput` 接口。与 `OutputStream` 不同的是,`DataOutputStream` 可以直接将 Java 中的基本数据类型(如 `int`、`double` 等)以二进制形式写入到输出中,这使得数据的传输和存储更加方便。 `DataOutputStream` 中最常用的方法是 `writeXXX` 系列方法,其中 `XXX` 表示不同的数据类型。例如,`writeInt(int v)` 方法用于将一个 `int` 类型的数据以二进制形式写入到输出中。除此之外,`DataOutputStream` 还实现了 `DataOutput` 接口中定义的一些方法,如 `write(byte[] b)`、`writeBytes(String s)` 等,这些方法可以将字节数组、字符串等类型的数据写入到输出中。 以下是一个使用 `DataOutputStream` 的示例: ```java OutputStream outputStream = new FileOutputStream("output.bin"); DataOutputStream dataOutputStream = new DataOutputStream(outputStream); dataOutputStream.writeInt(42); // 将整数 42 写入输出 dataOutputStream.writeDouble(3.14); // 将浮点数 3.14 写入输出 dataOutputStream.close(); ``` 在上面的示例中,我们首先创建了一个 `FileOutputStream` 对象,将数据输出到文件 "output.bin" 中。接着,我们通过 `DataOutputStream` 对象的 `writeInt` 和 `writeDouble` 方法,将整数和浮点数数据以二进制形式写入到输出中。最后,我们关闭了 `DataOutputStream` 对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值