1. 程式人生 > >java IO(二):字節流(InputStream和OutputStream)

java IO(二):字節流(InputStream和OutputStream)

資源 roo details nsis utils andro 程序 hit sts

數據流分為輸入、輸出流,無論是輸入流還是輸出流,都可看作是在源和目標之間架設一根"管道",這些管道都是單向流動的,要麽流入到內存(輸入流),要麽從內存流出(輸出流)。

應用於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();
    }
}

註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!

java IO(二):字節流(InputStream和OutputStream)