(最近剛來到簡書平臺,以前在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)容。