天天看點

流------I/O、位元組流、字元流、I/O異常處理、BIO,NIO,AIOIO概述位元組流字元流IO異常的處理BIO,NIO,AIO 有什麼差別?

目錄

  • IO概述
    • 什麼是IO
    • IO的分類
    • IO中的頂級父類
  • 位元組流
    • 位元組輸出流-OutputStream
    • 位元組輸入流-InputStream
    • FileOutputStream類
      • 聲明:
      • 構造方法
      • 寫出位元組資料
      • 資料追加續寫
      • 寫出換行
    • FileInputStream類
      • 聲明:
      • 構造方法
      • 讀取位元組資料
    • 位元組流----實作檔案複制
  • 字元流
    • 前言-位元組流讀取文本檔案時存在的問題
    • 字元輸入流-Reader
    • 字元輸出流-Writer
    • FileReader類
      • 聲明:
      • 構造方法
      • 讀取字元資料
    • FileWriter類
      • 聲明:
      • 構造方法
      • 關閉和重新整理(重要!!!)
      • 基本寫出資料
      • 資料追加續寫
      • 寫出換行
  • IO異常的處理
    • JDK7前處理
    • JDK7的處理
    • JDK9的改進(擴充、了解)
  • BIO,NIO,AIO 有什麼差別?
    • 簡答
    • 詳細說明

IO概述

什麼是IO

  • 我們把資料的傳輸(例如把某個檔案從C槽複制到D盤),可以看做是一種資料的流動。
  • 按照流動的方向,以記憶體為基準,分為

    輸入input

    輸出output

  • 即流向記憶體是輸入流,流出記憶體是輸出流。
  • Java中I/O操作主要是指使用

    java.io

    包下的内容進行輸入、輸出操作。
  • 輸入也叫做讀取資料,輸出也叫做作寫出資料。
    流------I/O、位元組流、字元流、I/O異常處理、BIO,NIO,AIOIO概述位元組流字元流IO異常的處理BIO,NIO,AIO 有什麼差別?

IO的分類

根據資料的流向分為:輸入流和輸出流。

  • 輸入流 :把資料從

    其他裝置

    上讀取到

    記憶體

    中的流。
  • 輸出流 :把資料從

    記憶體

    中寫出到

    其他裝置

    上的流。

根據資料的類型分為:位元組流和字元流。

  • 位元組流 :以位元組為機關,讀寫資料的流。
  • 字元流 :以字元為機關,讀寫資料的流。

按照流的角色劃分為:節點流和處理流。

  • 節點流:可以從某節點讀資料或向某節點寫資料的流。

    如 FileInputStream

  • 處理流:對已存在的流的連接配接和封裝,實作更豐富的流資料處理,且構造方法有其他的流對象參數。

    如 BufferedReader

IO中的頂級父類

\ 輸入流 輸出流
位元組流

位元組輸入流

InputStream

位元組輸出流

OutputStream

字元流

字元輸入流

Reader

字元輸出流

Writer

位元組流

位元組輸出流-OutputStream

聲明:

  • java.io.OutputStream

    抽象類是表示位元組輸出流的所有類的超類

位元組輸出流的共性方法:

  • public void close()

    :關閉此輸出流并釋放與此流相關聯的任何系統資源。
  • public void flush()

    :重新整理此輸出流并強制任何緩沖的輸出位元組被寫出。
  • public void write(byte[] b)

    :将 b.length位元組從指定的位元組數組寫入此輸出流。
  • public void write(byte[] b, int off, int len)

    :從指定的位元組數組寫入 len位元組,從偏移量 off開始輸出到此輸出流。
  • public abstract void write(int b)

    :将指定的位元組輸出流。

位元組輸入流-InputStream

聲明:

  • java.io.InputStream

    抽象類是表示位元組輸入流的所有類的超類,可以讀取位元組資訊到記憶體中

位元組輸入流的共性方法:

  • public void close()

    :關閉此輸入流并釋放與此流相關聯的任何系統資源。
  • public abstract int read()

    : 從輸入流讀取資料的下一個位元組。
  • public int read(byte[] b)

    : 從輸入流中讀取一些位元組數,并将它們存儲到位元組數組 b中 。
close方法,當完成流的操作時,必須調用此方法,釋放系統資源。

FileOutputStream類

聲明:

  • OutputStream

    位元組輸出流的子類之一
  • java.io.FileOutputStream

    類是檔案輸出流,用于将資料寫出到檔案。

構造方法

  • public FileOutputStream(File file)

    :建立檔案輸出流以寫入由指定的 File對象表示的檔案。
  • public FileOutputStream(String name)

    : 建立檔案輸出流以指定的名稱寫入檔案。
通過上述2種方法,建立一個流對象時,必須傳入一個檔案路徑。該路徑下,如果沒有這個檔案,會建立該檔案。如果有這個檔案,會清空這個檔案的資料。

寫出位元組資料

1-寫出位元組:

write(int b)

方法,每次可以寫出一個位元組資料

  1. 雖然參數為int類型四個位元組,但是隻會保留一個位元組的資訊寫出。
  2. 流操作完畢後,必須釋放系統資源,調用close方法,千萬記得。

2-寫出位元組數組:

write(byte[] b)

方法,每次可以寫出數組中的資料

  • 對于字元串,可以先轉換為位元組數組,在寫出。
  • 字元串轉換為位元組數組:byte[] b = “我是字元串”.getBytes();

3-寫出指定長度位元組數組:

write(byte[] b, int off, int len)

,每次寫出從off索引開始,共寫出len個位元組

資料追加續寫

建立輸出流對象時,不清空目标檔案中的資料,而是保留,并繼續往後添加新資料

方法:

  • public FileOutputStream(File file, boolean append)

    : 建立檔案輸出流以寫入由指定的 File對象表示的檔案。
  • public FileOutputStream(String name, boolean append)

    : 建立檔案輸出流以指定的名稱寫入檔案。

寫出換行

Windows系統裡,換行符号是

\r\n

寫出一個換行, 換行符号轉成數組寫出 :

fos.write("\r\n".getBytes());

FileInputStream類

聲明:

  • java.io.FileInputStream

    類是檔案輸入流,從檔案中讀取位元組。

構造方法

  • FileInputStream(File file)

    : 通過打開與實際檔案的連接配接來建立一個 FileInputStream ,該檔案由檔案系統中的 File對象 file命名。
  • FileInputStream(String name)

    : 通過打開與實際檔案的連接配接來建立一個 FileInputStream ,該檔案由檔案系統中的路徑名 name命名。
當你建立一個流對象時,必須傳入一個檔案路徑。該路徑下,如果沒有該檔案,會抛出

FileNotFoundException

讀取位元組資料

1-讀取位元組:

read

方法,每次可以讀取一個位元組的資料,提升為int類型。當讀取到檔案末尾,傳回

-1

// 循環讀取 fis為流對象(省略了部分代碼)

while ((b = fis.read())!=-1) {

System.out.println((char)b);

}

2-使用位元組數組讀取:

read(byte[] b)

方法,每次讀取b的長度個位元組到數組中,傳回讀取到的有效位元組個數,讀取到末尾時,傳回

-1

注意:

極有可能出現,最後一次讀取時,讀取到的位元組數量不足緩沖數組的長度,導緻上次讀取的資料沒有被完全替換。解決方法:通過read(byte[] b)方法的傳回值擷取有效位元組數量

len

,可供後續處理。

關鍵代碼示範:

// 定義位元組數組,作為裝位元組資料的容器   
        byte[] b = new byte[2];
        // 循環讀取
        while (( len= fis.read(b))!=-1) {
           	// 每次讀取後,把數組的有效位元組部分,變成字元串列印
            System.out.println(new String(b,0,len));//  len 每次讀取的有效位元組個數
        }
           
使用數組讀取,每次讀取多個位元組,減少了系統間的IO操作次數,進而提高了讀寫的效率,建議開發中使用。

位元組流----實作檔案複制

複制原理圖解

流------I/O、位元組流、字元流、I/O異常處理、BIO,NIO,AIOIO概述位元組流字元流IO異常的處理BIO,NIO,AIO 有什麼差別?

案例實作(代碼)

public class Copy {
    public static void main(String[] args) throws IOException {
        // 1.建立流對象
        // 1.1 指定資料源
        FileInputStream fis = new FileInputStream("D:\\test.jpg");
        // 1.2 指定目的地
        FileOutputStream fos = new FileOutputStream("test_copy.jpg");

        // 2.讀寫資料
        // 2.1 定義數組
        byte[] b = new byte[1024];
        // 2.2 定義長度
        int len;
        // 2.3 循環讀取
        while ((len = fis.read(b))!=-1) {
            // 2.4 寫出資料
            fos.write(b, 0 , len);
        }

        // 3.關閉資源
        fos.close();
        fis.close();
    }
}
           

字元流

前言-位元組流讀取文本檔案時存在的問題

  • 當使用位元組流讀取文本檔案,遇到中文字元時,可能不會顯示完整的字元,那是因為一個中文字元可能占用多個位元組存儲。
  • 是以Java提供一些字元流類,以字元為機關讀寫資料,專門用于處理文本檔案。

字元輸入流-Reader

聲明:

  • java.io.Reader

    抽象類是表示用于讀取字元流的所有類的超類,可以讀取字元資訊到記憶體中。

位元組輸入流的共性方法:

  • public void close()

    :關閉此流并釋放與此流相關聯的任何系統資源。
  • public int read()

    : 從輸入流讀取一個字元。
  • public int read(char[] cbuf)

    : 從輸入流中讀取一些字元,并将它們存儲到字元數組 cbuf中 。

字元輸出流-Writer

聲明:

  • java.io.Writer

    抽象類是表示用于寫出字元流的所有類的超類,将指定的字元資訊寫出到目的地。

字元輸出流的共性方法:

  • void write(int c)

    寫入單個字元。
  • void write(char[] cbuf)

    寫入字元數組。
  • abstract void write(char[] cbuf, int off, int len)

    寫入字元數組的某一部分,off數組的開始索引,len寫的字元個數。
  • void write(String str)

    寫入字元串。
  • void write(String str, int off, int len)

    寫入字元串的某一部分,off字元串的開始索引,len寫的字元個數。
  • void flush()

    重新整理該流的緩沖。
  • void close()

    關閉此流,但要先重新整理它。

FileReader類

聲明:

  • java.io.FileReader

    類是讀取字元檔案的便利類。構造時使用系統預設的字元編碼和預設位元組緩沖區。

    小貼士:

  1. 字元編碼:位元組與字元的對應規則。

    Windows系統的中文編碼預設是GBK編碼表。 idea中UTF-8

  2. 位元組緩沖區:一個位元組數組,用來臨時存儲位元組資料。

構造方法

  • FileReader(File file)

    : 建立一個新的 FileReader ,給定要讀取的File對象。
  • FileReader(String fileName)

    : 建立一個新的 FileReader ,給定要讀取的檔案的名稱。
當你建立一個流對象時,必須傳入一個檔案路徑。該路徑下,如果沒有該檔案,會抛出

FileNotFoundException

讀取字元資料

1-讀取字元:

read

方法,每次可以讀取一個位元組的資料,提升為int類型。當讀取到檔案末尾,傳回

-1

// 定義變量,儲存資料
    int b ;
    // 循環讀取
    while ((b = fr.read())!=-1) {
        System.out.println((char)b);
    }
     
     //雖然讀取了一個字元,但是會自動提升為int類型。
           

2-使用字元數組讀取:

read(char[] cbuf)

,每次讀取b的長度個字元到數組中,傳回讀取到的有效字元個數,讀取到末尾時,傳回

-1

FileWriter類

聲明:

  • java.io.FileWriter

    類是寫出字元到檔案的便利類。構造時使用系統預設的字元編碼和預設位元組緩沖區。

構造方法

  • FileWriter(File file)

    : 建立一個新的 FileWriter,給定要讀取的File對象。
  • FileWriter(String fileName)

    : 建立一個新的 FileWriter,給定要讀取的檔案的名稱。

關閉和重新整理(重要!!!)

因為内置緩沖區的原因,無法直接寫出字元到檔案中。可以使用下面2種方法–

  • flush

    :重新整理緩沖區,流對象可以繼續使用。
  • close

    :先重新整理緩沖區,然後通知系統釋放資源。流對象不可以再被使用了。

基本寫出資料

1-寫出字元:

write(int b)

方法,每次可以寫出一個字元資料

// 寫出資料
  	fw.write(97); // 寫出第1個字元
  	fw.write('b'); // 寫出第2個字元
  	fw.write('C'); // 寫出第3個字元
  	fw.write(30000); // 寫出第4個字元,中文編碼表中30000對應一個漢字。
  
  // 注意:
  //上述代碼,未調用close方法關流,資料隻是儲存到了緩沖區,并未寫出到檔案中。
           

2-寫出字元數組 :

write(char[] cbuf)

write(char[] cbuf, int off, int len)

每次可以寫出字元數組中的資料,用法類似FileOutputStream

3-寫出字元串:

write(String str)

write(String str, int off, int len)

每次可以寫出字元串中的資料,更為友善,

資料追加續寫

方法:

  • public FileWriter(File file, boolean append)

    : 建立檔案字元輸出流以寫入由指定的 File對象表示的檔案。
  • public FileWriter(String name, boolean append)

    : 建立檔案字元輸出流以指定的名稱寫入檔案。

字元流,隻能操作文本檔案,不能操作圖檔,視訊等非文本檔案。

當我們單純讀或者寫文本檔案時 使用字元流 其他情況使用位元組流

寫出換行

Windows系統裡,換行符号是

\r\n

//此處fw是字元輸出流	
  // 寫出換行
  	fw.write("\r\n");
           

IO異常的處理

JDK7前處理

  • 通過

    try...catch...finally

    代碼塊,處理異常部分
public class HandleException1 {
    public static void main(String[] args) {
      	// 聲明變量
        FileWriter fw = null;
        try {
            //建立流對象
            fw = new FileWriter("fw.txt");
            // 寫出資料
            fw.write("黑馬程式員"); //黑馬程式員
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fw != null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
           

JDK7的處理

  • 使用JDK7優化後的

    try-with-resource

    語句
  • 該語句確定了每個資源在語句結束時關閉。
  • 所謂的資源(resource)是指在程式完成後,必須關閉的對象。

格式:

1----

try (建立流對象語句,如果多個,使用';'隔開) {
	// 讀寫資料
} catch (IOException e) {
	e.printStackTrace();
}
           

2----

// 被final修飾的對象
final Resource resource1 = new Resource("resource1");
// 普通對象
Resource resource2 = new Resource("resource2");
// 引入方式:建立新的變量儲存
try (Resource r1 = resource1;
     Resource r2 = resource2) {
     // 使用對象
}
           

代碼使用示範:

public class HandleException2 {
    public static void main(String[] args) {
      	// 建立流對象
        try ( FileWriter fw = new FileWriter("fw.txt"); ) {
            // 寫出資料
            fw.write("黑馬程式員"); //黑馬程式員
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
           

JDK9的改進(擴充、了解)

  • JDK9中對

    try-with-resource

    進行了改進,采用引入對象的方式,支援的更加簡潔。
  • 被引入的對象,同樣可以自動關閉,無需手動close

格式:

// 被final修飾的對象
final Resource resource1 = new Resource("resource1");
// 普通對象
Resource resource2 = new Resource("resource2");

// 引入方式:直接引入
try (resource1; resource2) {
     // 使用對象
}
           

代碼使用示範:

public class TryDemo {
    public static void main(String[] args) throws IOException {
       	// 建立流對象
        final  FileReader fr  = new FileReader("in.txt");
        FileWriter fw = new FileWriter("out.txt");
       	// 引入到try中
        try (fr; fw) {
          	// 定義變量
            int b;
          	// 讀取資料
          	while ((b = fr.read())!=-1) {
            	// 寫出資料
            	fw.write(b);
          	}
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
           

BIO,NIO,AIO 有什麼差別?

簡答

  • BIO:Block IO 同步阻塞式 IO,就是我們平常使用的傳統 IO,它的特點是模式簡單使用友善,并發處理能力低。
  • NIO:Non IO 同步非阻塞 IO,是傳統 IO 的更新,用戶端和伺服器端通過 Channel(通道)通訊,實作了多路複用。
  • AIO:Asynchronous IO 是 NIO 的更新,也叫 NIO2,實作了異步非堵塞 IO ,異步 IO 的操作基于事件和回調機制。

詳細說明

  • BIO (Blocking I/O): 同步阻塞I/O模式,資料的讀取寫入必須阻塞在一個線程内等待其完成。在活動連接配接數不是特别高(小于單機1000)的情況下,這種模型是比較不錯的,可以讓每一個連接配接專注于自己的 I/O 并且程式設計模型簡單,也不用過多考慮系統的過載、限流等問題。線程池本身就是一個天然的漏鬥,可以緩沖一些系統處理不了的連接配接或請求。但是,當面對十萬甚至百萬級連接配接的時候,傳統的 BIO 模型是無能為力的。是以,我們需要一種更高效的 I/O 處理模型來應對更高的并發量。
  • NIO (New I/O): NIO是一種同步非阻塞的I/O模型,在Java 1.4 中引入了NIO架構,對應 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以了解為Non-blocking,不單純是New。它支援面向緩沖的,基于通道的I/O操作方法。NIO提供了與傳統BIO模型中的 Socket 和 ServerSocket 相對應的 SocketChannel 和 ServerSocketChannel 兩種不同的套接字通道實作,兩種通道都支援阻塞和非阻塞兩種模式。阻塞模式使用就像傳統中的支援一樣,比較簡單,但是性能和可靠性都不好;非阻塞模式正好與之相反。對于低負載、低并發的應用程式,可以使用同步阻塞I/O來提升開發速率和更好的維護性;對于高負載、高并發的(網絡)應用,應使用 NIO 的非阻塞模式來開發
  • AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改進版 NIO 2,它是異步非阻塞的IO模型。異步 IO 是基于事件和回調機制實作的,也就是應用操作之後會直接傳回,不會堵塞在那裡,當背景處理完成,作業系統會通知相應的線程進行後續的操作。AIO 是異步IO的縮寫,雖然 NIO 在網絡操作中,提供了非阻塞的方法,但是 NIO 的 IO 行為還是同步的。對于 NIO 來說,我們的業務線程是在 IO 操作準備好時,得到通知,接着就由這個線程自行進行 IO 操作,IO操作本身是同步的。查閱網上相關資料,我發現就目前來說 AIO 的應用還不是很廣泛,Netty 之前也嘗試使用過 AIO,不過又放棄了。