數據流分為輸入、輸出流,無論是輸入流還是輸出流,都可看作是在源和目標之間架設一根"管道",這些管道都是單向流動的,要麼流入到記憶體(輸入流),要麼從記憶體流出(輸出流)。 應用於java上,輸入流和輸出流分別為InputStream和OutputStream。輸入流用於讀取(read)數據,將數據載入到 ...
數據流分為輸入、輸出流,無論是輸入流還是輸出流,都可看作是在源和目標之間架設一根"管道",這些管道都是單向流動的,要麼流入到記憶體(輸入流),要麼從記憶體流出(輸出流)。
應用於java上,輸入流和輸出流分別為InputStream和OutputStream。輸入流用於讀取(read)數據,將數據載入到記憶體(應用程式),輸出流用於寫入(write)數據,將數據從記憶體寫入到磁碟中。
數據流可分為位元組流和字元流,位元組流按位元組輸入、輸出數據,字元流按字元個數輸入、輸出數據。本文介紹的是位元組流。
1.OutputStream類和FileOutputStream類
OutputStream類是位元組輸出流的超類。它只提供了幾個方法,註意這幾個方法都會拋出IOExcepiton異常,因此需要捕獲或向上拋出:
close()
:關閉輸出流,即撤掉管道。flush()
:將緩存位元組數據強制刷到磁碟上。write(byte[] b)
:將byte數組中的數據寫入到輸出流。write(byte[] b, int off, int len)
:將byte數組中從off開始的len個位元組寫入到此輸出流。write(int b)
:將指定的單個位元組寫入此輸出流。
FileOutputStream類是OutputStream的子類,專門用於操作文件相關的流,例如向文件中寫入數據。該類有以下幾個構造方法:
FileOutputStream(File file):創建一個向指定 File 對象表示的文件中寫入數據的文件輸出流。 FileOutputStream(File file, boolean append):創建一個向指定 File 對象表示的文件中寫入數據的文件輸出流。 FileOutputStream(String name):創建一個向具有指定名稱的文件中寫入數據的輸出文件流。 FileOutputStream(String name, boolean append):創建一個向具有指定 name 的文件中寫入數據的輸出文件流。
例如:創建一個新文件,向其中寫入"abcde"。
import java.io.*;
public class OutStr1 {
public static void main(String[] args) throws IOException {
File tempdir = new File("D:/temp"); //create a tempdir
if(!tempdir.exists()) {
tempdir.mkdir();
}
File testfile = new File(tempdir,"test.txt");
FileOutputStream fos = new FileOutputStream(testfile)//在記憶體和testfile之間架一根名為fos的管道
fos.write("abcde".getBytes()); //將位元組數組中所有位元組(byte[] b,b.length)都寫入到管道中
fos.close();
}
}
註意:
(1).此處的FileOutputStream()構造方法會創建一個新文件並覆蓋舊文件。如果要追加文件,採用FileOutputStream(file,true)構造方法構造位元組輸出流。
(2).上面的for.write("abcde".getBytes())
用的是write(byte[] b)方法,它會將位元組數組中b.length個位元組即所有位元組都寫入到輸出流中。可以採用write(byte[] b,int off,int len)方法每次寫給定位置、長度的位元組數據。
(3).無論是構造方法FileOutputStream(),還是write()、close()方法,都會拋出異常,有些是IOException,有些是FileNotFoundException。因此,必須捕獲這些異常或向上拋出。
追加寫入和換行寫入
向文件中追加數據時,採用write(File file,boolean append)方法。
File testfile = new File(tempdir,"test.txt");
FileOutputStream fos = new FileOutputStream(testfile,true);
byte[] data = "abcde".getBytes();
fos.write(data,0,2);
fos.close();
換行追加時,需要加上換行符,Windows上的換行符為"\r\n",unix上的換行符為"\n"。如果要保證良好的移植性,可獲取系統屬性中的換行符並定義為常量。
import java.io.*;
public class OutStr1 {
private static final String LINE_SEPARATOR = System.getProperty("line.separator"); //newline
public static void main(String[] args) throws IOException {
File tempdir = new File("D:/temp"); //create a tempdir
if(!tempdir.exists()) {
tempdir.mkdir();
}
File testfile = new File(tempdir,"test.txt");
FileOutputStream fos = new FileOutputStream(testfile,true);
String str = LINE_SEPARATOR+"abcde"; //
fos.write(str.getByte());
fos.close();
}
}
2.捕獲IO異常的方法
輸出流中很多方法都定義了拋出異常,這些異常必須向上拋出或捕獲。向上拋出很簡單,捕獲起來可能比想象中的要複雜一些。
以向某文件寫入數據為例,以下是最終的捕獲代碼,稍後將逐層分析為何要如此捕獲。
File file = new File("d:/tempdir/a.txt");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
fos.write("abcde".getBytes());
} catch (IOException e) {
...
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException("xxxx");
}
}
}
向某文件寫入數據,大致會包含以下幾個過程。
File file = new File("d:/tempdir/a.txt");
FileOutputStream fos = new FileOutputStream(file); //---->(2)
fos.write("abcde".getBytes()); //------------------->(3)
fos.close(); //--------------------------------------->(4)
其中(2)-(4)這三條代碼都需要去捕獲,如果將它們放在一個try結構中,顯然邏輯不合理,例如(2)正常實例化了一個輸出流,但(3)異常,這時無法用(4)來關閉流。無論異常發生在何處,close()動作都是應該要執行,因此需要將close()放入finally結構中。
File file = new File("D:/tempdir/a.txt");
try {
FileOutputStream fos = new FileOutputStream(file); //---->(2)
fos.write("abcde".getBytes()); //------------------->(3)
} catch (IOException e) {
...
} finally {
fos.close(); //--------------------------------------->(4)
}
但這樣一來,fos.close()中的fos是不可識別的,因此,考慮將fos定義在try結構的外面。因此:
File file = new File("D:/tempdir/a.txt");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file); //---->(2)
fos.write("abcde".getBytes()); //-->(3)
} catch (IOException e) {
...
} finally {
fos.close(); //---------------------->(4)
}
如果d:\tempdir\a.txt文件不存在,那麼(2)中的fos將指向空的流對象,即空指針異常。再者,finally中的close()也是需要捕獲異常的,因此加上判斷並對其try...catch,於是:
File file = new File("D:/tempdir/a.txt");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file); //---->(2)
fos.write("abcde".getBytes()); //-->(3)
} catch (IOException e) {
...
} finally {
if(fos != null) {
try {
fos.close();//-------------------->(4)
} catch (IOException e) {
throw new RuntimeException("xxx");
}
}
}
3.InputStream類和FileInputStream類
以下是InputStream類提供的方法:
close()
:關閉此輸入流並釋放與該流關聯的所有系統資源。mark(int readlimit)
:在此輸入流中標記當前的位置。read()
:從輸入流中讀取數據的下一個位元組,返回的是0-255之間的ASCII碼,讀到文件結尾時返回-1。read(byte[] b)
:從輸入流中讀取一定數量的位元組存儲到緩衝區數組b中,返回讀取的位元組數,讀到文件結尾時返回-1。read(byte[] b, int off, int len)
:將輸入流中最多 len 個數據位元組讀入 byte 數組,返回讀取的位元組數,讀到文件結尾時返回-1。reset()
:將此流重新定位到最後一次對此輸入流調用 mark 方法時的位置。skip(long n)
:跳過和丟棄此輸入流中數據的 n 個位元組。
示例:d:\temp\test.txt文件中的內容為"abcde",讀取該文件。
import java.io.*;
public class InStr1 {
public static void main(String[] args) throws IOException {
File file = new File("d:/temp/test.txt");
FileInputStream fis = new FileInputStream(file);
System.out.println(fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println(fis.read());
System.out.println(fis.read());
fis.close();
}
}
執行結果為:
97
b
c
d
e
-1
-1
從結果可知:
(1).read()每次讀取一個位元組並返回0-255之間的數值。可以強制轉換為char字元。
(2).每次讀取後指針下移一位,直到文件的結尾。
(3).讀取文件結束符返回-1,且指針一直停留在結束符位置處,因此後面繼續讀取還是返回-1。
因此,可以使用如下代碼來讀取整個文件。
int ch = 0 ;
while ((ch=fis.read())!= -1) {
System.out.println((char)ch);
}
當文件很大時,這樣一個位元組一個位元組讀取的速度必然極慢。因此,需要一次讀取多個位元組。下麵是使用read(byte[] b)一次讀取多個位元組的代碼。
int len = 0;
byte[] buf = new byte[2];
while ((len=fis.read(buf))!=-1) {
System.out.println(new String(buf,0,len));
}
執行結果為:
ab
cd
e
幾個註意點:
(1).read(byte[] b)是從文件中讀取b.length個位元組存儲到位元組數組中,第一個位元組存儲到b[0],第二個位元組存儲到b[2],以此類推,註意存儲在位元組數組中的每一個位元組都是0-255的ASCII碼。例如文件中有3個位元組abc,位元組數組b的長度為5,則b[0]=a,b[1]=b,b[2]=c,b[3]和b[4]不受影響(即仍為初始化值0),如果位元組數組b長度為2,則第一批讀取兩個位元組ab存儲到b[0]和b[1],第二批再讀取一個位元組c存儲到b[0]中,此時位元組數組b中存儲的內容為cb。
(2).read(b)返回的是讀取的位元組數,在到達文件末尾時返回-1。
因此,上面的代碼讀取文件的過程大致為:讀取a和b存放到buf[0]和buf[1]中,此時len=2,轉換為字元串後列印得到ab,再讀取c覆蓋buf[0],讀取d覆蓋buf[1],轉換為字元串後列印得到cd,最後讀取e覆蓋到buf[0],到了文件的末尾,此時buf[1]=d。也就是說到此為止,b數組中存放的仍然有d,但因為String(buf,0,len)是根據len來轉換為字元串的,因此d不會被轉換。但如果將new String(buf,0,len)
改為new String(buf)
,將得到abcded。
由此可知,位元組數組的長度決定了每次讀取的位元組數量。通常來說,可以將byte[]的長度設置為1024的整數倍以達到更高的效率,例如設置為4096,8192等都可,但也不應設置過大。
4.複製文件(位元組流)
原理:
1.在源文件上架起一根輸出流(read)管道。
2.在目標文件上架起一根輸入流(write)管道。
3.但註意,這兩根管道之間沒有任何聯繫。可以通過中間媒介實現中轉。每read()一定數量的位元組到記憶體中,可以將這些位元組write到磁碟中。
4.應該使用位元組數組作為buffer做緩存,而不應該使用單位元組的讀取、寫入,這樣會非常非常慢。
import java.io.*;
public class CopyFile {
public static void main(String[] args) throws IOException {
// src file and src Stream
File src = new File("d:/temp/1.avi");
FileInputStream fis = new FileInputStream(src);
// dest file and dest Stream
File dest = new File("d:/temp/1_copy.avi");
FileOutputStream fos = new FileOutputStream(dest);
int len = 0;
byte[] buf = new byte[1024];
while((len=fis.read(buf)) != -1) { // read
fos.write(buf,0,len); // write
}
fis.close();
fos.close();
}
}
註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!