一.概念
FileInputStream和FileOutputStream 是一對繼承與InputStream和OutputStream的類,用于本地檔案讀寫,按二進制格式讀寫并且順序讀寫,讀和寫的檔案流要區分開,即分别建立不同檔案流對象。
二.記住in和out
死記硬背型:
不管你從磁盤讀,從網絡讀,或者從鍵盤讀,讀到記憶體,就是InputStream。
不管你寫入磁盤,寫入網絡,或者寫到螢幕,都是OuputStream。
了解型:
有些人經常遇到InputStream、OuputStream,不知道哪是寫出,哪個是寫入,其實我們隻要記住以記憶體為中心以及流的流向,就能很容易記住。
來個場景讓你了解下:
如果我們往硬碟上寫檔案,用in還是out?從字面看,寫檔案啊,存儲啊,必須是in啊,但是你沒注意到資料流方向是從 記憶體---->硬碟,前面說了,以記憶體為中心,這是一種寫出資料,對應的out,相反從硬碟讀資料,資料流方向是從硬碟---->記憶體,是以得用in。
三.類構造解析:
FileInputStream和FileOutputStream作為檔案流輸入\輸出對象,顧名思義,必然離不開File對象(不懂的參照上一篇)。
①FileInputStream
首先看一下 FileInputStream的構造函數:
FileInputStream(String name);
FileInputStream(File file);
FileInputStream(FileDescriptor fdObj);
從上面可以看出可以分别為File對象、字元串、FileDescriptor對象為參數構造FileInputStream對象。
然後看一下FileInputStream的主要方法:
read();
read(byte b[]);
read(byte b[], int off, int len);
close();
官方API解釋:
1.read() : 從輸入流中讀取資料的下一個位元組,傳回0到255範圍内的int位元組值。如果因為已經到達流末尾而沒有可用的位元組,則傳回-1。在輸入資料可用、檢測到流末尾或者抛出異常前,此方法一直阻塞。
2.read(byte[] b) : 從輸入流中讀取一定數量的位元組,并将其存儲在緩沖區數組 b 中。以整數形式傳回實際讀取的位元組數。在輸入資料可用、檢測到檔案末尾或者抛出異常前,此方法一直阻塞。
如果 b 的長度為 0,則不讀取任何位元組并傳回 0;否則,嘗試讀取至少一個位元組。如果因為流位于檔案末尾而沒有可用的位元組,則傳回值 -1;否則,至少讀取一個位元組并将其存儲在 b 中。
将讀取的第一個位元組存儲在元素 b[0] 中,下一個存儲在 b[1] 中,依次類推。讀取的位元組數最多等于b 的長度。設 k 為實際讀取的位元組數;這些位元組将存儲在 b[0] 到 b[k-1] 的元素中,不影響 b[k] 到b[b.length-1] 的元素。
3.read(byte[] b,int off,int len):将輸入流中最多 len 個資料位元組讀入位元組數組。嘗試讀取多達 len 位元組,但可能讀取較少數量。以整數形式傳回實際讀取的位元組數。在輸入資料可用、檢測到流的末尾或者抛出異常前,此方法一直阻塞。
如果
b
為
null
,則抛出
NullPointerException
。
off
為負,或
len
off+len
大于數組
b
的長度,則抛出
IndexOutOfBoundsException
len
為 0,則沒有位元組可讀且傳回
;否則,要嘗試讀取至少一個位元組。如果因為流位于檔案末尾而沒有可用的位元組,則傳回值
-1
;否則,至少可以讀取一個位元組并将其存儲在
b
中。
将讀取的第一個位元組存儲在元素
b[off]
中,下一個存儲在
b[off+1]
中,依次類推。讀取的位元組數最多等于
len
。讓 k 為實際讀取的位元組數;這些位元組将存儲在元素
b[off]
至
b[off+
k
-1]
之間,其餘元素
b[off+
]
至
b[off+len-1]
不受影響。
在任何情況下,元素
b[0]
b[off]
和元素
b[off+len]
b[b.length-1]
都不會受到影響。
如果不是因為流位于檔案末尾而無法讀取第一個位元組,則抛出
IOException
。特别是,如果輸入流已關閉,則抛出
IOException
類
InputStream
的
read(b,
off,
len)
方法隻重複調用方法
read()
。如果第一個這樣的調用導緻
IOException
,則從對
read(b,
off,
len)
方法的調用中傳回該異常。如果對
read()
的任何後續調用導緻
IOException
,則該異常會被捕獲并将發生異常時的位置視為檔案的末尾;到達該點時讀取的位元組存儲在
b
中并傳回發生異常之前讀取的位元組數。建議讓子類提供此方法的更有效的實作。
以上是3種read方法的詳細介紹,下面我們來用代碼解釋一下:
㈠read()的用法
建立檔案test.txt,裡面我們隻放入一個1,然後我們用FileinputStream讀取。
public static void main(String[] args) {
try {
File file=new File("D:/test.txt");
FileInputStream input = new FileInputStream(file);
for(int i=1;i<=3;i++){
System.out.println(input.read());
}
input.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
--------------列印結果
49
-1
-1
我們來解讀一下列印結果:
- read()方法是每調用一次,就從FileInputStream中讀取一個位元組。
- ANSI編碼中,1隻占用一個位元組并且1的Ascii碼為49。
-
傳回下一個資料位元組,如果已達到檔案末尾,傳回-1。
我們來畫個圖來解釋下read讀資料時的移動原理。
Java IO流(第三講):位元組流中的FileInputStream與FileoutputStream 注意:如果檔案的編碼方式不同,則上面列印結果會存在不同,因為不同的檔案類型可能存在檔案頭
附:文本檔案各格式檔案頭:ANSI類型:什麼都沒有,UTF-8類型:EF BB BF,UNICODE類型:FF FE,UNICODE BIG ENDIAN類型:FE FF
㈡read(byte[])的用法
建立檔案test.txt,裡面我們隻放入1234567890,然後我們用FileinputStream讀取。
public static void main(String[] args) {
try {
File file=new File("D:/test.txt");
byte[] b=new byte[3];
FileInputStream input = new FileInputStream(file);
for(int i=1;i<=5;i++){
System.out.println(input.read(b)+"---------"+Arrays.toString(b));
}
input.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
--------------列印結果
3---------[49, 50, 51]
3---------[52, 53, 54]
3---------[55, 56, 57]
1---------[48, 56, 57]
-1---------[48, 56, 57]
從列印結果進行分析:
- read(byte[])方法是每次将b.length個位元組的資料讀入一個byte資料組中。
- 傳回讀入緩沖的位元組總數,如果因為已經到達檔案末尾而沒有更多的資料,則傳回-1。
這裡可能有一些同學會有疑問,第四次調用read(byte[])時,已經達到尾部,這時候應該是傳回-1,這樣不就導緻最後一個數字讀取不了嗎,這個疑問讓我們來看一下源碼:
public int read(byte b[]) throws IOException {
Object traceContext = IoTrace.fileReadBegin(path);
int bytesRead = 0;
try {
bytesRead = readBytes(b, 0, b.length);//調用下面方法
} finally {
IoTrace.fileReadEnd(traceContext, bytesRead == -1 ? 0 : bytesRead);
}
return bytesRead;
}
-------------------------分割-----------------
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
<strong>int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}</strong>
return i;
}
從上代碼可以看出,如果在某次讀取時,指針開始不是指向檔案末尾,則會去執行循環,如果執行循環的過程中指針到達末尾,則直接跳出循環,傳回byte數組。
㈢read(byte[] b,int off,int len)的用法
見上面代碼。
注意點:
因為read()每次執行都讀取一個位元組,中文經常在檔案中占用兩個位元組,是以在調用read()方法的次數最好是偶數次,同理read(byte[])中數組的長度為偶數。
②FileOutputStream
FileOutputStream是從記憶體将資料讀入檔案的對象。
首先看一下構造函數
FileOutputStream(String name);
FileOutputStream(String name, boolean append);
FileOutputStream(File file);
FileOutputStream(File file, boolean append);
從源碼可以看出所有構造函數最後都是FileOutputStream(File file, boolean append);這個構造函數,boolean append這個參數決定你是否将讀出的内容追加到目标檔案的末尾,沒這個參數的構造器預設是false。
其次來看一看方法
write(int b);
write(byte b[]);
write(byte b[], int off, int len)
這三種方法主要是配合FileInputStream使用,這裡我不做詳細介紹了
附上執行個體:
public static void main(String[] args) {
try {
File file=new File("D:/test.txt");
File file1=new File("D:/test1.txt");
byte[] b=new byte[1024];
FileInputStream input = new FileInputStream(file);
FileOutputStream out = new FileOutputStream(file1,true);
int i=0;
long startTime=System.currentTimeMillis();
while((i=input.read(b))>0){
out.write(b);
}
long endTime=System.currentTimeMillis();
System.out.println("共消耗"+(endTime-startTime));
input.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}