Java IO筆記(DataInputStream/DataOutputStream)


(最近剛來到簡書平臺,以前在CSDN上寫的一些東西,也在逐漸的移到這兒來,有些篇幅是很早的時(shí)候?qū)懴碌?,因此可能會看到一些?nèi)容雜亂的文章,對此深感抱歉,以下為正文)


正文

本篇講述的是Java IO包中的DataInputStream和DataOutputStream類。

這兩個類都屬于Java IO中的包裝類,為基礎(chǔ)流提供了豐富的讀寫方法,可以將Java中的數(shù)據(jù)類型輕松地寫入流中。

下面還是先貼出源碼進(jìn)行簡單的分析:
DataInputStream.java


package java.io;
 
public class DataInputStream extends FilterInputStream implements DataInput {
 
    /**
     * 一個帶一個參數(shù)的構(gòu)造方法,傳入的參數(shù)為一個InputStream對象。內(nèi)部本質(zhì)是調(diào)用父類FilterInputStream的構(gòu)造方法。
     */
    public DataInputStream(InputStream in) {
        super(in);
    }
 
    //定義了兩個數(shù)組,一個是byte型數(shù)組,一個是char型數(shù)組,初始容量都為80,用于后面readUTF方法中提供緩存數(shù)組。
    private byte bytearr[] = new byte[80];
    private char chararr[] = new char[80];
 
    /**
     * 每次讀取多個字節(jié)的數(shù)據(jù),傳入的參數(shù)為一個byte型數(shù)組,讀取的數(shù)據(jù)會填充到該數(shù)組中區(qū),最終返回實(shí)際讀取的字節(jié)數(shù)。
     */
    public final int read(byte b[]) throws IOException {
        return in.read(b, 0, b.length);
    }
 
    /**
     * 每次讀取多個字節(jié)的數(shù)據(jù),有3個傳入的參數(shù),第一個參數(shù)為一個byte型數(shù)組,用于裝取讀取到的字節(jié)數(shù)據(jù),第二和第三個參數(shù)都是int型數(shù)據(jù),分別表示讀取的起點(diǎn),
     * 和讀取的長度。
     */
    public final int read(byte b[], int off, int len) throws IOException {
        return in.read(b, off, len);
    }
 
    /**
     * 每次讀取多個字節(jié)的數(shù)據(jù),傳入的參數(shù)為一個byte型數(shù)組,用于裝取讀取的字節(jié)數(shù)據(jù)。與上面read方法的區(qū)別在于該方法必須等到傳入的字節(jié)數(shù)組填滿或者讀取到文件
     * 結(jié)尾或者拋出異常時(shí)才會返回,最終返回實(shí)際讀取的字節(jié)數(shù)。
     */
    public final void readFully(byte b[]) throws IOException {
        readFully(b, 0, b.length);
    }
 
    /**
     * 每次讀取多個字節(jié)的數(shù)據(jù),傳入的第一個參數(shù)為一個byte型數(shù)組,用于裝取讀取的字節(jié)數(shù)據(jù),第二個參數(shù)和第三個參數(shù)都為int型數(shù)據(jù),分別代表著讀取的起點(diǎn)和讀取
     * 的長度。
     */
    public final void readFully(byte b[], int off, int len) throws IOException {
    //對傳入的參數(shù)進(jìn)行安全檢測,如果len<0,則拋出相應(yīng)異常。
        if (len < 0)
            throw new IndexOutOfBoundsException();
    //通過一個循環(huán)讀取指定長度len的數(shù)據(jù),直到讀取完畢或者讀到文件結(jié)尾時(shí)才返回。
        int n = 0;
        while (n < len) {
            int count = in.read(b, off + n, len - n);
            if (count < 0)
                throw new EOFException();
            n += count;
        }
    }
 
    /**
     * 該方法用于跳過指定長度的字節(jié)數(shù)。最終返回實(shí)際跳過的字節(jié)數(shù)。
     */
    public final int skipBytes(int n) throws IOException {
        int total = 0;//實(shí)際跳過的字節(jié)數(shù)
        int cur = 0;  //當(dāng)前跳過的字節(jié)數(shù)
 
        while ((total<n) && ((cur = (int) in.skip(n-total)) > 0)) {
            total += cur;
        }
        return total;
    }
 
    /**
     * 讀取boolean型數(shù)據(jù),原理是讀取一個字節(jié)的數(shù)據(jù),將其與0比較,非0為true,0為false,最終返回比較結(jié)果。
     */
    public final boolean readBoolean() throws IOException {
        int ch = in.read();
        if (ch < 0)
            throw new EOFException();
        return (ch != 0);
    }
 
    /**
     * 每次讀取一個byte型數(shù)據(jù),原理是每次讀取一個字節(jié)的數(shù)據(jù),然后將其轉(zhuǎn)換成byte型數(shù)據(jù)并返回。
     */
    public final byte readByte() throws IOException {
        int ch = in.read();
        if (ch < 0)
            throw new EOFException();
        return (byte)(ch);
    }
 
    /**
     * 每次讀取一個無符號的byte型數(shù)據(jù),原理是每次讀取一個字節(jié)的數(shù)據(jù),最終將其返回。
     */
    public final int readUnsignedByte() throws IOException {
        int ch = in.read();
        if (ch < 0)
            throw new EOFException();
        return ch;
    }
 
    /**
     * 每次讀取一個short類型的的數(shù)據(jù),原理是每次讀取兩個字節(jié)的數(shù)據(jù),分別代表著short類型數(shù)據(jù)的高八位和低八位,最終將其組合成一個short型數(shù)據(jù)并返回。
     */
    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類型的數(shù)據(jù),原理是每次讀取兩個字節(jié)的數(shù)據(jù),分別代表著short類型數(shù)據(jù)的高八位和低八位,最終將其組合并返回。
     */
    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類型的數(shù)據(jù),原理是每次讀取兩個字節(jié)的數(shù)據(jù),分別代表著char類型數(shù)據(jù)高八位和低八位,最終將其組合并轉(zhuǎn)換成一個char類型數(shù)據(jù)并返回。
     */
    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類型的數(shù)據(jù),原理是每次讀取4個字節(jié)的數(shù)據(jù),每個字節(jié)的數(shù)據(jù)依次從int型數(shù)據(jù)的最高位開始填充,最終將拼裝后的int型數(shù)據(jù)返回。
     */ 
    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));
    }
 
    //定義了一個字節(jié)數(shù)組,容量為8,用于在readLong方法中調(diào)用讀取多個字節(jié)的read方法時(shí),提供緩存數(shù)組。
    private byte readBuffer[] = new byte[8];
 
    /**
     * 每次讀取一個long類型的數(shù)據(jù),其工作原理是通過readFully方法一次讀取8個字節(jié)的數(shù)據(jù),然后將讀取的數(shù)據(jù)依次從long型數(shù)據(jù)的最高位開始填充,最終將填充完畢的
     * long型數(shù)據(jù)返回。
     */
    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型數(shù)據(jù),原理是先通過readInt方法讀取一個int型數(shù)據(jù),然后再將其轉(zhuǎn)換成float行數(shù)據(jù)并返回。
     */
    public final float readFloat() throws IOException {
        return Float.intBitsToFloat(readInt());
    }
 
    /**
     * 每次讀取一個double型數(shù)據(jù),原理是先通過readLong方法讀取一個long型數(shù)據(jù),然后再將其轉(zhuǎn)換成double型數(shù)據(jù)并返回。
     */
    public final double readDouble() throws IOException {
        return Double.longBitsToDouble(readLong());
    }
    
    //定義了一個char型數(shù)組,用于readLine中存儲讀取的數(shù)據(jù)。
    private char lineBuffer[];
 
    /**
     * 一次讀取一行數(shù)據(jù),最終將讀取的數(shù)據(jù)轉(zhuǎn)換為一個String類型并返回。
     */
    @Deprecated
    public final String readLine() throws IOException {
    //又定義了一個char類型數(shù)組buf,作為臨時(shí)存儲數(shù)據(jù)的緩存。
        char buf[] = lineBuffer;
        //當(dāng)?shù)谝淮握{(diào)用該方法時(shí),buf和lineBuffer都為null,所以對它們進(jìn)行初始化,初始化容量為128,這里指的注意的是兩個數(shù)組句柄指向了同一個數(shù)組對象。
        if (buf == null) {
            buf = lineBuffer = new char[128];
        }
    //聲明了3個int型變量,room為buf的容量大小,offset記錄了當(dāng)前讀取的位置,c用來記錄每次讀取的數(shù)據(jù)。
        int room = buf.length;
        int offset = 0;
        int c;
 
    //通過一個死循環(huán)來不斷調(diào)用read方法讀取數(shù)據(jù),在循環(huán)外圍留下了標(biāo)記loop,方便在隨后的switch塊中直接跳出最外圍循環(huán)。
loop:   while (true) {
        //通過一個switch塊來判斷每次讀取的內(nèi)容,根據(jù)不同的內(nèi)容來決定不同的操作,主要用于判斷是否遇到換行符。
            switch (c = in.read()) {
              case -1:
              case '\n':
        //當(dāng)讀取的內(nèi)容為'\n'或-1時(shí),都直接跳出循環(huán),-1表示已經(jīng)沒有數(shù)據(jù)可讀,'\n'表示讀到換行符。
                break loop;
              case '\r':
        //當(dāng)讀取的內(nèi)容為'\r'時(shí),因?yàn)椴僮飨到y(tǒng)原因,換行符除了'\n'外還可能是'\r\n',所以我們需要繼續(xù)往后讀一個字節(jié)內(nèi)容,看后一個字節(jié)的內(nèi)容是否是
        //'\n',如果是則表示讀取到換行符,跳出循環(huán),如果不是且讀取的數(shù)據(jù)不為-1(即讀取到了新數(shù)據(jù)),那么通過PushbackInputStream流將第二次讀取的數(shù)
        //回推回去,然后跳出循環(huán)。
                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:
        //當(dāng)文件讀取沒有結(jié)束且沒有讀取到換行符時(shí),執(zhí)行此處代碼。
                if (--room < 0) {
            //--room小于零時(shí),則表示下一次讀取時(shí),存放數(shù)據(jù)的緩存數(shù)組容量已經(jīng)不足,所以此時(shí)需要對其擴(kuò)容(增加128字節(jié)的容量),將臨時(shí)緩存buf擴(kuò)容后,
            //重新為room值賦值,并將原緩存中的數(shù)據(jù)拷貝到擴(kuò)容后的新緩存數(shù)組中,最后將lineBuffer也指向擴(kuò)容后的數(shù)組,丟棄吊原有的緩存數(shù)組。
                    buf = new char[offset + 128];
                    room = buf.length - offset - 1;
                    System.arraycopy(lineBuffer, 0, buf, 0, offset);
                    lineBuffer = buf;
                }
        //每次讀取的數(shù)據(jù)都根據(jù)offset位置,依次存放至緩存數(shù)組中去。
                buf[offset++] = (char) c;
                break;
            }
        }
    //當(dāng)沒有讀取到任何數(shù)據(jù)時(shí),直接返回null。
        if ((c == -1) && (offset == 0)) {
            return null;
        }
    //將緩存中的數(shù)據(jù)轉(zhuǎn)化為String類型數(shù)據(jù)并返回。
        return String.copyValueOf(buf, 0, offset);
    }
 
    /**
     * 從流中以UTF編碼格式讀取數(shù)據(jù),并以String類型返回。實(shí)際調(diào)用的是之后帶參的readUTF的方法。
     */
    public final String readUTF() throws IOException {
        return readUTF(this);
    }
 
    /**
     * 從流中已UTF編碼格式讀取數(shù)據(jù),傳入的參數(shù)為一個DataInput對象,用于對流進(jìn)行讀取操作。
     */
    public final static String readUTF(DataInput in) throws IOException {
    //根據(jù)DataOutputStream中writeUTF方法規(guī)定,前兩個字節(jié)中存放的是數(shù)據(jù)的總長度,所以通過readUnsignedShort方法可以將其讀取出來并賦值給聲明的int型變量
    //utflen。
        int utflen = in.readUnsignedShort();
    //定義了兩個數(shù)組,一個byte型數(shù)組,一個char型數(shù)組,前者用于存放流中所有的數(shù)據(jù),后者則是存放經(jīng)過處理后的數(shù)據(jù)。
        byte[] bytearr = null;
        char[] chararr = null;
    
    //為兩個緩存數(shù)組進(jìn)行初始化,先判斷傳入的DataInput對象是否屬于DataInputStream及其衍生類,如果是就直接使用內(nèi)置的兩個緩存數(shù)組,并且檢測數(shù)組容量是否
    //滿足數(shù)據(jù)長度,如果不滿足則自動將其容量擴(kuò)大2倍,如果不屬于DataInputStream,則根據(jù)數(shù)據(jù)長度新建兩個數(shù)組對象。
        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表示每次讀取的數(shù)據(jù),因?yàn)橐粋€utf8格式的數(shù)據(jù)最多可以有4個字節(jié),所以在數(shù)據(jù)拼接時(shí)最多用到兩個char型數(shù)據(jù),char2,char3就是用于
    //數(shù)據(jù)拼接。count,和chararr_count則分別表示了兩個緩存區(qū)中的讀取的數(shù)據(jù)位置。
        int c, char2, char3;
        int count = 0;
        int chararr_count=0;
 
    //通過readFully方法,將所有的數(shù)據(jù)都直接存儲到byte型數(shù)組bytearr中。
        in.readFully(bytearr, 0, utflen);
 
    //這里其實(shí)可以當(dāng)做對所有的數(shù)據(jù)進(jìn)行的一個預(yù)處理,通過該方法可以將數(shù)據(jù)中單字節(jié)的數(shù)據(jù)存儲到char型數(shù)組中去,如果遇到不是單字節(jié)數(shù)組的則直接跳出循環(huán)。
        while (count < utflen) {
        //前面經(jīng)過readFully操作,bytearr中存放了所有的數(shù)據(jù),所以此時(shí)每次從中取出一個字節(jié)的數(shù)據(jù),通過與Oxff相與轉(zhuǎn)化成int型數(shù)據(jù)(1與任何一個數(shù)相與都是
        //其本身)。
            c = (int) bytearr[count] & 0xff;
        //因?yàn)閡tf8單字節(jié)的表示范圍是0~127,所以如果c>127則直接退出。
            if (c > 127) break;
        //正常情況下則是將讀取的數(shù)據(jù)存放進(jìn)char型數(shù)組中,兩個索引值自動加1。
            count++;
            chararr[chararr_count++]=(char)c;
        }
 
    //下面則是對數(shù)據(jù)進(jìn)行正式處理,通過一個循環(huán)遍歷byte數(shù)組bytearr中的數(shù)據(jù),進(jìn)行匹配后進(jìn)行不同的操作,然后將拼接的數(shù)據(jù)方法char型數(shù)組chararr中。
        while (count < utflen) {
        //每次從bytearr中取出一個字節(jié)的數(shù)據(jù),通過與0xff相與轉(zhuǎn)化成int型數(shù)據(jù)賦值給c。
            c = (int) bytearr[count] & 0xff;
        //下面將c右移4位,然后用一個switch塊將其包裹,來對其進(jìn)行匹配。這里要說一下UTF8的編碼規(guī)則了,如果只有一個字節(jié)則其最高二進(jìn)制位為0;如果是多字
        //節(jié),其第一個字節(jié)從最高位開始,連續(xù)的二進(jìn)制位值為1的個數(shù)決定了其編碼的字節(jié)數(shù),其余各字節(jié)均以10開頭,后面會附上utf編碼格式圖。下面的操作實(shí)際
        //上只取utf8數(shù)據(jù)的高八位,右移四位后,可以通過其值來判斷字節(jié)數(shù)(1的個數(shù))。
            switch (c >> 4) {
                case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
                    // 0xxxxxxx,該種情況下表示是單字節(jié)數(shù)據(jù),可以直接將其轉(zhuǎn)成char型數(shù)據(jù)存放入chararr數(shù)組中去。
                    count++;
                    chararr[chararr_count++]=(char)c;
                    break;
                case 12: case 13:
                    // 110xxxxx 10xxxxxx,該種情況下是雙字節(jié)數(shù)據(jù),需要從byte型數(shù)組中取出兩個字節(jié)數(shù)據(jù),所以先將讀取索引count+2。然后進(jìn)行安全檢測,讀取位置
                    // 不能超過數(shù)據(jù)總長度。
                    count += 2;
                    if (count > utflen)
                        throw new UTFDataFormatException(
                            "malformed input: partial character at end");
            //這里c是兩字節(jié)的高八位,所以還需向后讀取一字節(jié)數(shù)據(jù),賦值給char2。
                    char2 = (int) bytearr[count-1];
            //根據(jù)utf8的編碼規(guī)則可知,雙字節(jié)的后八位必須滿足10xxxxxx,所以將其與0xCO相與,如果滿足,運(yùn)算結(jié)果必為0x80,所以如果不等于,則拋出異常。
                    if ((char2 & 0xC0) != 0x80)
                        throw new UTFDataFormatException(
                            "malformed input around byte " + count);
            //這里就是將讀取的兩個字節(jié)的數(shù)據(jù)拼裝成一個char型數(shù)據(jù)了,以雙字節(jié)詳細(xì)說明:
            //step1(c & 0x1F):c讀取的其實(shí)是雙字節(jié)數(shù)據(jù)的高八位,因?yàn)閡tf8編碼格式,雙字節(jié)高八位的數(shù)據(jù)編碼格式為110xxxxx,當(dāng)與0x1F相與后可以將前
            //3位的標(biāo)志位置0,只保留數(shù)據(jù)位,然后左移6位作為char型數(shù)據(jù)的高位,至于為什么是移動6位,是因?yàn)殡p字節(jié)utf8編碼的表示范圍是128~2047,所以
            //數(shù)據(jù)位使用12位就足夠表示,因此在DataOutputStream中的writeUTF中就以6位為一個移動的步數(shù)。
            //step2(char2 & 0x3F):char2為讀取的雙字節(jié)數(shù)據(jù)的低八位,因?yàn)閡tf8編碼格式,雙字節(jié)低八位的數(shù)據(jù)編碼格式為10xxxxxx,當(dāng)與0x3F相與后將高兩位
            //的標(biāo)志位置0,只保留數(shù)據(jù)位。到此一個char型數(shù)據(jù)的所有數(shù)據(jù)位都得到了。
            //step3(將兩者或):通過一個或操作將上兩步得到的數(shù)據(jù)位拼接起來得到一個char型數(shù)據(jù)存儲到字符數(shù)組chararr中。
                    chararr[chararr_count++]=(char)(((c & 0x1F) << 6) |
                                                    (char2 & 0x3F));
                    break;
                case 14:
                    //1110 xxxx  10xx xxxx  10xx xxxx ,此種情況下表示是3字節(jié)數(shù)據(jù),需從bytearr數(shù)組中取出3個字節(jié)的數(shù)據(jù)進(jìn)行拼裝處理,因?yàn)椴襟E與上面雙字節(jié)數(shù)據(jù)
            //類似所以在此就不多敘述了,原理是一樣的。
                    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 剩余的就是這兩者情況了,剛開始在這里糾結(jié)了好久,因?yàn)闃?biāo)準(zhǔn)utf8是可以占有1~4個字節(jié)的,然而這里并沒有對占用4字節(jié)
            // 的情況進(jìn)行處理,但是后來通過在Stack Overflow找到了答案,其實(shí)源碼的JavaDocs中已經(jīng)有了解釋,在這里使用的是modified UTF-8而不是標(biāo)準(zhǔn)的
            // utf8,所以只會占用1~3個字節(jié),在這里不得不再次感嘆一下,Stack Overflow確實(shí)很強(qiáng)大。
                    throw new UTFDataFormatException(
                        "malformed input around byte " + count);
            }
        }
        // 最終將chararr中的數(shù)據(jù)轉(zhuǎn)換成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,該變量用于記錄實(shí)際寫入數(shù)據(jù)的字節(jié)數(shù),當(dāng)寫入的數(shù)據(jù)超過int型數(shù)據(jù)的表達(dá)范圍時(shí),該值將被賦予Integer.MAX_VALUE.
    protected int written;
 
    //聲明了一個byte型數(shù)組的句柄,該數(shù)組在之后的writeUTF中充當(dāng)著數(shù)據(jù)緩存的作用。
    private byte[] bytearr = null;
 
    /**
     * 一個帶一個參數(shù)的構(gòu)造方法,傳入的參數(shù)是一個OutputStream對象,內(nèi)部調(diào)用FilterOutputStream對象的構(gòu)造參數(shù)。
     */
    public DataOutputStream(OutputStream out) {
        super(out);
    }
 
    /**
     * 該方法用于記錄在流中吸入了多少字節(jié)的數(shù)據(jù),如果長度超過了int型表達(dá)范圍,則該值一直為Integer.MAX_VALUE。
     */
    private void incCount(int value) {
        int temp = written + value;
    //這里的temp<0的情況其實(shí)就是int型變量數(shù)據(jù)溢出了。
        if (temp < 0) {
            temp = Integer.MAX_VALUE;
        }
        written = temp;
    }
 
    /**
     * 該方法每次向?qū)懭胍粋€字節(jié)的數(shù)據(jù)。
     */
    public synchronized void write(int b) throws IOException {
        out.write(b);
    //每寫入一個字節(jié)數(shù)據(jù)后,調(diào)用incCount方法,使得計(jì)數(shù)加1。
        incCount(1);
    }
 
    /**
     * 該方法每次寫入多個字節(jié)的數(shù)據(jù),含有3個參數(shù),第一個參數(shù)為一個byte型數(shù)組, 作為要寫入流中的數(shù)據(jù)源,后兩個參數(shù)都為int型數(shù)據(jù),第一個表示開始寫入的位置,
     * 第二個為寫入的長度,成功往流中寫入數(shù)據(jù)后調(diào)用incCount方法,記錄下寫入數(shù)據(jù)的長度。
     */
    public synchronized void write(byte b[], int off, int len)
        throws IOException
    {
        out.write(b, off, len);
        incCount(len);
    }
 
    /**
     * 該方法用于將緩存區(qū)中的數(shù)據(jù)寫入流中。
     */
    public void flush() throws IOException {
        out.flush();
    }
 
    /**
     * 每次向流中寫入一個boolean型數(shù)據(jù),本質(zhì)其實(shí)是根據(jù)java中默認(rèn)的非零為true,0為false,向流中寫入一個數(shù)值,讀取時(shí)根據(jù)這個規(guī)則再還原成對應(yīng)的boolean型值。
     */
    public final void writeBoolean(boolean v) throws IOException {
        out.write(v ? 1 : 0);
        incCount(1);
    }
 
    /**
     * 每次向流中寫入一個byte型數(shù)據(jù)。
     */
    public final void writeByte(int v) throws IOException {
        out.write(v);
        incCount(1);
    }
 
    /**
     * 每次向流中寫入一個short型數(shù)據(jù),實(shí)質(zhì)是經(jīng)過兩次寫入操作,先寫入short型數(shù)據(jù)的高八位,再寫入低八位。
     */
    public final void writeShort(int v) throws IOException {
        out.write((v >>> 8) & 0xFF);
        out.write((v >>> 0) & 0xFF);
        incCount(2);
    }
 
    /**
     * 原理同上,每次向流中寫入一個char型數(shù)據(jù),實(shí)質(zhì)是經(jīng)過兩次寫入,先入char型數(shù)據(jù)的高八位,在寫入其低八位。
     */
    public final void writeChar(int v) throws IOException {
        out.write((v >>> 8) & 0xFF);
        out.write((v >>> 0) & 0xFF);
        incCount(2);
    }
 
    /**
     * 每次向流中寫入一個int型數(shù)據(jù),實(shí)質(zhì)是經(jīng)過4次寫入,依次從最高位開始,每八位為一組,向流中寫入數(shù)據(jù)。
     */
    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型數(shù)組,其容量為8個字節(jié),用于后面的writeLong方法中作為數(shù)據(jù)緩存使用。
    private byte writeBuffer[] = new byte[8];
 
    /**
     * 一次向流中寫入一個long型數(shù)據(jù)。將long型數(shù)據(jù)從最高位開始以8位為一組,依次寫入緩存數(shù)組中,最后通過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型數(shù)據(jù),本質(zhì)是將float型數(shù)據(jù)轉(zhuǎn)化成int型數(shù)據(jù),然后通過writeInt方法寫入流中。
     */
    public final void writeFloat(float v) throws IOException {
        writeInt(Float.floatToIntBits(v));
    }
 
    /**
     * 一次向流中寫入一個double型數(shù)據(jù),本質(zhì)是將double型數(shù)據(jù)轉(zhuǎn)化成long型數(shù)據(jù),然后通過writeLong方法寫入流中。
     */
    public final void writeDouble(double v) throws IOException {
        writeLong(Double.doubleToLongBits(v));
    }
 
    /**
     * 一次向流中以byte的形式寫入一個字符串類型數(shù)據(jù),本質(zhì)是通過字符串的charAt方法,將字符串中的每個字符轉(zhuǎn)化成byte型數(shù)據(jù)寫入流中。
     */
    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的形式寫入一個字符串類型數(shù)據(jù),本質(zhì)是通過字符串的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類型的數(shù)據(jù)。本質(zhì)是調(diào)用之后帶兩個參數(shù)的writeUTF方法。
     */
    public final void writeUTF(String str) throws IOException {
        writeUTF(str, this);
    }
 
    /**
     * 一次向流中以utf8的格式寫入一個String類型的數(shù)據(jù)。
     */
    static int writeUTF(String str, DataOutput out) throws IOException {
    //定義了四個int型變量,strlen記錄了要寫入String類型數(shù)據(jù)的總長度,utflen記錄了以utf8格式進(jìn)行編碼后所占用的字節(jié)數(shù),c用于記錄str中每個字符的數(shù)據(jù),
    //count則表示了str中已經(jīng)寫入流中的字符數(shù)。
        int strlen = str.length();
        int utflen = 0;
        int c, count = 0;
 
        //這里使用一個循環(huán)來記錄以utf8格式來將數(shù)據(jù)寫入流中所需要的字節(jié)數(shù)。這里講究的原則是,單字節(jié)utf8數(shù)據(jù)表達(dá)的范圍是0~127,utf8雙字節(jié)表達(dá)范圍為128~
    //2047,三字節(jié)為~65535。因此根據(jù)取出字符的數(shù)值可以得知相應(yīng)字符轉(zhuǎn)換成utf8格式所需要的字節(jié)數(shù),然后將utflen累加上相應(yīng)的字節(jié)數(shù),最終得到總共需要的utf
    //8格式下的字節(jié)數(shù)。
        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類型數(shù)據(jù)的總長度,在轉(zhuǎn)化為utf8格式后,其總長度不要超過65535,否則將會拋出相應(yīng)異常。
        if (utflen > 65535)
            throw new UTFDataFormatException(
                "encoded string too long: " + utflen + " bytes");
    //定義了一個字節(jié)數(shù)組,用于充當(dāng)向流中寫入數(shù)據(jù)時(shí)的臨時(shí)緩存。
        byte[] bytearr = null;
    //先判斷傳入的out對象是否是DataOuputStream類或者其子類,如果是,就使用其內(nèi)部定義的bytearr數(shù)組,否則就新建一個byte數(shù)組賦值給bytearr。值得注意的是
    //如果使用內(nèi)置的數(shù)組,會先對內(nèi)置數(shù)組進(jìn)行檢測,檢測其是否為null和容量是否滿足需求,如果不滿足也要重新創(chuàng)建。
        if (out instanceof DataOutputStream) {
            DataOutputStream dos = (DataOutputStream)out;
            if(dos.bytearr == null || (dos.bytearr.length < (utflen+2)))
        //這里就是內(nèi)置數(shù)組為null或者容量不夠時(shí),重新創(chuàng)建數(shù)組對象,新建的容量會根據(jù)utflen來進(jìn)行擴(kuò)容,這里注意到所有的數(shù)組容量中都有+2的操作,這是
        //除了要寫入數(shù)據(jù)外,該方法還會在開頭出寫入兩個字節(jié)的數(shù)據(jù),這兩個字節(jié)的數(shù)據(jù)記錄了流中數(shù)據(jù)的總長度。
                dos.bytearr = new byte[(utflen*2) + 2];
            bytearr = dos.bytearr;
        } else {
            bytearr = new byte[utflen+2];
        }
 
    //在開端寫入兩個字節(jié)的數(shù)據(jù),該數(shù)據(jù)為utflen,記錄了以utf8格式要寫入數(shù)據(jù)的總的字節(jié)數(shù)。因?yàn)榍懊嫦拗屏丝傞L度不能高于65535,所以這里只需兩個字節(jié)就可以
    //記錄下總長度。
        bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
        bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF);
    
    //下面通過一個循環(huán),先對數(shù)據(jù)進(jìn)行預(yù)處理,如果是utf8中的單字節(jié)數(shù)據(jù),則直接裝換成byte型寫入數(shù)據(jù)緩存中。
        int i=0;
        for (i=0; i<strlen; i++) {
           c = str.charAt(i);
           if (!((c >= 0x0001) && (c <= 0x007F))) break;
           bytearr[count++] = (byte) c;
        }
 
    //下面是正式處理要寫入的數(shù)據(jù),根據(jù)其數(shù)值范圍,確定其在utf8格式下需要幾個字節(jié)來表示,然后根據(jù)相應(yīng)字節(jié)數(shù)通過對應(yīng)的移位操作,將其以8位為一組,從最高
    //開始,依次寫入數(shù)據(jù)緩存區(qū)中。
        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格式寫入流中的字節(jié)數(shù)。
        return utflen + 2;
    }
 
    /**
     * 返回當(dāng)前寫入流中數(shù)據(jù)的字節(jié)總數(shù)。
     */
    public final int size() {
        return written;
    }
}

為了方便理解,這里附上一幅utf8編碼的一些小格式:


UTF編碼

通過以上對源碼的簡要描述,相信我們對DataInputStream/DataOutputStream有了初步的認(rèn)識,下面我們通過一個簡單的小例子來展示這兩個類的具體用法:

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();
        }
    }
}

以上為本篇的全部內(nèi)容。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容