天天看點

Java IO流(第三講):位元組流中的FileInputStream與FileoutputStream

一.概念 

     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      

我們來解讀一下列印結果:

  1. read()方法是每調用一次,就從FileInputStream中讀取一個位元組。
  2. ANSI編碼中,1隻占用一個位元組并且1的Ascii碼為49。
  3. 傳回下一個資料位元組,如果已達到檔案末尾,傳回-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]      

從列印結果進行分析:

  1. read(byte[])方法是每次将b.length個位元組的資料讀入一個byte資料組中。
  2. 傳回讀入緩沖的位元組總數,如果因為已經到達檔案末尾而沒有更多的資料,則傳回-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();
			}
	}
	}      

繼續閱讀