java IO笔记(DataInputStream/DataOutputStream)

JavaAPI 高级特性 DataInputStream二进制输入流 FileInputStream fis = null; DataInputStream dis = null; FileOutputStream fos = null; DataOutputStream dos = null; try { fis = new FileInputStream("F:\\IdeaProjects\\Java-Api\\file\\old.jpg"); dis = . 阅读详情

本篇讲述的是java io包中的DataInputStream和DataOutputStream。

这两个类都属于java io中的包装类,为基础流提供了丰富的读写方法,可以将java中的数据类型轻松地写入流中。

下面还是先贴出源码进行简单的分析:
DataInputStream.java:

package java.io;

public class DataInputStream extends FilterInputStream implements DataInput {

    /**
     * 一个带一个参数的构造方法,传入的参数为一个InputStream对象。内部本质是调用父类FilterInputStream的构造方法。
     */
    public DataInputStream(InputStream in) {
        super(in);
    }

    //定义了两个数组,一个是byte型数组,一个是char型数组,初始容量都为80,用于后面readUTF方法中提供缓存数组。
    private byte bytearr[] = new byte[80];
    private char chararr[] = new char[80];

    /**
     * 每次读取多个字节的数据,传入的参数为一个byte型数组,读取的数据会填充到该数组中区,最终返回实际读取的字节数。
     */
    public final int read(byte b[]) throws IOException {
        return in.read(b, 0, b.length);
    }

    /**
     * 每次读取多个字节的数据,有3个传入的参数,第一个参数为一个byte型数组,用于装取读取到的字节数据,第二和第三个参数都是int型数据,分别表示读取的起点,
     * 和读取的长度。
     */
    public final int read(byte b[], int off, int len) throws IOException {
        return in.read(b, off, len);
    }

    /**
     * 每次读取多个字节的数据,传入的参数为一个byte型数组,用于装取读取的字节数据。与上面read方法的区别在于该方法必须等到传入的字节数组填满或者读取到文件
     * 结尾或者抛出异常时才会返回,最终返回实际读取的字节数。
     */
    public final void readFully(byte b[]) throws IOException {
        readFully(b, 0, b.length);
    }

    /**
     * 每次读取多个字节的数据,传入的第一个参数为一个byte型数组,用于装取读取的字节数据,第二个参数和第三个参数都为int型数据,分别代表着读取的起点和读取
     * 的长度。
     */
    public final void readFully(byte b[], int off, int len) throws IOException {
	//对传入的参数进行安全检测,如果len<0,则抛出相应异常。
        if (len < 0)
            throw new IndexOutOfBoundsException();
	//通过一个循环读取指定长度len的数据,直到读取完毕或者读到文件结尾时才返回。
        int n = 0;
        while (n < len) {
            int count = in.read(b, off + n, len - n);
            if (count < 0)
                throw new EOFException();
            n += count;
        }
    }

    /**
     * 该方法用于跳过指定长度的字节数。最终返回实际跳过的字节数。
     */
    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;
    }

    /**
     * 读取boolean型数据,原理是读取一个字节的数据,将其与0比较,非0为true,0为false,最终返回比较结果。
     */
    public final boolean readBoolean() throws IOException {
        int ch = in.read();
        if (ch < 0)
            throw new EOFException();
        return (ch != 0);
    }

    /**
     * 每次读取一个byte型数据,原理是每次读取一个字节的数据,然后将其转换成byte型数据并返回。
     */
    public final byte readByte() throws IOException {
        int ch = in.read();
        if (ch < 0)
            throw new EOFException();
        return (byte)(ch);
    }

    /**
     * 每次读取一个无符号的byte型数据,原理是每次读取一个字节的数据,最终将其返回。
     */
    public final int readUnsignedByte() throws IOException {
        int ch = in.read();
        if (ch < 0)
            throw new EOFException();
        return ch;
    }

    /**
     * 每次读取一个short类型的的数据,原理是每次读取两个字节的数据,分别代表着short类型数据的高八位和低八位,最终将其组合成一个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类型的数据,原理是每次读取两个字节的数据,分别代表着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);
    }

    /**
     * 每次读取一个char类型的数据,原理是每次读取两个字节的数据,分别代表着char类型数据高八位和低八位,最终将其组合并转换成一个char类型数据并返回。
     */
    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类型的数据,原理是每次读取4个字节的数据,每个字节的数据依次从int型数据的最高位开始填充,最终将拼装后的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));
    }

    //定义了一个字节数组,容量为8,用于在readLong方法中调用读取多个字节的read方法时,提供缓存数组。
    private byte readBuffer[] = new byte[8];

    /**
     * 每次读取一个long类型的数据,其工作原理是通过readFully方法一次读取8个字节的数据,然后将读取的数据依次从long型数据的最高位开始填充,最终将填充完毕的
     * 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型数据,原理是先通过readInt方法读取一个int型数据,然后再将其转换成float行数据并返回。
     */
    public final float readFloat() throws IOException {
        return Float.intBitsToFloat(readInt());
    }

    /**
     * 每次读取一个double型数据,原理是先通过readLong方法读取一个long型数据,然后再将其转换成double型数据并返回。
     */
    public final double readDouble() throws IOException {
        return Double.longBitsToDouble(readLong());
    }
    
    //定义了一个char型数组,用于readLine中存储读取的数据。
    private char lineBuffer[];

    /**
     * 一次读取一行数据,最终将读取的数据转换为一个String类型并返回。
     */
    @Deprecated
    public final String readLine() throws IOException {
	//又定义了一个char类型数组buf,作为临时存储数据的缓存。
        char buf[] = lineBuffer;
        //当第一次调用该方法时,buf和lineBuffer都为null,所以对它们进行初始化,初始化容量为128,这里指的注意的是两个数组句柄指向了同一个数组对象。
        if (buf == null) {
            buf = lineBuffer = new char[128];
        }
	//声明了3个int型变量,room为buf的容量大小,offset记录了当前读取的位置,c用来记录每次读取的数据。
        int room = buf.length;
        int offset = 0;
        int c;

	//通过一个死循环来不断调用read方法读取数据,在循环外围留下了标记loop,方便在随后的switch块中直接跳出最外围循环。
loop:   while (true) {
   	    //通过一个switch块来判断每次读取的内容,根据不同的内容来决定不同的操作,主要用于判断是否遇到换行符。
            switch (c = in.read()) {
              case -1:
              case '\n':
		//当读取的内容为'\n'或-1时,都直接跳出循环,-1表示已经没有数据可读,'\n'表示读到换行符。
                break loop;
              case '\r':
		//当读取的内容为'\r'时,因为操作系统原因,换行符除了'\n'外还可能是'\r\n',所以我们需要继续往后读一个字节内容,看后一个字节的内容是否是
		//'\n',如果是则表示读取到换行符,跳出循环,如果不是且读取的数据不为-1(即读取到了新数据),那么通过PushbackInputStream流将第二次读取的数
		//回推回去,然后跳出循环。
                int c2 = in.read();
                if ((c2 != '\n') && (c2 != -1)) {
                    if (!(in instanceof PushbackInputStream)) {
                        this.in = new PushbackInputStream(in);
                    }
                    ((PushbackInputStream)in).unread(c2);
                }
                break loop;
              default:
		//当文件读取没有结束且没有读取到换行符时,执行此处代码。
                if (--room < 0) {
		    //--room小于零时,则表示下一次读取时,存放数据的缓存数组容量已经不足,所以此时需要对其扩容(增加128字节的容量),将临时缓存buf扩容后,
		    //重新为room值赋值,并将原缓存中的数据拷贝到扩容后的新缓存数组中,最后将lineBuffer也指向扩容后的数组,丢弃吊原有的缓存数组。
                    buf = new char[offset + 128];
                    room = buf.length - offset - 1;
                    System.arraycopy(lineBuffer, 0, buf, 0, offset);
                    lineBuffer = buf;
                }
		//每次读取的数据都根据offset位置,依次存放至缓存数组中去。
                buf[offset++] = (char) c;
                break;
            }
        }
	//当没有读取到任何数据时,直接返回null。
        if ((c == -1) && (offset == 0)) {
            return null;
        }
	//将缓存中的数据转化为String类型数据并返回。
        return String.copyValueOf(buf, 0, offset);
    }

    /**
     * 从流中以UTF编码格式读取数据,并以String类型返回。实际调用的是之后带参的readUTF的方法。
     */
    public final String readUTF() throws IOException {
        return readUTF(this);
    }

    /**
     * 从流中已UTF编码格式读取数据,传入的参数为一个DataInput对象,用于对流进行读取操作。
     */
    public final static String readUTF(DataInput in) throws IOException {
	//根据DataOutputStream中writeUTF方法规定,前两个字节中存放的是数据的总长度,所以通过readUnsignedShort方法可以将其读取出来并赋值给声明的int型变量
	//utflen。
        int utflen = in.readUnsignedShort();
	//定义了两个数组,一个byte型数组,一个char型数组,前者用于存放流中所有的数据,后者则是存放经过处理后的数据。
        byte[] bytearr = null;
        char[] chararr = null;
	
	//为两个缓存数组进行初始化,先判断传入的DataInput对象是否属于DataInputStream及其衍生类,如果是就直接使用内置的两个缓存数组,并且检测数组容量是否
	//满足数据长度,如果不满足则自动将其容量扩大2倍,如果不属于DataInputStream,则根据数据长度新建两个数组对象。
        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];
        }

	//声明了3个int型变量,c表示每次读取的数据,因为一个utf8格式的数据最多可以有4个字节,所以在数据拼接时最多用到两个char型数据,char2,char3就是用于
	//数据拼接。count,和chararr_count则分别表示了两个缓存区中的读取的数据位置。
        int c, char2, char3;
        int count = 0;
        int chararr_count=0;

	//通过readFully方法,将所有的数据都直接存储到byte型数组bytearr中。
        in.readFully(bytearr, 0, utflen);

	//这里其实可以当做对所有的数据进行的一个预处理,通过该方法可以将数据中单字节的数据存储到char型数组中去,如果遇到不是单字节数组的则直接跳出循环。
        while (count < utflen) {
	    //前面经过readFully操作,bytearr中存放了所有的数据,所以此时每次从中取出一个字节的数据,通过与Oxff相与转化成int型数据(1与任何一个数相与都是
	    //其本身)。
            c = (int) bytearr[count] & 0xff;
	    //因为utf8单字节的表示范围是0~127,所以如果c>127则直接退出。
            if (c > 127) break;
	    //正常情况下则是将读取的数据存放进char型数组中,两个索引值自动加1。
            count++;
            chararr[chararr_count++]=(char)c;
        }

	//下面则是对数据进行正式处理,通过一个循环遍历byte数组bytearr中的数据,进行匹配后进行不同的操作,然后将拼接的数据方法char型数组chararr中。
        while (count < utflen) {
	    //每次从bytearr中取出一个字节的数据,通过与0xff相与转化成int型数据赋值给c。
            c = (int) bytearr[count] & 0xff;
	    //下面将c右移4位,然后用一个switch块将其包裹,来对其进行匹配。这里要说一下UTF8的编码规则了,如果只有一个字节则其最高二进制位为0;如果是多字
	    //节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的字节数,其余各字节均以10开头,后面会附上utf编码格式图。下面的操作实际
	    //上只取utf8数据的高八位,右移四位后,可以通过其值来判断字节数(1的个数)。
            switch (c >> 4) {
                case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
                    // 0xxxxxxx,该种情况下表示是单字节数据,可以直接将其转成char型数据存放入chararr数组中去。
                    count++;
                    chararr[chararr_count++]=(char)c;
                    break;
                case 12: case 13:
                    // 110xxxxx 10xxxxxx,该种情况下是双字节数据,需要从byte型数组中取出两个字节数据,所以先将读取索引count+2。然后进行安全检测,读取位置
                    // 不能超过数据总长度。
                    count += 2;
                    if (count > utflen)
                        throw new UTFDataFormatException(
                            "malformed input: partial character at end");
		    //这里c是两字节的高八位,所以还需向后读取一字节数据,赋值给char2。
                    char2 = (int) bytearr[count-1];
		    //根据utf8的编码规则可知,双字节的后八位必须满足10xxxxxx,所以将其与0xCO相与,如果满足,运算结果必为0x80,所以如果不等于,则抛出异常。
                    if ((char2 & 0xC0) != 0x80)
                        throw new UTFDataFormatException(
                            "malformed input around byte " + count);
		    //这里就是将读取的两个字节的数据拼装成一个char型数据了,以双字节详细说明:
		    //step1(c & 0x1F):c读取的其实是双字节数据的高八位,因为utf8编码格式,双字节高八位的数据编码格式为110xxxxx,当与0x1F相与后可以将前
		    //3位的标志位置0,只保留数据位,然后左移6位作为char型数据的高位,至于为什么是移动6位,是因为双字节utf8编码的表示范围是128~2047,所以
		    //数据位使用12位就足够表示,因此在DataOutputStream中的writeUTF中就以6位为一个移动的步数。
		    //step2(char2 & 0x3F):char2为读取的双字节数据的低八位,因为utf8编码格式,双字节低八位的数据编码格式为10xxxxxx,当与0x3F相与后将高两位
		    //的标志位置0,只保留数据位。到此一个char型数据的所有数据位都得到了。
		    //step3(将两者或):通过一个或操作将上两步得到的数据位拼接起来得到一个char型数据存储到字符数组chararr中。
                    chararr[chararr_count++]=(char)(((c & 0x1F) << 6) |
                                                    (char2 & 0x3F));
                    break;
                case 14:
                    //1110 xxxx  10xx xxxx  10xx xxxx ,此种情况下表示是3字节数据,需从bytearr数组中取出3个字节的数据进行拼装处理,因为步骤与上面双字节数据
		    //类似所以在此就不多叙述了,原理是一样的。
                    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:
                    // 10xx xxxx,  1111 xxxx 剩余的就是这两者情况了,刚开始在这里纠结了好久,因为标准utf8是可以占有1~4个字节的,然而这里并没有对占用4字节
		    // 的情况进行处理,但是后来通过在Stack Overflow找到了答案,其实源码的JavaDocs中已经有了解释,在这里使用的是modified UTF-8而不是标准的
		    // utf8,所以只会占用1~3个字节,在这里不得不再次感叹一下,Stack Overflow确实很强大。
                    throw new UTFDataFormatException(
                        "malformed input around byte " + count);
            }
        }
        // 最终将chararr中的数据转换成String类型并返回,这里指的注意的在源码中这里有个注释'The number of chars produced may be less than utflen'。
        return new String(chararr, 0, chararr_count);
    }
}
DataOutputStream.java:
package java.io;

public class DataOutputStream extends FilterOutputStream implements DataOutput {
    //定义了一个int型变量written,该变量用于记录实际写入数据的字节数,当写入的数据超过int型数据的表达范围时,该值将被赋予Integer.MAX_VALUE.
    protected int written;

    //声明了一个byte型数组的句柄,该数组在之后的writeUTF中充当着数据缓存的作用。
    private byte[] bytearr = null;

    /**
     * 一个带一个参数的构造方法,传入的参数是一个OutputStream对象,内部调用FilterOutputStream对象的构造参数。
     */
    public DataOutputStream(OutputStream out) {
        super(out);
    }

    /**
     * 该方法用于记录在流中吸入了多少字节的数据,如果长度超过了int型表达范围,则该值一直为Integer.MAX_VALUE。
     */
    private void incCount(int value) {
        int temp = written + value;
	//这里的temp<0的情况其实就是int型变量数据溢出了。
        if (temp < 0) {
            temp = Integer.MAX_VALUE;
        }
        written = temp;
    }

    /**
     * 该方法每次向写入一个字节的数据。
     */
    public synchronized void write(int b) throws IOException {
        out.write(b);
	//每写入一个字节数据后,调用incCount方法,使得计数加1。
        incCount(1);
    }

    /**
     * 该方法每次写入多个字节的数据,含有3个参数,第一个参数为一个byte型数组, 作为要写入流中的数据源,后两个参数都为int型数据,第一个表示开始写入的位置,
     * 第二个为写入的长度,成功往流中写入数据后调用incCount方法,记录下写入数据的长度。
     */
    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型数据,本质其实是根据java中默认的非零为true,0为false,向流中写入一个数值,读取时根据这个规则再还原成对应的boolean型值。
     */
    public final void writeBoolean(boolean v) throws IOException {
        out.write(v ? 1 : 0);
        incCount(1);
    }

    /**
     * 每次向流中写入一个byte型数据。
     */
    public final void writeByte(int v) throws IOException {
        out.write(v);
        incCount(1);
    }

    /**
     * 每次向流中写入一个short型数据,实质是经过两次写入操作,先写入short型数据的高八位,再写入低八位。
     */
    public final void writeShort(int v) throws IOException {
        out.write((v >>> 8) & 0xFF);
        out.write((v >>> 0) & 0xFF);
        incCount(2);
    }

    /**
     * 原理同上,每次向流中写入一个char型数据,实质是经过两次写入,先入char型数据的高八位,在写入其低八位。
     */
    public final void writeChar(int v) throws IOException {
        out.write((v >>> 8) & 0xFF);
        out.write((v >>> 0) & 0xFF);
        incCount(2);
    }

    /**
     * 每次向流中写入一个int型数据,实质是经过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);
    }

    //定义了一个byte型数组,其容量为8个字节,用于后面的writeLong方法中作为数据缓存使用。
    private byte writeBuffer[] = new byte[8];

    /**
     * 一次向流中写入一个long型数据。将long型数据从最高位开始以8位为一组,依次写入缓存数组中,最后通过write方法写入流中。
     */
    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型数据,本质是将float型数据转化成int型数据,然后通过writeInt方法写入流中。
     */
    public final void writeFloat(float v) throws IOException {
        writeInt(Float.floatToIntBits(v));
    }

    /**
     * 一次向流中写入一个double型数据,本质是将double型数据转化成long型数据,然后通过writeLong方法写入流中。
     */
    public final void writeDouble(double v) throws IOException {
        writeLong(Double.doubleToLongBits(v));
    }

    /**
     * 一次向流中以byte的形式写入一个字符串类型数据,本质是通过字符串的charAt方法,将字符串中的每个字符转化成byte型数据写入流中。
     */
    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);
    }

    /**
     * 一次向流中以char的形式写入一个字符串类型数据,本质是通过字符串的charAt方法,将字符串中的每个字符以char型(通过高低位拆分)直接写入流中。
     */
    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);
    }

    /**
     * 一次向流中以utf8的格式写入一个String类型的数据。本质是调用之后带两个参数的writeUTF方法。
     */
    public final void writeUTF(String str) throws IOException {
        writeUTF(str, this);
    }

    /**
     * 一次向流中以utf8的格式写入一个String类型的数据。
     */
    static int writeUTF(String str, DataOutput out) throws IOException {
	//定义了四个int型变量,strlen记录了要写入String类型数据的总长度,utflen记录了以utf8格式进行编码后所占用的字节数,c用于记录str中每个字符的数据,
	//count则表示了str中已经写入流中的字符数。
        int strlen = str.length();
        int utflen = 0;
        int c, count = 0;

        //这里使用一个循环来记录以utf8格式来将数据写入流中所需要的字节数。这里讲究的原则是,单字节utf8数据表达的范围是0~127,utf8双字节表达范围为128~
	//2047,三字节为~65535。因此根据取出字符的数值可以得知相应字符转换成utf8格式所需要的字节数,然后将utflen累加上相应的字节数,最终得到总共需要的utf
	//8格式下的字节数。
        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;
            }
        }
	
	//这里限制了所要写入string类型数据的总长度,在转化为utf8格式后,其总长度不要超过65535,否则将会抛出相应异常。
        if (utflen > 65535)
            throw new UTFDataFormatException(
                "encoded string too long: " + utflen + " bytes");
	//定义了一个字节数组,用于充当向流中写入数据时的临时缓存。
        byte[] bytearr = null;
	//先判断传入的out对象是否是DataOuputStream类或者其子类,如果是,就使用其内部定义的bytearr数组,否则就新建一个byte数组赋值给bytearr。值得注意的是
	//如果使用内置的数组,会先对内置数组进行检测,检测其是否为null和容量是否满足需求,如果不满足也要重新创建。
        if (out instanceof DataOutputStream) {
            DataOutputStream dos = (DataOutputStream)out;
            if(dos.bytearr == null || (dos.bytearr.length < (utflen+2)))
		//这里就是内置数组为null或者容量不够时,重新创建数组对象,新建的容量会根据utflen来进行扩容,这里注意到所有的数组容量中都有+2的操作,这是
		//除了要写入数据外,该方法还会在开头出写入两个字节的数据,这两个字节的数据记录了流中数据的总长度。
                dos.bytearr = new byte[(utflen*2) + 2];
            bytearr = dos.bytearr;
        } else {
            bytearr = new byte[utflen+2];
        }

	//在开端写入两个字节的数据,该数据为utflen,记录了以utf8格式要写入数据的总的字节数。因为前面限制了总长度不能高于65535,所以这里只需两个字节就可以
	//记录下总长度。
        bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
        bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF);
	
	//下面通过一个循环,先对数据进行预处理,如果是utf8中的单字节数据,则直接装换成byte型写入数据缓存中。
        int i=0;
        for (i=0; i<strlen; i++) {
           c = str.charAt(i);
           if (!((c >= 0x0001) && (c <= 0x007F))) break;
           bytearr[count++] = (byte) c;
        }

	//下面是正式处理要写入的数据,根据其数值范围,确定其在utf8格式下需要几个字节来表示,然后根据相应字节数通过对应的移位操作,将其以8位为一组,从最高
	//开始,依次写入数据缓存区中。
        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);
	//最终返回以utf8格式写入流中的字节数。
        return utflen + 2;
    }

    /**
     * 返回当前写入流中数据的字节总数。
     */
    public final int size() {
        return written;
    }
}
为了方便理解,这里附上一幅utf8编码的一些小格式:

通过以上对源码的简要描述,相信我们对DataInputStream/DataOutputStream有了初步的认识,下面我们通过一个简单的小例子来展示这两个类的具体用法:
package dataInOut;

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

public class DataIOTest1 {
	public static void main(String[] args) {
		try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(
				new File("./src/file/test1.txt")));
				DataInputStream dis = new DataInputStream(new FileInputStream(
						new File("./src/file/test1.txt")))) {
			dos.writeInt(1);
			dos.writeBoolean(true);
			dos.writeUTF("Hello World");
			dos.flush();
			int tempInt = dis.readInt();
			boolean tempBoolean = dis.readBoolean();
			String tempUTF = dis.readUTF();
			System.out.println(tempInt+" : "+tempBoolean+" : "+tempUTF);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
以上为本篇的全部内容。
Java8 I/O源码-DataInputStream与DataOutputStream DataInputStream为数据输入流,它允许应用程序以与机器无关方式从底层输入流中读取基本Java数据类型。DataOutputStream为数据输出流,它允许应用程序以适当方式将基本 Java数据类型写入输出流中。 阅读详情

相关推荐

IO流之使用DataInputStream和DataOutputStream复制二进制文件

文章目录前言一、DataInputStream和DataOutputStream二、实现步骤1.例题内容总结 前言 在IO流学习中,我们通常会碰到一些二进制文件的复制问题,例如一张图片,一段视频,也可能是我们的一个class字节码文件,他们都是典型的使用二进制编码实现的文件. 那么我们应该如何使用IO流知识解决二进制文件的复制问题呢? 答案是需要使用DataOutputStream和DataInputStream实现类,下面介绍如何使用这两个类. 提示:以下是本篇文章正文内容,下面案例可供参考...

Richarddjh的博客 1066

java--基础--16.6--IO流--DataInputStream,DataOutputStream

通常,Reader 所作的每个读取请求都会导致对底层字符或字节流进行相应的读取请求。将缓冲指定文件的输入。如果没有缓冲,则每次调用 read() 或 readLine() 都会导致从文件中读取字节,并将其转换为字符后返回,而这是极其低效的。如果没有缓冲,则每次调用 print() 方法会导致将字符转换为字节,然后立即写入到文件,而这是极其低效的。将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。

zhou920786312的博客 1万+

050-Java中DataInputStream和DataOutputStream使用详解

本文主要介绍Java中DataInputStream和DataOutputStream的使用方法,数据输出流(DataOutputStream)、数据输入流 (DataInputStream)是与平台无关的数据操作流。通常数据输出流会按照一定的格式将数据输出,再通过数据输入流按照一定的格式将数据读入。DataOutputStream 和 DataOutputStream 用来读写固定字节格式数据。

软件之家的博客 9043

java datainputstream_Java中DataInputStream的用法

展开全部1、DataOutputStream:数据输出流允许e5a48de588b63231313335323631343130323136353331333337396239应用程序以适当方式将基本 Java 数据类型写入输出流中。然后应用程序可以使用数据输入流将数据读入。2、例子:/***必须先使用DataOutputStream写入数据,然后使用DataInputStream读取数据方可。...

weixin_39886205的博客 1万+

招标文件智能解析系统开发文档

本系统是一个基于OCR与大语言模型的招标文件智能解析平台,专门用于处理和分析招标文件,自动提取关键信息并进行结构化展示。

警世龙的技术博客 1925

HFSS入门学习笔记——微带一分四功分器的设计与仿真

功率分配器是将输入信号功率分成相等或不相等的几路功率输出的一种多端口无源微波网络,用于功率分配和功率合成。工程上常用的微带功分器有T型功分器和威尔金森功分器。经典的威尔金森功分器有几个缺点:①、多应用于低频段,高频段时,波长会与隔离电阻的尺寸相比拟,此时需要考虑电阻的分布参数效应;②、大功率应用时,隔离电阻的耗散功率要很大,因此隔离电阻的体积也会比较大。

weixin_42126696的博客 2万+

Java IO流之DataInputStream和DataOutputStream分析

简介 DataOutputStream数据输出流允许应用程序将基本Java数据类型写到基础输出流中,而DataInputStream数据输入流允许应用程序以机器无关的方式从底层输入流中读取基本的Java类型. DataInputStream介绍 1.构造方法 public DataInputStream(InputStream in) { super(in);} 有参构造方法传入的基础...

梦悠哉的博客 3万+

WPF窗口定位技巧:从基础到高级的三种实现方法

本文详细解析WPF窗口定位的三种核心方法:屏幕居中(CenterScreen)、父窗口居中(CenterOwner)和手动定位(Manual)。通过对比其适用场景、实现代码与注意事项,帮助开发者根据实际需求选择最佳方案,从而提升WPF应用程序的用户体验与专业度。

weixin_29224309的博客 272

Java IO操作——数据操作流DataOutputStream和DataInputStream的使用

学习目标  掌握DataOutputStream和DataInputStream的作用 可以使用DataOutputStream和DataInputStream写入和读入数据 数据操作流 在io包中,提供了两个与平台无关的数据操作流: 数据输出流(DataOutputStream) 数据输入流 (DataInputStream) 通常数据输出流会按照一定的格式将数据输出,再通过数据输

2万+

斑马算法优化变分模态分解ZOA-VMD数字信号去噪(优化K值 alpha值)【含Matlab源码 4887期】

斑马算法优化变分模态分解ZOA-VMD数字信号去噪(优化K值 alpha值) 完整的代码,包运行;运行操作视频见优快云资源!适合小白!

订阅付费专栏Matlab(奶茶价版),可赠送奶茶价版付费专栏指定代码1份; 810

虚拟机安装VMware Tools

单击虚拟机-->安装VMware Tool 安装之后会有一个VMware Tools硬盘,里面有VMware Tools.tar.gz的压缩包 按照提示解压它,然后执行vmware-install.pl安装VMware Tools 将安装包放到/tmp目录下。可以直接拽过去,也可以使用cp或者mv命令 进入tmp文件目录并解压此文件包 cd /tmp ...

afei001 620

【Java IO】DataInputStream/DataOutputStream示例

package mjn.io; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.FileInputStrea

MJN 3287

数据架构师必看:大数据时代的架构演进与实践

数据架构是企业数据资产的“骨架”——它决定了数据能多快被处理、多准被分析、多安全被存储。本文将聚焦从传统架构到大数据架构的关键转折点,覆盖技术选型、典型场景、落地挑战三大核心命题,帮助读者建立“演进思维”而非“固定思维”。本文将按“历史演进→核心概念→技术原理→实战案例→趋势展望”的逻辑展开,用“超市仓库升级”贯穿始终,让抽象的技术概念具象化。传统数据仓库:精但小,适合结构化、批量分析。大数据架构:大但乱,适合全类型、海量数据存储。流批一体:解决实时+批量的效率问题。湖仓一体。

AI Agent Harness Engineering 913

JAVA中常用IO流类: DataInputStream和DataOutputStream

DataInputStream和DataOutputStream二者分别实现了DataInput/DataOutput接口 DataInputStream能以一种与机器无关(当前操作系统等)的方式,直接从地从字节输入流读取JAVA基本类型和String类型的数据,常用于网络传输

caixiexin的专栏 2万+

YOLO26 实战全流程:从数据集标注到端到端无NMS推理(完整版,附ProgLoss核心调参技巧+避坑指南)

本章节是实战经验的精华❌ 数据集标注不规范,导致模型精度低 → ✅ 严格遵守工业级标注规范,标注框紧贴目标边缘,不要漏标/多标;❌ 训练时未使用预训练权重,导致收敛极慢 → ✅ 必须开启,预训练权重能让模型快速收敛;❌ ProgLoss参数调整幅度过大,导致训练崩溃 → ✅ 每次只调整一个参数,幅度±0.02即可,循序渐进;❌ 推理时强行添加NMS参数,画蛇添足 → ✅ YOLO26无需NMS,推理命令中不要加conf/iou参数,否则会降低推理速度;

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1141

【大模型学习】第二十四章 生成式人工智能(GAI)简介

‌的AI技术,其核心目标是通过学习数据中的规律,生成与训练数据类似但全新的文本、图像、音频、视频甚至代码。生成式人工智能(GAI)是一类能够‌。

以实战经验介绍行业知识 1637

深入剖析Java的DataInputStream和DataOutputStream

Java的DataInputStream和DataOutputStream是处理原始数据类型的高效工具,采用装饰者模式在字节流和基本类型间转换,严格遵循大端字节序。写入流程通过位运算拆分数据,读取时需保持相同顺序。实战中配合缓冲流可提升性能,但需注意字节顺序陷阱、EOF异常等问题。相比NIO方案,DataStream更适合流式处理场景,是网络传输和跨平台数据交换的理想选择。使用时应保持读写顺序一致,并妥善处理资源关闭。

weixin_51786043的博客 847

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

DataOutputStream和DataInputStream

是 Java 中的一个类,用于将基本数据类型和字符串数据写入输出流。它是的子类,因此可以用于将数据写入各种输出目标,如文件、网络连接等。是 Java 中的一个类,用于从输入流中读取基本数据类型和字符串数据。它是的子类,因此可以用于从各种输入源(如文件、网络连接等)读取数据。

qq_27210677的专栏 1290

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Java_io体系之DataInputStream、DataOutputStream简介、走进源码及示例——08

Java_io体系之DataInputStream、DataOutputStream简介、走进源码及示例——08        还是从输出流开始说、此流是有付出才有回报、虽然不想PipedInputStream、PipedOutputStream那样必须一对对象结合才能使用、但是也要先有DataOutputStream将数据写入目的地中、才能使用DataInputStream读取到数据。 一:

Oscar Chen 4355

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

java dataoutputstream_java 数据流DataOutputStream和DataInputstream

应用实例import java.io.*;public class DataStreamDemo{public static void main(String[] args){try{DataOutputStream out=new DataOutputStream(new FileOutputStream("F:\\workspace\\JavaPrj\\test.txt"));double[]...

weixin_33746819的博客 1万+

Java IO(八):DataInputStream和DataOutputStream的使用

DataInputStream 和 DataOutputStream 的使用场景核心在于需要把数据以固定的二进制表示方式写入或读取,便于高效、简单地进行二进制文件存储或跨语言的数据交换。它们提供了对基本数据类型的直接读写方法,减少手动字节处理的工作量,同时要求写入与读取端保持一致的顺序与类型约定。

欧阳方超的专栏 712

java IO流之DataInputStream DataOutputStream 数据流 实例

java IO流之DataInputStream DataOutputStream 数据流 数据流:用来操作基本数据类型(byte short int long double float char boolean)和字符串(String) DataInputStream 将文件中存储的基本数据类型和字符串写入内存的变量中。数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。 构造方法摘要 DataInputStream

SKPrimin的博客 823

Java语言DataOutputStream和DataInputStream使用方法

Java语言DataOutputStream和DataInputStream使用方法 常用方法 DataOutputStream //创建数据写入流(写入到文件),第二个参数为true意思是追加写入,而不是覆盖写入 DataOutputStream dos = new DataOutputStream(new FileOutputStream("water.txt",true));. //写入数据 dos.writeInt(int类型的数据); //写入int类型 dos.writeDouble(dou

影龙武的博客qq_43594119 3863

DataInputStream 和 DataOutputStream流

1. 简介 DataInputStream 和 DataOutputStream流是基于 InputStream流 和 OutputStream流衍生出的子类,当然他们还基于父类DataInput 和 DataOutput。它们是针对Java基本数据类型读取/写入操作的。这两个类读写的文件都是针对机器的,DataOutputStream写出去的数据不是给人看的是给机器看的。而DataInputStream只能读取DataOutputStream写出去的文件。 使用DataOutputStream流写入t

weixin_43808717的博客 2686

IO流 | DataInputStream和DataOutputStream

过滤器字节流: FilterInputStream:过滤器字节输入流 FilterOutputStream:过滤器字节输出流 它们的主要用于封装其他的输入输出流,为它们提供一些额外的功能。 具有以下实现类: 以下以FilterInputStream做主要分析: 一、FilterInputStream 1. 源码分析 (1)继承关系 该类继承自InputStream父类(表示该类可实现父类...

BUZZ_Q的博客 1409
上一篇: java IO笔记(RandomAccessFile)
下一篇: java IO笔记(PushbackInputStream)
moonfish0607
博客等级 码龄11年 26粉丝 50原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

moonfish0607

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值