Java8 I/O源码-DataInputStream与DataOutputStream

前面已经学习了FilterInputStream与FilterOutputStream。文章中到了FilterInputStream与FilterOutputStream的子类可进一步重写父类方法中的一些方法,来提供装饰功能。今天就来介绍下它们子类中的DataInputStream与DataOutputStream。

DataInputStream为数据输入流,它允许应用程序以与机器无关方式从底层输入流中读取基本Java数据类型。
DataOutputStream为数据输出流,它允许应用程序以适当方式将基本 Java数据类型写入输出流中。

阅读源码需要复习一些基础知识,比如基本数据类型、位移运算、&xFF操作。

先来复习下基本数据类型各有多少位。

基本数据类型byteshortintlongfloatdoublebooleanchar
81632643264116


再来复习下位移运算符中的<<和>>>

<<表示左移运算符,是将运算符左边的对象,向左移动运算符右边指定的位数,并且在低位补零。其实,向左移n 位,就相当于乘上2 的n 次方。

如,20 < 2;20的二进制为 0001 0100,右移2位后为 0010 1000,则结果就为 40;

>>>表示无符号右移,也叫逻辑右移。即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0。

如,20 >>> 2;20的二进制为 0001 0100,右移2位后为 0000 0101,则结果就为 5;
而-20 >> 2;-20的二进制为 1110 1011,右移2位,此时高位补0,即 0011 1010,结果为58;

最后复习下&xFF操作代表什么含义。

取低8位。0xFF是二进制的 1111 1111,那么0000 1010 0101 0101&1111 1111就能取到0000 1010 0101 0101的低8位。

DataInputStream

下面来看下DataInputStream的源码。

/**
 * DataInput接口用于从二进制流中读取字节,并根据所有Java基本类型数据进行重构。
 * 同时还提供根据UTF-8修改版格式的数据重构String的工具。
 */
public class DataInputStream extends FilterInputStream implements DataInput {

    /**
     * 构造方法之一
     * 使用特定的输出流创建DataInputStream
     */
    public DataInputStream(InputStream in) {
        super(in);
    }

    /**
     * readUTF()使用的数组
     */
    private byte bytearr[] = new byte[80];
    private char chararr[] = new char[80];

    /**
     * 从输入流中读取b.length个数据保存到byte数组b中。
     * 实际读取的字节数以整数的形式返回。
     * 在输入数据可用、检测到文件末尾、或抛出异常之前,该方法一直阻塞。
     * 如果b为null,会抛出NullPointerException。
     * 如果b长度为0,不会读取数据,方法返回值为0;否则会尝试读取至少一个字节。
     * 如果因为流在文件末尾导致没有字节可用,将返回-1;否则至少会读取一个字节并保存到b中。
     *
     * 第一个字节保存到b[0],下一个字节保存到b[1],以此类推。
     * 读取的字节数最大等于b的长度。
     *
     * 此方法等价于read(b, 0, b.length)
     *
     * @param      b   存储读取数据的byte数组
     * @return     实际读取的字节数, 如果因为到达流末尾导致没有数据,返回-1。
     * @exception  IOException 如果不是因为流位于文件末尾而无法读取第一个字节;该流已关闭并且底层输入流在关闭后不支持读取操作;发生其他 I/O错误。
     */
    public final int read(byte b[]) throws IOException {
        return in.read(b, 0, b.length);
    }

    /**
     * 从包含的输入流中将最多len个字节读入一个byte数组中。
     * 尽量读取len个字节,但读取的字节数可能少于len个,也可能为零。
     * 以整数形式返回实际读取的字节数。
     * 在输入数据可用、检测到文件末尾或抛出异常之前,此方法将阻塞。
     * 
     * 如果len为零,则不读取任何字节并返回0;
     * 否则,尝试读取至少一个字节。如果因为流位于文件未尾而没有字节可用,则返回值-1;
     * 否则,至少读取一个字节并将其存储到b中。
     *
     * @param      b     存储读取数据的缓冲区。
     * @param off  目标数组 b 中的起始偏移量
     * @param      len   读取的最大字节数。
     * @return     读入缓冲区的字节总数;如果因为已经到达流末尾而没有更多的数据,则返回-1。
     * @exception  NullPointerException 如果b为null。
     * @exception  IndexOutOfBoundsException  如果off为负, len为负,或者len大于b.length-off
     * @exception  IOException 如果不是因为流位于文件末尾而无法读取第一个字节;该流已关闭并且底层输入流在关闭后不支持读取操作;发生其他I/O 
     */
    public final int read(byte b[], int off, int len) throws IOException {
        return in.read(b, off, len);
    }

    /**
     * 参考DataInput.readFully()的介绍。
     * 
     * 以下是DataInput.readFully()的介绍。
     * 从输入流中读取一些字节,并将它们存储在缓冲区数组b中。读取的字节数等于b的长度。
     * 
     * 该方法的执行结果有三种:
     * 1.输入数据的 len 个字节是可用的,在这种情况下,正常返回。
     * 2.检测到文件末尾,在这种情况下,抛出 EOFException。
     * 3.如果发生 I/O 错误,在这种情况下,将抛出 IOException,而不是 EOFException。
     * 
     * 如果b为null,则抛出NullPointerException。
     * 如果off为负,或len为负,或者off+len大于数组b的长度,则抛出IndexOutOfBoundsException。
     * 如果len为零,则不读取字节。
     * 否则,将读取的第一个字节存储到元素b[off]中,下一个字节存储到 b[off+1]中,
     * 依此类推。读取的字节数至多等于b[0]。
     * 
     * @param      b  存储读取数据的缓冲区。
     * @exception  EOFException  如果此输入流在读取所有字节之前到达末尾。
     * @exception  IOException   该流已关闭并且包含的输入流在关闭后不支持读取操作,或者发生其他 I/O 错误。
     */
    public final void readFully(byte b[]) throws IOException {
        readFully(b, 0, b.length);
    }

    /**
     * 参考readFully(byte b[])
     *
     * @param      b     存储读取数据的缓冲区。
     * @param      off   指定数据中的偏移量
     * @param      len   指定读取的字节数
     * @exception  EOFException  如果此流在读取所有字节之前到达末尾。
     * @exception  IOException 该流已关闭并且包含的输入流在关闭后不支持读取操作,或者发生其他I/O错误。
     */
    public final void readFully(byte b[], int off, int len) throws IOException {
        //如果参数不合法,抛出异常
        if (len < 0)
            throw new IndexOutOfBoundsException();
        int n = 0;
        //读取字节,直到读取了len个字节
        while (n < len) {
            int count = in.read(b, off + n, len - n);
            //如果此流在读取所有字节之前到达末尾,抛出异常
            if (count < 0)
                throw new EOFException();
            n += count;
        }
    }

    /**
     * 参考DataInput.skipBytes()的介绍。
     * 
     * 以下是DataInput.skipBytes()的介绍:
     * 试图在输入流中跳过数据的n个字节,并丢弃跳过的字节。
     * 不过,可以跳过更少的字节数,该字节数甚至可以为零。
     * 这可能由很多情况引起;在已经跳过n个字节前到达文件末尾只是其中的一种可能。此方法从不抛出EOFException。返回实际跳过的字节数。
     *
     * @param      n  要跳过的字节数
     * @return     实际跳过的字节数。
     * @exception  IOException 如果包含的输入流不支持查找操作;该流已关闭并且包含的输入流在关闭后不支持读取操作;发生其他I/O错误。
     */
    public final int skipBytes(int n) throws IOException {
        //实际跳过的字节数
        int total = 0;
        //每次跳过的字节数
        int cur = 0;

        //如果还没有跳过n个字节,且还没有到达文件末尾,就继续向下跳
        while ((total<n) && ((cur = (int) in.skip(n-total)) > 0)) {
            //累加每次跳过的字节数
            total += cur;
        }

        //返回实际跳过的字节数
        return total;
    }

    /**
     * 参考DataInput.readBoolean()
     * 
     * 以下是DataInput.readBoolean()的介绍:
     * 
     * 从输入流中读取一个输入字节,如果该字节不是零,则返回true;
     * 如果是零,则返回false。此方法适用于读取用接口DataOutput的 writeBoolean方法写入的字节。
     *
     * @return     如果读取的字节不是零,则返回true;如果是零,则返回false
     * @exception  EOFException 如果此输入流已经到达末尾。
     * @exception  IOException  该流已关闭并且包含的输入流在关闭后不支持读取操作,或者发生另其他I/O错误。
     */
    public final boolean readBoolean() throws IOException {
        //从输入流中读取一个字节
        int ch = in.read();
        //如果达到输入流末尾,抛出异常
        if (ch < 0)
            throw new EOFException();
        //如果读取的字节不是零,则返回true;如果是零,则返回false
        return (ch != 0);
    }

    /**
     * 参考DataInput.readByte方法的介绍。
     * 
     * 以下是DataInput.readByte方法的介绍:
     * 
     * 从输入流中读取并返回一个输入字节。
     */
    public final byte readByte() throws IOException {
        int ch = in.read();
        if (ch < 0)
            throw new EOFException();
        return (byte)(ch);
    }

    /**
     * 参考DataInput.readUnsignedByte方法的介绍
     * 
     * 下面是DataInput.readUnsignedByte方法的介绍:
     * 
     * 读取一个输入字节,将它左侧补零转变为int类型,并返回结果,所以结果的范围是0到255
     */
    public final int readUnsignedByte() throws IOException {
        int ch = in.read();
        if (ch < 0)
            throw new EOFException();
        return ch;
    }

    /**
     * 读取两个输入字节并返回一个short值。设ch1为第一个读取字节,ch2为第二个读取字节。
     * 返回的值是:(short)((ch1 << 8) + (ch2 << 0))
     * 
     * 如ch1为0010 0010,ch2为0001 1001,那么ch1<<8为0010 0010 0000 0000,ch2<<0不变,这样(ch1 << 8) + (ch2 << 0)就拼接成了一个完成的值。
     *
     * @return 读取此输入流的两个字节,将它们解释为一个有符号16位数
     */
    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));
    }

    /**
     * 参考readShort方法。
     * 问:readUnsignedShort方法与readShort方法代码完全相同,如何保证readUnsignedShort是无符号的?
     * 
     * @return     此输入流的下两个字节,将它们解释为一个无符号16位整数。
     */
    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);
    }

    /**
     * 参考readShort方法。
     */
    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));
    }

    /**
     * 参考readShort方法。
     */
    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];

    /**
     * 参考readShort方法。
     */
    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));
    }

    /**
     * 参考DataInput.readFloat方法的介绍
     * 
     * 下面是对DataInput.readFloat方法的介绍
     * 读取四个输入字节并返回一个float值。
     * 实现这一点的方法是:先使用与readInt方法完全相同的方式构造一个int值,然后使用与Float.intBitsToFloat方法完全相同的方式将此int值转换成一个float值。
     */
    public final float readFloat() throws IOException {
        return Float.intBitsToFloat(readInt());
    }

    /**
     * 参考对DataInput.readDouble方法的介绍
     * 
     * 下面是对DataInput.readDouble方法的介绍
     * 读取八个输入字节并返回一个double值。
     * 实现这一点的方法是:先使用与readlong方法完全相同的方式构造一个long值,
     * 然后使用与Double.longBitsToDouble方法完全相同的方式将此long值转换成一个double值。
     */
    public final double readDouble() throws IOException {
        return Double.longBitsToDouble(readLong());
    }

    private char lineBuffer[];

    /**
     * 参考对DataInput.readLine方法的介绍
     * 
     * 下面是对DataInput.readLine方法的介绍
     * 从输入流中读取下一文本行。
     * 该方法读取连续的字节,将每个字节分别转换成一个字符,直到遇到行结尾符或到达末尾;然后以 String 形式返回读取的字符。
     */
    @Deprecated
    public final String readLine() throws IOException {
        //该方法已不建议使用,省略
    }

    /**
     * 参考对DataInput.readUTF方法的介绍。
     * 
     * 下面是对DataInput.readUTF方法的介绍:
     * 读入一个已使用UTF-8修改版格式编码的字符串。
     * readUTF的常规协定是:该方法读取使用UTF-8修改版格式编码的 Unicode字符串的表示形式;
     * 然后以String的形式返回此字符串。
     *
     * @return     一个Unicode字符串
     * @exception  EOFException 如果此输入流在读取所有字节之前到达末尾。
     * @exception  IOException 该流已关闭并且包含的输入流在关闭后不支持读取操作,或者发生其他I/O错误。
     * @exception  UTFDataFormatException 如果这些字节不表示一个有效的、UTF-8修改版编码的Unicode字符串。
     */
    public final String readUTF() throws IOException {
        return readUTF(this);
    }

    /**
     * 从输入流in中读取用UTF-8修改版格式编码的Unicode字符格式的字符串;然后以String形式返回此字符串。
     * 
     * @param in 数据输入流
     */
    public final static String readUTF(DataInput in) throws IOException {
    //待补充
}
思考
  1. readUnsignedShort方法与readShort方法代码完全相同,如何保证readUnsignedShort是无符号的?

DataOutputStream

/**
 * DataOutput接口用于将数据从任意Java基本类型转换为一系列字节,并将这些字节写入二进制流。同时还提供了一个将String转换成UTF-8修改版格式并写入所得到的系列字节的工具。
 */
public class DataOutputStream extends FilterOutputStream implements DataOutput {
    /**
     * 到目前为止写入到输出流中的字节数
     * 最大值为Integer.MAX_VALUE
     */
    protected int written;

    /**
     * writeUTF方法使用的字节数组
     */
    private byte[] bytearr = null;

    /**
     * 创建一个新的数据输出流
     * written初始值为0
     */
    public DataOutputStream(OutputStream out) {
        super(out);
    }

    /**
     * 增加wirtten的值。最大值为Integer.MAX_VALUE
     */
    private void incCount(int value) {
        int temp = written + value;
        //int允许的最大值为Integer.MAX_VALUE,即2147483647,2147483647+1即为负数
        if (temp < 0) {
            temp = Integer.MAX_VALUE;
        }
        written = temp;
    }

    /**
     * 将指定字节(参数b的八个低位)写入基础输出流。
     */
    public synchronized void write(int b) throws IOException {
        out.write(b);
        //如果没有抛出异常,则计数器written增加 1。
        incCount(1);
    }

    /**
     * 将指定byte数组中从偏移量off开始的len个字节写入基础输出流。
     * 如果没有抛出异常,则计数器written增加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值以1-byte值形式写入基础输出流。
     * 值true以值(byte)1的形式被写出;值false以值(byte)0的形式被写出。
     * 如果没有抛出异常,则计数器written增加 1。
     */
    public final void writeBoolean(boolean v) throws IOException {
        out.write(v ? 1 : 0);
        incCount(1);
    }

    /**
     * 将一个byte值以1-byte值形式写出到基础输出流中。
     * 如果没有抛出异常,则计数器written增加1。
     */
    public final void writeByte(int v) throws IOException {
        out.write(v);
        incCount(1);
    }

    /**
     * 将一个short值以2-byte值形式写入基础输出流中,先写入高字节。
     * 如果没有抛出异常,则计数器written增加2(注:因为short是16位的,占两个字节,所以要加2)。
     * 
     * v >>> 8,如v为0010 0011 0000 1111,那么右移8位后为0010 0011
     * 再& 0xFF,结果还是0010 0011,这样就取到了v的高8位。
     * 
     * v >>> 0,结果是0010 0011 0000 1111,再& 0xFF,结果是0000 1111,这样就取到了低8位。
     * 
     * 现在的问题是,传入的参数v是二进制的吗?
     */
    public final void writeShort(int v) throws IOException {
        out.write((v >>> 8) & 0xFF);
        out.write((v >>> 0) & 0xFF);
        incCount(2);
    }

    /**
     * 将一个char值以2-byte值形式写入基础输出流中,先写入高字节。
     * 如果没有抛出异常,则计数器written增加2。(注:注:因为char是16位的)
     * 
     * 参考writeShort()中关于(v >>> 8) & 0xFF的介绍。
     */
    public final void writeChar(int v) throws IOException {
        out.write((v >>> 8) & 0xFF);
        out.write((v >>> 0) & 0xFF);
        incCount(2);
    }

    /**
     * 将一个int值以4-byte值形式写入基础输出流中,先写入高字节。
     * 如果没有抛出异常,则计数器written增加4。(注:int为32位的)
     * 
     * 参考writeShort()中关于(v >>> 8) & 0xFF的介绍。
     */
    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值以8-byte值形式写入基础输出流中,先写入高字节。
     * 如果没有抛出异常,则计数器written增加8。(注:long是64位的)
     * 
     * 参考writeShort()中关于(v >>> 8) & 0xFF的介绍。
     */
    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类中的floatToIntBits方法将float参数转换为一个int值,
     * 然后将该int值以4-byte值形式写入基础输出流中,先写入高字节。
     * 如果没有抛出异常,则计数器written增加4。(注:float是32位的)
     */
    public final void writeFloat(float v) throws IOException {
        writeInt(Float.floatToIntBits(v));
    }

    /**
     * 使用Double类中的doubleToLongBits方法将double参数转换为一个long值,
     * 然后将该long值以8-byte值形式写入基础输出流中,先写入高字节。
     * 如果没有抛出异常,则计数器written增加8。(注:double是64位的)
     */
    public final void writeDouble(double v) throws IOException {
        writeLong(Double.doubleToLongBits(v));
    }

    /**
     * 将字符串按字节顺序写出到基础输出流中。
     * 按顺序写出字符串中每个字符,丢弃其八个高位。
     * 如果没有抛出异常,则计数器written增加s的长度。
     * 
     * ???不懂为什么这样做。
     */
    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);
    }

    /**
     * 将字符串按字符顺序写入基础输出流。
     * 通过writeChar方法将每个字符写入数据输出流。
     * 如果没有抛出异常,则计数器written增加s长度的两倍。(注:字节数/2=字符串长度=字符个数)
     * 
     * 参考writeShort()中关于(v >>> 8) & 0xFF的介绍。
     */
    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-8修改版编码将一个字符串写入基础输出流。
     */
    public final void writeUTF(String str) throws IOException {
        writeUTF(str, this);
    }

    /**
     * 待补充
     */
    static int writeUTF(String str, DataOutput out) throws IOException {
        //待补充
    }

    /**
     * 返回written的当前值,即到目前为止写入此数据输出流的字节数。最大值为Integer.MAX_VALUE。
     */
    public final int size() {
        return written;
    }
}
思考
  1. writeBytes的实现为什么是那样?

demo

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * DataInputStream 和 DataOutputStream测试类
 */
public class DataInputStreamTest {

    public static void main(String[] args) {
        testDataOutputStream();
        testDataInputStream();
    }

    /**
     * DataOutputStream的API测试函数
     */
    private static void testDataOutputStream() {

        try {
            File file = new File("dataOutputStream.txt");
            DataOutputStream out = new DataOutputStream(new FileOutputStream(file));

            out.writeBoolean(true);
            out.writeByte(122);
            out.writeChar('b');
            out.writeShort(123);
            out.writeInt(1111);
            out.writeLong(1233442L);

            out.writeUTF("DataOutputStream");

            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * DataInputStream的API测试函数
     */
    private static void testDataInputStream() {

        try {
            File file = new File("dataOutputStream.txt");
            DataInputStream in = new DataInputStream(new FileInputStream(file));

            System.out.printf("readBoolean():%s\n", in.readBoolean());
            System.out.printf("readByte():%s\n", in.readByte());
            System.out.printf("readChar():%s\n", in.readChar());
            System.out.printf("readShort():%s\n", in.readShort());
            System.out.printf("readInt():%s\n", in.readInt());
            System.out.printf("readLong():%s\n", in.readLong());
            System.out.printf("readUTF():%s\n", in.readUTF());

            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

执行main方法后,控制台会打印出

readBoolean():true
readByte():122
readChar():b
readShort():123
readInt():1111
readLong():1233442
readUTF():DataOutputStream

总结

  • DataInputStream提供了一系列从二进制流中读取字节,并根据所有Java基本类型数据进行重构的readXXXX方法。同时还提供根据UTF-8修改版格式的数据重构String的工具,即readUTF方法。
  • DataOutputStream提供了一系列将数据从任意Java基本类型转换为一系列字节,并将这些字节写入二进制流的writeXXXX方法。同时还提供了一个将String转换成UTF-8修改版格式并写入所得到的系列字节的工具,即writeUTF方法。

关于DataInputStream与DataOutputStream就讲到这里,想了解更多内容请参考

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值