Java-IO流篇-DataOutputStream

DataOutputStream

DataOutputStreams是OutputStream的子類。
是數據輸出流,此類繼承自FillterOutputStream類,同時實現DataOutput接口。在DataOutput接口定義了一系列寫入各種數據的方法。
從類 java.io.FilterOutputStream 繼承的方法有:close,write
從接口 java.io.DataOutput 繼承的方法有:write
字節處理流,提供了多個很常用的方法。
是對普通流的功能的一個擴展,可以更加方便地讀取int,long,字符等類型數據 。

數據流: DataInputStream 和 DataOutputStream
  • 1). DataInputStream 數據的字節輸入流; DataOutputStream 數據的字節輸出流。
  • 2). 功能: 實現八種基本類型數據的輸入/輸出。 同時,也可實現字符串的輸入/輸出。
  • 3). 特點: 八種基本類型的數據在輸入/輸出時,會保持類型不變。
  • 因此,這種流特別適合在網絡上實現基本類型數據和字符串的傳遞。
  • 4). 方法:
  • readByte() writeByte()
  • readShort() writeShort();
  • readInt() writeInt();
  • readLong() writeLong();
  • readFloat() writeFloat();
  • readDouble() writeDouble();
  • readBoolean() writeBoolean();
  • readChar() writeChar();
  • readUTF(); writeUTF();
  • 5). 數據流屬於處理流,它必須套接在節點流。
  • 6). 注意: 數據流在讀取與存儲時的順序要一致。否則,讀取數據會失真。

對DataOutputStream源碼的分析

package java.io;
public class DataOutputStream extends FilterOutputStream implements DataOutput {
     //字節數,該變量用於記錄實際寫入數據的字節數,當寫入的數據超過int型數據的表達範圍時,該值將被賦予Integer.MAX_VALUE
    protected int written;
    
     //對應的字節數組
    private byte[] bytearr = null;

     //構造函數:一個帶一個參數的構造方法,傳入的參數是一個OutputStream對象,內部調用FilterOutputStream對象的構造參數。
    public DataOutputStream(OutputStream out) {
        super(out);
    }

     //增加written
    private void incCount(int value) {
        int temp = written + value;
        if (temp < 0) {//這裏的temp<0的情況其實就是int型變量數據溢出了。
            temp = Integer.MAX_VALUE;
        }
        written = temp;
    }

     // 將int類型的值寫入到“數據輸出流”中
    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();
    }

    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佔2個字節
    public final void writeShort(int v) throws IOException {
        out.write((v >>> 8) & 0xFF);// 寫入 short高8位 對應的字節
        out.write((v >>> 0) & 0xFF); // 寫入 short低8位 對應的字節
        incCount(2);
    }

      // 注意:char佔2個字節
    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);
    }

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

      // 注意:long佔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));
    }

      // 將String類型的值寫入到“數據輸出流”中,實際寫入時,是將String對應的每個字符轉換成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);
    }

     // 將String類型的值寫入到“數據輸出流”中, 實際寫入時,是將String對應的每個字符轉換成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);
    }

     // 將UTF-8類型的值寫入到“數據輸出流”中
    public final void writeUTF(String str) throws IOException {
        writeUTF(str, this);
    }

     // 將String數據以UTF-8類型的形式寫入到“輸出流out”中
    static int writeUTF(String str, DataOutput out) throws IOException {
        int strlen = str.length();//獲取String的長度
        int utflen = 0;
        int c, count = 0;

		// 由於UTF-8是1~4個字節不等;這裏,根據UTF-8首字節的範圍,判斷UTF-8是幾個字節的。
        /* 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)//(65535)10=(1111111111111111)2
            throw new UTFDataFormatException(
                "encoded string too long: " + utflen + " bytes");

        byte[] bytearr = null;// 新建“字節數組bytearr”
        //定義了一個字節數組,用於充當向流中寫入數據時的臨時緩存。

		//先判斷傳入的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];
        }
		
		// “字節數組”的前2個字節保存的是“UTF-8數據的長度”
        bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
        bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF);

		// 對UTF-8中的單字節數據進行預處理,如果是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;
        }
		
		// 對預處理後的數據,接着進行處理
        for (;i < strlen; i++){
            c = str.charAt(i);
			 // UTF-8數據是1個字節的情況
            if ((c >= 0x0001) && (c <= 0x007F)) {
                bytearr[count++] = (byte) c;

            } else if (c > 0x07FF) { // UTF-8數據是3個字節的情況
                bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
                bytearr[count++] = (byte) (0x80 | ((c >>  6) & 0x3F));
                bytearr[count++] = (byte) (0x80 | ((c >>  0) & 0x3F));
            } else { // UTF-8數據是2個字節的情況
                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編碼的一些小格式:
UTF-8

最後附上一個小例子:
import java.io.*;

/**
 * @Author: lishi
 * @Description:
 * @Date: Create in 18:40 2019/3/25
 */
public class DataOutputStream0325 {
    public static void main(String args[]) throws Exception{    // 所有異常拋出
        DataOutputStream dos = null ;            // 聲明數據輸出流對象
        File f = new File("C:\\Users\\lishi\\Desktop\\test.txt") ; // 文件的保存路徑
        dos = new DataOutputStream(new FileOutputStream(f)) ;    // 實例化數據輸出流對象
        String names[] = {"襯衣","手套","圍巾"} ;    // 商品名稱
        float prices[] = {98.3f,30.3f,50.5f} ;        // 商品價格
        int nums[] = {3,2,1} ;    // 商品數量
        for(int i=0;i<names.length;i++){    // 循環輸出
            dos.writeChars(names[i]) ;    // 寫入字符串,注意,這邊少數writeChars(),不是writechar()。
            dos.writeChar('\t') ;    // 寫入分隔符,這邊是讀取writechar()。
            dos.writeFloat(prices[i]) ; // 寫入價格
            dos.writeChar('\t') ;    // 寫入分隔符
            dos.writeInt(nums[i]) ; // 寫入數量
            dos.writeChar('\n') ;    // 換行
        }
        dos.close() ;    // 關閉輸出流


        DataInputStream dis = null ;        // 聲明數據輸入流對象
  //      File f = new File("d:" + File.separator + "order.txt") ; // 文件的保存路徑
        dis = new DataInputStream(new FileInputStream(f)) ;    // 實例化數據輸入流對象
        String name = null ;    // 接收名稱
        float price = 0.0f ;    // 接收價格
        int num = 0 ;    // 接收數量
        char temp[] = null ;    // 接收商品名稱
        int len = 0 ;    // 保存讀取數據的個數
        char c = 0 ;    // '\u0000'
        try{
            while(true){
                temp = new char[200] ;    // 開闢空間
                len = 0 ;
                while((c=dis.readChar())!='\t'){    // 接收內容,因爲直到讀取到'\t'才完成了讀取一個字符串,未讀取到表示還有內容。
                    temp[len] = c ;
                    len ++ ;    // 讀取長度加1
                }
                name = new String(temp,0,len) ;    // 將字符數組變爲String
                price = dis.readFloat() ;    // 讀取價格
                dis.readChar() ;    // 讀取\t
                num = dis.readInt() ;    // 讀取int
                dis.readChar() ;    // 讀取\n
                System.out.printf("名稱:%s;價格:%5.2f;數量:%d\n",name,price,num) ;
            }
        }catch(Exception e){}
        dis.close() ;
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章