java - IO流

不回頭~發表於2020-12-25

1.流的概念和作用

流:代表任何有能力產出資料的資料來源物件或者是有能力接受資料的接收端物件。出自< Thinking in Java>

流的本質:資料傳輸,根據資料傳輸特性將流抽象為各種類,方便更直觀的進行資料操作。

作用:為資料來源和目的地建立一個輸送通道

1.1 Java IO所採用的模型

Java的IO模型設計非常優秀,它使用Decorator(裝飾者)模式,按功能劃分Stream,您可以動態裝配這些Stream,以便獲得您需要的功能。

例如,您需要一個具有緩衝的檔案輸入流,則應當組合使用FileInputStreamBufferedInputStream

1.2 IO流的分類

1.2.1 按資料流的方向分為 輸入流、輸出流

此輸入、輸出是相對於我們寫的程式碼程式而言。

輸入流:從別的地方(本地檔案,網路上的資源等)獲取資源 輸入到 我們的程式中
輸出流:從我們的程式中 輸出到 別的地方(本地檔案), 將一個字串儲存到本地檔案中,就需要使用輸出流。

例如:從磁碟讀取檔案到記憶體(也可以理解為,給我們的程式碼進行處理),為輸入流,從別的地方流到記憶體。從記憶體(我們的程式碼,例如一個字串“hello”)寫入到磁碟檔案中,為輸出流,從記憶體輸出到其他地方。

1.2.2 按處理資料單位不同分為 位元組流、字元流

1字元 = 2位元組 、 1位元組(byte) = 8位(bit) 、 一個漢字佔兩個位元組長度

位元組流:每次讀取(寫出)一個位元組,當傳輸的資原始檔有中文時,就會出現亂碼。

字元流:每次讀取(寫出)兩個位元組(一個字元),有中文時,使用該流就可以正確傳輸顯示中文。

疑問

  1. Java 中 一個字元佔幾個位元組?

我記得在書本中看到過,Java的char型別是UTF-16的code unit,也就是一定是16位(2位元組),但是當我用字串的getBytes函式時,發現,並不是2而是1。

java中的一個char型別字元是兩個位元組.但String型別並不是按照char型別的方式來儲存的,而是靈活的按照英文1位元組,中文2位元組的形式(UTF-8編碼情況下)。

String 底層不一定都是由 char 組成的,可能做了什麼處理,所以這樣轉換是不對的。

  1. 如果我的檔案是 GBK 編碼的,那麼直接用字元流或位元組流讀取會怎麼樣?

如果不指定編碼,那麼預設unicode編碼(UTF-8),用 UTF-8 編碼方式讀取GBK編碼檔案可能會出現亂碼,所以使用的時候,要注意編碼問題。(為什麼說是由可能呢,因為如果檔案都是字母,沒有中文,不會出現亂碼,後面會驗證這個)

1.2.3 按功能不同分為 節點流、處理流

節點流:以從或向一個特定的地方(節點)讀寫資料。如FileInputStream

處理流:是對一個已存在的流的連線和封裝,通過所封裝的流的功能呼叫實現資料讀寫。如BufferedReader。處理流的構造方法總是要帶一個其他的流物件做引數。一個流物件經過其他流的多次包裝。

1.2.4 四個基本的抽象流型別,所有的流都繼承這四個

輸入流輸出流
位元組流InputStreamoutputStream
字元流ReaderWriter

InputStream:位元組輸入流

在這裡插入圖片描述

OutputStream:位元組輸出流

在這裡插入圖片描述

Reader:字元輸入流

在這裡插入圖片描述

Writer:字元輸出流

在這裡插入圖片描述

1.3 總結流的分類

看上面的幾個分類,可能對於初次學io的同學會感覺到有些混亂,那什麼時候用位元組流,什麼時候該用輸出流呢?其實非常簡單,舉一個例子就學會了。

  1. 首先自己要知道是選擇輸入流還是輸出流,這就要根據自己的情況而定,如果你想從程式寫東西到別的地方,那麼就選擇輸出流,反之用輸入流
  2. 然後考慮你傳輸資料時,是選擇使用位元組流傳輸還是字元流,也就是每次傳1個位元組還是2個位元組,有中文肯定就選擇字元流了(文字檔案可以用字元流,二進位制檔案可以用位元組流,例如圖片,但是二進位制檔案不可用字元流,位元組流的萬能的)
  3. 前面兩步就可以選出一個合適的節點流了,比如位元組輸入流inputStream,如果要在此基礎上增強功能,那麼就在處理流中選擇一個合適的即可。

什麼是文字檔案和二進位制檔案?

從檔案編碼的方式來看,檔案可分為ASCII碼檔案和二進位制碼檔案兩種。

  1. ASCII檔案也稱為文字檔案,這種檔案在磁碟中存放時每個字元對應一個位元組,用於存放對應的ASCII碼。例如,數5678的儲存形式為:ASC碼:  00110101 00110110 00110111 00111000。十進位制碼: 5 6 7 8 共佔用4個位元組。ASCII碼檔案可在螢幕上按字元顯示, 例如源程式檔案就是ASCII檔案,用DOS命令TYPE可顯示檔案的內容。 由於是按字元顯示,因此能讀懂檔案內容。
  2. 二進位制檔案是按二進位制的編碼方式來存放檔案的。 例如, 數5678的儲存形式為: 00010110 00101110只佔二個位元組。二進位制檔案雖然也可在螢幕上顯示, 但其內容無法讀懂。
  3. 大家都知道計算機的儲存在物理上都是二進位制的,所以文字檔案與二進位制檔案的區別並不是物理上的,而是邏輯上的。這兩者只是在編碼層次上有差異。
  4. 簡單來說,
  • 文字檔案是基於字元編碼的檔案,常見的編碼有ASCII編碼,UNICODE編碼等等。
  • 二進位制檔案是基於值編碼的檔案

1.4 IO流特性

  1. 先進先出,最先寫入輸出流的資料最先被輸入流讀取到
  2. 順序存取,可以一個接一個地往流中寫入一串位元組,讀出時也將按寫入順序讀取一串位元組,不能隨機訪問中間的資料。(RandomAccessFile 可以從檔案的任意位置進行存取(輸入輸出)操作
  3. 只讀或只寫,每個流只能是輸入流或輸出流的一種,不能同時具備兩個功能,輸入流只能進行讀操作,對輸出流只能進行寫操作。在一個資料傳輸通道中,如果既要寫入資料,又要讀取資料,則要分別提供兩個流

1.5 IO流常用到的五類一介面

在整個Java.io包中最重要的就是5個類和一個介面。5個類指的是File、OutputStream、InputStream、Writer、Reader;一個介面指的是Serializable.掌握了這些IO的核心操作那麼對於Java中的IO體系也就有了一個初步的認識了。

主要的類如下:

  1. File(檔案特徵與管理): File類是對檔案系統中檔案以及資料夾進行封裝的物件,可以通過物件的思想來操作檔案和資料夾。 File類儲存檔案或目錄的各種後設資料資訊,包括檔名、檔案長度、最後修改時間、是否可讀、獲取當前檔案的路徑名,判斷指定檔案是否存在、獲得當前目錄中的檔案列表,建立、刪除檔案和目錄等方法。
  2. InputStream(二進位制格式操作): 抽象類,基於位元組的輸入操作,是所有輸入流的父類。定義了所有輸入流都具有的共同特徵。
  3. OutputStream(二進位制格式操作): 抽象類。基於位元組的輸出操作。是所有輸出流的父類。定義了所有輸出流都具有的共同特徵
  4. Reader(檔案格式操作): 抽象類,基於字元的輸入操作。
  5. Writer(檔案格式操作): 抽象類,基於字元的輸出操作。
  6. RandomAccessFile(隨機檔案操作): 一個獨立的類,直接繼承至Object.它的功能豐富,可以從檔案的任意位置進行存取(輸入輸出)操作。

在這裡插入圖片描述

2. Java 流物件

2.1 輸入位元組流InputStream

在這裡插入圖片描述

認識每個類的功能即作用

  • ByteArrayInputStream:位元組陣列輸入流,該類的功能就是從位元組陣列(byte[])中進行以位元組為單位的讀取,也就是將資原始檔都以位元組的形式存入到該類中的位元組陣列中去,我們拿也是從這個位元組陣列中拿
  • PipedInputStream:管道位元組輸入流,它和PipedOutputStream一起使用,能實現多執行緒間的管道通訊
  • FilterInputStream :裝飾者模式中處於裝飾者,具體的裝飾者都要繼承它,所以在該類的子類下都是用來裝飾別的流的,也就是處理類
  • BufferedInputStream:緩衝流,對處理流進行裝飾,增強,內部會有一個快取區,用來存放位元組,每次都是將快取區存滿然後傳送,而不是一個位元組或兩個位元組這樣傳送。效率更高
  • DataInputStream:資料輸入流,它是用來裝飾其它輸入流,它“允許應用程式以與機器無關方式從底層輸入流中讀取基本 Java 資料型別”
  • FileInputSream:檔案輸入流。它通常用於對檔案進行讀取操作
  • File:對指定目錄的檔案進行操作。注意,該類雖然是在IO包下,但是並不繼承自四大基礎類
  • ObjectInputStream:物件輸入流,用來提供對“基本資料或物件”的持久儲存。通俗點講,也就是能直接傳輸物件(反序列化中使用)

常用方法

// 從輸入流中讀取資料的下一個位元組
abstract int read()
// 從輸入流中讀取一定數量的位元組,並將其儲存在緩衝區陣列 b中
int read(byte[] b)
// 將輸入流中最多 len 個資料位元組讀入 byte 陣列
int read(byte[] b, int off, int len)
	
// 跳過和丟棄此輸入流中資料的 n個位元組
long skip(long n)

// 關閉此輸入流並釋放與該流關聯的所有系統資源
void close()

2.2 輸出位元組流OutputStream

在這裡插入圖片描述
IO 中輸出位元組流的繼承圖可見上圖,可以看出:

  1. OutputStream 是所有的輸出位元組流的父類,它是一個抽象類
  2. ByteArrayOutputStream、FileOutputStream 是兩種基本的介質流,它們分別向Byte 陣列、和本地檔案中寫入資料。PipedOutputStream 是向與其它執行緒共用的管道中寫入資料
  3. ObjectOutputStream 和所有FilterOutputStream 的子類都是裝飾流(序列化中使用)

常用方法

// 將 b.length 個位元組從指定的 byte 陣列寫入此輸出流
void write(byte[] b)
// 將指定 byte 陣列中從偏移量 off 開始的 len 個位元組寫入此輸出流
void write(byte[] b, int off, int len)
// 將指定的位元組寫入此輸出流
abstract void write(int b)

// 關閉此輸出流並釋放與此流有關的所有系統資源
void close()
	
// 重新整理此輸出流並強制寫出所有緩衝的輸出位元組
void flush()

2.3 字元輸入流Reader

在這裡插入圖片描述

在上面的繼承關係圖中可以看出:

  1. Reader 是所有的輸入字元流的父類,它是一個抽象類。
  2. CharReader、StringReader 是兩種基本的介質流,它們分別將Char 陣列、String中讀取資料。PipedReader 是從與其它執行緒共用的管道中讀取資料
  3. BufferedReader 很明顯就是一個裝飾器,它和其子類負責裝飾其它Reader 物件
  4. FilterReader 是所有自定義具體裝飾流的父類,其子類PushbackReader 對Reader 物件進行裝飾,會增加一個行號
  5. InputStreamReader 是一個連線位元組流和字元流的橋樑,它將位元組流轉變為字元流

常用方法

// 讀取單個字元
int read()
// 將字元讀入陣列
int read(char[] cbuf)
// 將字元讀入陣列的某一部分
abstract int read(char[] cbuf, int off, int len)
// 跳過字元
long skip(long n)
	
// 關閉該流並釋放與之關聯的所有資源
abstract void close()

2.4 字元輸出流Writer

在這裡插入圖片描述

在上面的關係圖中可以看出:

  1. Writer 是所有的輸出字元流的父類,它是一個抽象類
  2. CharArrayWriter、StringWriter 是兩種基本的介質流,它們分別向Char 陣列、String 中寫入資料。PipedWriter 是向與其它執行緒共用的管道中寫入資料
  3. BufferedWriter 是一個裝飾器為Writer 提供緩衝功能
  4. PrintWriter 和PrintStream 極其類似,功能和使用也非常相似
  5. OutputStreamWriter 是OutputStream 到Writer 轉換的橋樑(轉換流,位元組輸出流 轉換為字元輸出流)

常用方法

// 寫入字元陣列
void write(char[] cbuf)
// 寫入字元陣列的某一部分
abstract void write(char[] cbuf, int off, int len)
// 寫入單個字元
void write(int c)
// 寫入字串
void write(String str)
// 寫入字串的某一部分
void write(String str, int off, int len)

// 將指定字元新增到此 writer
Writer append(char c)
// 將指定字元序列新增到此 writer
Writer append(CharSequence csq)
// 將指定字元序列的子序列新增到此 writer.Appendable
Writer append(CharSequence csq, int start, int end)

// 關閉此流,但要先重新整理它
abstract void close()
// 重新整理該流的緩衝
abstract void flush()

2.5 位元組流和字元流使用情況

字元流和位元組流的使用範圍:位元組流一般用來處理影像,視訊,以及PPT,Word型別的檔案。
字元流一般用於處理純文字型別的檔案,如TXT檔案等。
位元組流可以用來處理純文字檔案,但是字元流不能用於處理影像視訊等非文字型別的檔案。

2.6 字元流與位元組流轉換

轉換流的作用,文字檔案在硬碟中以位元組流的形式儲存時,通過InputStreamReader讀取後轉化為字元流給程式處理,程式處理的字元流通過OutputStreamWriter轉換為位元組流儲存。

轉換流的特點:

  1. 其是字元流和位元組流之間的橋樑
  2. 可對讀取到的位元組資料經過指定編碼轉換成字元
  3. 可對讀取到的字元資料經過指定編碼轉換成位元組

何時使用轉換流?

  1. 當位元組和字元之間有轉換動作時
  2. 流操作的資料需要編碼或解碼時

具體的物件體現:

  1. InputStreamReader: 位元組到字元的橋樑
  2. OutputStreamWriter: 位元組到字元的橋樑

這兩個流物件是字元體系中的成員,它們有轉換作用,本身又是字元流,所以在構造的時候需要傳入位元組流物件進來。

OutputStreamWriter(OutStreamout):從字元流到位元組流的橋樑。
InputStreamReader(InputStream in):將位元組流以字元流輸入。


> InputStreamReader:位元組流轉字元流,它使用的字符集可以由名稱指定或顯式給定,否則將接受平臺預設的字符集。

```java
//構造方法:
 	// 建立一個使用預設字符集的 InputStreamReader
	InputStreamReader(InputStream in)
	// 建立使用給定字符集的 InputStreamReader
	InputStreamReader(InputStream in, Charset cs)
	// 建立使用給定字符集解碼器的 InputStreamReader
	InputStreamReader(InputStream in, CharsetDecoder dec)
	// 建立使用指定字符集的 InputStreamReader
	InputStreamReader(InputStream in, String charsetName)
//特有方法:
    //返回此流使用的字元編碼的名稱 
    String getEncoding() 
//使用預設編碼		
InputStreamReader reader = new InputStreamReader(new FileInputStream("test.txt"));
int len;
while ((len = reader.read()) != -1) {
	System.out.print((char) len);//愛生活,愛Android
}
	reader.close();
		
//指定編碼	
InputStreamReader reader = new InputStreamReader(new FileInputStream("test.txt"),"utf-8");
int len;
while ((len = reader.read()) != -1) {
	System.out.print((char) len);//愛生活,愛Android
}
reader.close();

OutputStreamWriter:從字元流到位元組流的橋樑

//構造方法:
 	// 建立使用預設字元編碼的 OutputStreamWriter
	OutputStreamWriter(OutputStream out)
	// 建立使用給定字符集的 OutputStreamWriter
	OutputStreamWriter(OutputStream out, Charset cs)
	// 建立使用給定字符集編碼器的 OutputStreamWriter
	OutputStreamWriter(OutputStream out, CharsetEncoder enc)
	// 建立使用指定字符集的 OutputStreamWriter
	OutputStreamWriter(OutputStream out, String charsetName)
//特有方法:
    //返回此流使用的字元編碼的名稱 
    String getEncoding() 

2.7 位元組流和字元流的區別

位元組流和字元流的區別

位元組流沒有緩衝區,是直接輸出的,而字元流是輸出到緩衝區的。因此在輸出時,位元組流不呼叫colse()方法時,資訊已經輸出了,而字元流只有在呼叫close()方法關閉緩衝區時,資訊才輸出。要想字元流在未關閉時輸出資訊,則需要手動呼叫flush()方法。

在這裡插入圖片描述

  • 讀寫單位不同:位元組流以位元組(8bit)為單位,字元流以字元為單位,根據碼錶對映字元,一次可能讀多個位元組。
  • 處理物件不同:位元組流能處理所有型別的資料(如圖片、avi等),而字元流只能處理字元型別的資料。

結論:只要是處理純文字資料,就優先考慮使用字元流。除此之外都使用位元組流。

3. System類對IO的支援

在這裡插入圖片描述

針對一些頻繁的裝置互動,Java語言系統預定了3個可以直接使用的流物件,分別是:

  • System.in(標準輸入),通常代表鍵盤輸入。
  • System.out(標準輸出):通常寫往顯示器。
  • System.err(標準錯誤輸出):通常寫往顯示器

標準I/O

  • Java程式可通過命令列引數與外界進行簡短的資訊交換,同時,也規定了與標準輸入、輸出裝置,如鍵盤、顯示器進行資訊交換的方式。而通過檔案可以與外界進行任意資料形式的資訊交換

4.處理流BufferedReader,BufferedWriter,BufferedInputStream,BufferedOutputStream

緩衝流原理圖解(緩衝流也叫高效流)

在這裡插入圖片描述

BufferedOutputsStream,都要包上一層節點流。也就是說處理流是在節點流的基礎之上進行的,帶有Buffered的流又稱為緩衝流,緩衝流處理檔案的輸入輸出的速度是最快的。所以一般緩衝流的使用比較多。

BufferedInputStream:位元組緩衝輸入流,提高了讀取效率

//構造方法:
	 // 建立一個 BufferedInputStream並儲存其引數,即輸入流in,以便將來使用。
	 BufferedInputStream(InputStream in)
	 // 建立具有指定緩衝區大小的 BufferedInputStream並儲存其引數,即輸入流in以便將來使用
	 BufferedInputStream(InputStream in, int size)
InputStream in = new FileInputStream("test.txt");
// 位元組快取流
BufferedInputStream bis = new BufferedInputStream(in);
byte[] bs = new byte[20];
int len = 0;
while ((len = bis.read(bs)) != -1) {
	System.out.print(new String(bs, 0, len));
	// ABCD
	// hello
}
// 關閉流
bis.close();

BufferedOutputStream:位元組緩衝輸出流,提高了寫出效率。

//構造方法:
	 // 建立一個新的緩衝輸出流,以將資料寫入指定的底層輸出流
	 BufferedOutputStream(OutputStream out)
	 // 建立一個新的緩衝輸出流,以將具有指定緩衝區大小的資料寫入指定的底層輸出流
	 BufferedOutputStream(OutputStream out, int size)
	 
//常用方法:
	 // 將指定 byte 陣列中從偏移量 off 開始的 len 個位元組寫入此緩衝的輸出流
	 void write(byte[] b, int off, int len)
	 // 將指定的位元組寫入此緩衝的輸出流
 	 void write(int b)
	 // 重新整理此緩衝的輸出流
	 void flush()
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test.txt", true));
// 輸出換行符
bos.write("\r\n".getBytes());
// 輸出內容
bos.write("Hello Android".getBytes());
// 重新整理此緩衝的輸出流
bos.flush();
// 關閉流
bos.close();

BufferedReader:字元緩衝流,從字元輸入流中讀取文字,緩衝各個字元,從而實現字元、陣列和行的高效讀取。

//構造方法:
    // 建立一個使用預設大小輸入緩衝區的緩衝字元輸入流
	BufferedReader(Reader in)
	// 建立一個使用指定大小輸入緩衝區的緩衝字元輸入流
	BufferedReader(Reader in, int sz)
//特有方法:
    // 讀取一個文字行
	String readLine()
//生成字元緩衝流物件
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("test.txt")));
String str;
//一次性讀取一行
while ((str = reader.readLine()) != null) {
	System.out.println(str);// 愛生活,愛Android
}

//關閉流
reader.close();

BufferedWriter:字元緩衝流,將文字寫入字元輸出流,緩衝各個字元,從而提供單個字元、陣列和字串的高效寫入

//構造方法:
 	// 建立一個使用預設大小輸出緩衝區的緩衝字元輸出流
	BufferedWriter(Writer out)
	// 建立一個使用給定大小輸出緩衝區的新緩衝字元輸出流
	BufferedWriter(Writer out, int sz)
//特有方法:
 	// 寫入一個行分隔符
	void newLine() 

5.序列化

將儲存在記憶體中的物件資料轉化為二進位制資料流進行傳輸,任何物件都可以序列化。

實現方法:實現 java.io.Serializable 介面

作用: 把一個Java物件寫入到硬碟或者傳輸到網路上面的其它計算機,這時我們就需要自己去通過java把相應的物件寫成轉換成位元組流。對於這種通用的操作,我們為什麼不使用統一的格式呢?沒錯,這裡就出現了java的序列化的概念。在Java的OutputStream類下面的子類ObjectOutput-Stream類就有對應的WriteObject(Object object) 其中要求對應的object實現了java的序列化的介面。

在使用tomcat開發JavaEE相關專案的時候,我們關閉tomcat後,相應的session中的物件就儲存在了硬碟上,如果我們想要在tomcat重啟的時候能夠從tomcat上面讀取對應session中的內容,那麼儲存在session中的內容就必須實現相關的序列化操作,還有jdbc載入驅動用的就是反序列化,將字串變為物件。

//序列化類:java.io.ObjectOutputStream
 
//講物件變為指定的二進位制資料
 
class Book implements Serializable{
 
	private String title;
 
	private double price;
 
	public Book(String tit,double pri){
 
		this.title=tit;
 
		this.price=pri;
 
	}
 
	public String toString() {
 
		return "書名:"+this.title+",價格:"+this.price;
 
	}
 
}
 
public class Demo10 {
 
	public static void main(String[] args) throws Exception {
 
//序列化到指定的文字
 
		ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(new File("e:"+File.separator+"demoA.txt")));
 
		oos.writeObject(new Book("java開發", 45.2));
 
		oos.close();
 
	}

}

6.反序列化

將二進位制資料換回原物件

構造方法:

  • ObjectInputStream(InputStream in)

方法:

  • Object readObject() 從 ObjectInputStream 讀取物件
 
class Book implements Serializable{
 
	private String title;
 
	private double price;
 
	public Book(String tit,double pri){
 
		this.title=tit;
 
		this.price=pri;
 
	}
 
	public String toString() {
 
		return "書名:"+this.title+",價格:"+this.price;
 
	}
 
}
 
public class Demo11 {
 
	public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
 
		ObjectInputStream ois=new ObjectInputStream(new FileInputStream(new File("e:"+File.separator+"demoA.txt")));
 
		Object obj=ois.readObject();
 
		Book book=(Book) obj;
 
		System.out.println(book);
 
		ois.close();
 
	}
 
}

transient關鍵字(一個類某些屬性不需要序列化)

以上序列化和反序列化實現了的物件序列化,但是可以發現,操作時是將整個物件的所有屬性序列化,那麼transient關鍵字可以將某些內容不需要儲存,就可以通過transient關鍵字來定義

private transient string title;//此時title屬性無法被序列化,

相關文章