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編碼的一些小格式:
最後附上一個小例子:
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() ;
}
}