(二)、Java I/O系統——字節流

在第一節中,我們瞭解了 File 類對於文件的操作。File對象封裝了文件或者路徑屬性,但是不包括從/向文件讀/寫數據的方法,本節主要講解Java的字節流。
I/O流解決的問題:設備與設備之間數據傳輸的問題。
Java流的分類:

  1. 從流的方向上可以分爲輸入流和輸出流,應用程序通過輸入流讀取數據,通過輸出流發送數據。
  2. 按照數據傳輸單位可以劃分爲字節流和字符流。
    字節流讀取的是文件中的二進制數據,不會對數據進行任何處理。
    字符流讀取的也是文件中的二進制數據,但是會對這些數據進行處理(解碼)

Java I/O類的設計是應用繼承一個很好的例子,使用裝飾者模式進行設計。關於I/O中裝飾者模式的內容請關注後續章節。

1、字節流

這裏寫圖片描述

1.1、FileInputStream

構造方法:

  • FileInputStream(File file) 由file對象創建一個FileInputStream
  • FileInputStream(String filename) 由文件名創建一個FileInputStream

    1. int read() 從輸入流中讀取一個字節的數據,返回讀取的數據,如果達到輸入流末尾,則返回-1。
    2. int read(byte[] buffer) 從輸入流中讀取b.length個字節的數據到字節數組buffer中,返回實際讀取的字節數。如果到達輸入流末尾,則返回-1。
    3. int read(byte[] buffer,int off,int len) 從輸入流中讀取多個字節的數據,並存儲在buffer[off]~buffer[off+len-1]中,返回實際讀取的字節數。如果到達輸入流末尾,則返回-1。

最常見的FileInputStream讀取文件的方式:

private static void readFile() {

        FileInputStream fis = null;

        File f = new File("./src/a.txt");
        try {
            fis = new FileInputStream(f);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        byte[] buf = new byte[4];  // 一次性讀取buf.length個字節的數據
        int length = 0;
        try {
            while ((length = fis.read(buf)) != -1) {  //將讀取的字節數據裝入byte數組中,返回的是讀取的字節數;讀至文件末尾返回 -1
                /**
                 * read方法將數據讀進byte數組採用覆蓋的方式,而並非清空重新賦值的方式。
                 * 故,在獲取輸出時要指定byte數組中有效的元素。
                 */
                System.out.print(new String(buf, 0, length));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

int read(byte[] b) 方法將數據讀進byte數組中,採用覆蓋的方式,並不是清空後重新添加的方式。所以從byte數組中獲取輸出數據時需要指定有效元素。
使用流讀取文件後務必要關閉資源 fis.close()

1.2、FileOutputStream

構造方法:

  • FileOutputStream(File file) 由file對象創建一個FileOutputStream
  • FileOutputStream(String filename) 由文件名創建一個FileOutputStream
  • FileOutputStream(File file,boolean append) append爲真,數據追加至文件末尾
  • FileOutputStream(File file,boolean append) append爲真,數據追加至文件末尾

    1. void write(int b) 將指定字節寫入輸出流
    2. void write(byte[] b) 將字節數組b中的所有字節寫入輸出流
    3. void write(byte[] b,int off,int len) 將b[off]~b[off+len-1]寫入輸出流
    4. void close() 關閉輸出流並釋放與其有關的系統資源
    5. void flush() 刷新輸出流並強制寫出所有緩衝的輸出字節

常見的使用FileOutputStream寫文件的方法:

private static void writeFile() throws IOException {

        FileOutputStream fos = null;

        File file =  new File("./src/b.txt");
        fos = new FileOutputStream(file,true);
        String data = "xiaopeng";
        fos.write(data.getBytes());   //寫入字節數組

        fos.close();
    }

write(int b)方法傳入int類型的變量(4個字節),但是該方法只會將最低位的那個字節寫入文件,其餘的三個字節會被捨棄。二進制數:00000000-00000000-00000001-01100001(353),將353寫入,但是文件中卻只有低八位(97)。

private static void writeTest() throws IOException {

        FileOutputStream fos = null;
        FileInputStream fis = null;
        File file = new File("./src/b.txt");
        fos = new FileOutputStream(file);
        fis = new FileInputStream(file);
        fos.write(353);   //00000000-00000000-00000001-01100001
        byte[] bytes = new byte[4];
        fis.read(bytes);

        System.out.println(Arrays.toString(bytes));

        fis.close();
        fos.close();
    }

輸出:

[97, 0, 0, 0]

1.3、BufferedInputStream/BufferedOutputStream

Java提供了專門的緩衝字節流來提高文件讀取的效率。BufferedInputStream/BufferedOutputStream類內部有一個緩衝字節數組(默認8192B),用來提高處理效率。

private static void bufferedReadTest() throws IOException {

        BufferedInputStream bufferedInputStream = null;
        //BufferedInputStream本身不具備讀取文件的功能,需要藉助FileInputStream進行讀取文件
        bufferedInputStream = new BufferedInputStream(new FileInputStream(new File("./src/a.txt")));

        int content = 0;
        while((content = bufferedInputStream.read()) != -1){
            System.out.print((char)content);
        }

        bufferedInputStream.close();  //內部其實調用 fileInputStream.close()
    }

BufferedInputStream不具備讀取文件的功能,需要藉助FileInputStream進行文件讀取,查看BufferedInputStream的read方法可以發現:

public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }
  • count:代表BufferedInputStream內部維護的字節數組大小
  • pos:代表當前讀取字節數組中那個下標的數據
    若pos<=count,那麼調用 fill() 方法(其實是FileInputStream的int read(byte[] b,int off,int len))將數據讀入字節數組;否則直接從字節數組中讀取一個字節的數據並返回。

BufferedInputStream的 close() 方法其實內部調用傳入的FileInputStream的 close() 方法,因此關閉資源時只需要調用BufferedInputStream即可。

public void close() throws IOException {
        byte[] buffer;
        while ( (buffer = buf) != null) {
            if (bufUpdater.compareAndSet(this, buffer, null)) {
                InputStream input = in;
                in = null;
                if (input != null)
                    input.close();    //FileInputStream.close()
                return;
            }
        }
    }

關於BufferedOutputStream將數據寫入磁盤的實現方式:

  1. 調用父類FilterOutputStream的 close() 方法關閉資源:
public void close() throws IOException {
        try (OutputStream ostream = out) {
            flush();
        }
    }
  1. 手工調用BufferOutputStream的 flush() 方法將數據寫入:
public synchronized void flush() throws IOException {
        flushBuffer();
        out.flush();
    }
  1. 查看BufferedOutputStream的 write() 方法可以發現,當緩衝字節數組容量滿時,也會調用將數據寫入:
public synchronized void write(int b) throws IOException {
        if (count >= buf.length) {
            flushBuffer();  //實際調用FilterOutputStream.write()寫入文件
        }
        buf[count++] = (byte)b;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章