目錄
- 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
- 輸入也叫做讀取資料,輸出也叫做作寫出資料。
IO的分類
根據資料的流向分為:輸入流和輸出流。
- 輸入流 :把資料從
上讀取到其他裝置
中的流。記憶體
- 輸出流 :把資料從
中寫出到記憶體
上的流。其他裝置
根據資料的類型分為:位元組流和字元流。
- 位元組流 :以位元組為機關,讀寫資料的流。
- 字元流 :以字元為機關,讀寫資料的流。
按照流的角色劃分為:節點流和處理流。
-
節點流:可以從某節點讀資料或向某節點寫資料的流。
如 FileInputStream
-
處理流:對已存在的流的連接配接和封裝,實作更豐富的流資料處理,且構造方法有其他的流對象參數。
如 BufferedReader
IO中的頂級父類
\ | 輸入流 | 輸出流 |
---|---|---|
位元組流 | 位元組輸入流 InputStream | 位元組輸出流 OutputStream |
字元流 | 字元輸入流 Reader | 字元輸出流 Writer |
位元組流
位元組輸出流-OutputStream
聲明:
-
抽象類是表示位元組輸出流的所有類的超類java.io.OutputStream
位元組輸出流的共性方法:
-
:關閉此輸出流并釋放與此流相關聯的任何系統資源。public void close()
-
:重新整理此輸出流并強制任何緩沖的輸出位元組被寫出。public void flush()
-
:将 b.length位元組從指定的位元組數組寫入此輸出流。public void write(byte[] b)
-
:從指定的位元組數組寫入 len位元組,從偏移量 off開始輸出到此輸出流。public void write(byte[] b, int off, int len)
-
:将指定的位元組輸出流。public abstract void write(int b)
位元組輸入流-InputStream
聲明:
-
抽象類是表示位元組輸入流的所有類的超類,可以讀取位元組資訊到記憶體中java.io.InputStream
位元組輸入流的共性方法:
-
:關閉此輸入流并釋放與此流相關聯的任何系統資源。public void close()
-
: 從輸入流讀取資料的下一個位元組。public abstract int read()
-
: 從輸入流中讀取一些位元組數,并将它們存儲到位元組數組 b中 。public int read(byte[] b)
close方法,當完成流的操作時,必須調用此方法,釋放系統資源。
FileOutputStream類
聲明:
-
位元組輸出流的子類之一OutputStream
-
類是檔案輸出流,用于将資料寫出到檔案。java.io.FileOutputStream
構造方法
-
:建立檔案輸出流以寫入由指定的 File對象表示的檔案。public FileOutputStream(File file)
-
: 建立檔案輸出流以指定的名稱寫入檔案。public FileOutputStream(String name)
通過上述2種方法,建立一個流對象時,必須傳入一個檔案路徑。該路徑下,如果沒有這個檔案,會建立該檔案。如果有這個檔案,會清空這個檔案的資料。
寫出位元組資料
1-寫出位元組:
write(int b)
方法,每次可以寫出一個位元組資料
- 雖然參數為int類型四個位元組,但是隻會保留一個位元組的資訊寫出。
- 流操作完畢後,必須釋放系統資源,調用close方法,千萬記得。
2-寫出位元組數組:
write(byte[] b)
方法,每次可以寫出數組中的資料
- 對于字元串,可以先轉換為位元組數組,在寫出。
- 字元串轉換為位元組數組:byte[] b = “我是字元串”.getBytes();
3-寫出指定長度位元組數組:
write(byte[] b, int off, int len)
,每次寫出從off索引開始,共寫出len個位元組
資料追加續寫
建立輸出流對象時,不清空目标檔案中的資料,而是保留,并繼續往後添加新資料
方法:
-
: 建立檔案輸出流以寫入由指定的 File對象表示的檔案。public FileOutputStream(File file, boolean append)
-
: 建立檔案輸出流以指定的名稱寫入檔案。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 ,該檔案由檔案系統中的路徑名 name命名。FileInputStream(String 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操作次數,進而提高了讀寫的效率,建議開發中使用。
位元組流----實作檔案複制
複制原理圖解
案例實作(代碼)
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()
-
: 從輸入流中讀取一些字元,并将它們存儲到字元數組 cbuf中 。public int read(char[] cbuf)
字元輸出流-Writer
聲明:
-
抽象類是表示用于寫出字元流的所有類的超類,将指定的字元資訊寫出到目的地。java.io.Writer
字元輸出流的共性方法:
-
寫入單個字元。void write(int c)
-
寫入字元數組。void write(char[] cbuf)
-
寫入字元數組的某一部分,off數組的開始索引,len寫的字元個數。abstract void write(char[] cbuf, int off, int len)
-
寫入字元串。void write(String str)
-
寫入字元串的某一部分,off字元串的開始索引,len寫的字元個數。void write(String str, int off, int len)
-
重新整理該流的緩沖。void flush()
-
關閉此流,但要先重新整理它。void close()
FileReader類
聲明:
-
java.io.FileReader
類是讀取字元檔案的便利類。構造時使用系統預設的字元編碼和預設位元組緩沖區。
小貼士:
字元編碼:位元組與字元的對應規則。
Windows系統的中文編碼預設是GBK編碼表。 idea中UTF-8
- 位元組緩沖區:一個位元組數組,用來臨時存儲位元組資料。
構造方法
-
: 建立一個新的 FileReader ,給定要讀取的File對象。FileReader(File file)
-
: 建立一個新的 FileReader ,給定要讀取的檔案的名稱。FileReader(String fileName)
當你建立一個流對象時,必須傳入一個檔案路徑。該路徑下,如果沒有該檔案,會抛出 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對象。FileWriter(File file)
-
: 建立一個新的 FileWriter,給定要讀取的檔案的名稱。FileWriter(String fileName)
關閉和重新整理(重要!!!)
因為内置緩沖區的原因,無法直接寫出字元到檔案中。可以使用下面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)
每次可以寫出字元串中的資料,更為友善,
資料追加續寫
方法:
-
: 建立檔案字元輸出流以寫入由指定的 File對象表示的檔案。public FileWriter(File file, boolean append)
-
: 建立檔案字元輸出流以指定的名稱寫入檔案。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,不過又放棄了。