[Java] Java I/O資料流傳輸工具整理

由於在Java I/O流中總是分不清楚有那些工具可以使用,也常常搞不懂之中的繼承關係,也搞不清楚之中有哪些方法可以使用,本文將有關常見資料流的類別與方法整理出來,提供大家參考

Liu Cheng Lee
技術學習
Oct 2, 2020

--

本文的架構主要如下:

  • 什麼是I/O資料流?
  • 資料流四大類別
  • 低階流與高階流
  • 各個資料流類別使用時機與方法

首先來說說什麼是I/O資料流?

I/O事實上是Input以及Output兩字的縮寫,是指使用Java進行資料的傳輸(存取)。這邊的Input指的是將源頭資料傳輸到程式之中,Output則是從程式輸出到指定的目的地。
首先我們從API文件閱讀開始,進入Java API文件後,可以發現到Java運用在資料流的類別都在java.io這個套件底下,很特別的是我們可以看到除了Input以及Output這個組合之外,還有另一個組合是Reader以及Writer。
這邊Reader指的是將源頭資料讀入程式之中,Writer則是將程式寫出到指定的目的地。

為何已經有了Input以及Output這個組合,還需要有Reader以及Writer這個組合呢?

原因是Input以及Output組合被設計出來時為JDK1.0版本,但當時並未考量到非英文字元的傳輸,由於英文字元的單個字都是一個位元,現行萬國碼將文字做一個完整的編碼統合,但萬國碼每個單字都是兩個位元,為了支援其他類型的資料傳輸因此又多了Reader以及Writer這項組合。
Reader以及Writer對於文字的支援度更完整。

資料流四大類別

因此資料流的工具都是在java.io套件下,又以InputStream、OutputStream、Reader、Writer這四個為主軸。將彼此的關係繪製成圖的話就像下圖這樣:

InputStream、OutputStream、Reader、Writer
這四個類別又分別有底下又有其他工具可以使用,上圖僅包含部分常用的資料流類別。

首先要先知道一般資料的傳輸類別都有以下幾個方法可以使用:

  • 輸入程式:
  1. read():指讀下一個字元,通常會搭配迴圈使用,進行資料的讀入。
  2. available():取得源頭資料的大小,File類別則是用length()方法來知道檔案大小。
  3. close():所有資料流類別執行結束都需要關閉,不然會出錯。
  • 輸出程式:
  1. write():將資料寫出,通常也會搭配迴圈使用,將資料自程式傳輸自目的地。
  2. flush():中文是沖的意思,在傳輸過程中,通常會使用到緩衝機制,資料有可能會傳輸不完整,因此使用此方法將剩餘資料傳輸出去。(後方說明Buffer時候會有完整說明)
  3. close():所有資料流類別執行結束都需要關閉,不然會出錯。

低階流 & 高階流

依照功能區分的話可以分為低階流以及高階流:

  • 低階I/O(節點資料流):負責與資料源頭與目的地相連。
  • 高階I/O(處理資料流):處理其他功能,如設立緩衝/型別轉換等。

依照低階流與高階流區分分類如下:

  • 低階I/O(節點資料流):

-InputStream
-FileInputStream
-OutputStream
-FileOutputStream
-FileReader
-FileWriter

  • 高階I/O(處理資料流):

-BufferedInputStream
-BufferedOutputStream
-BufferedReader
-BufferedWriter
-InputStreamReader
-OutputStreamWriter
-ObjectInputStream
-ObjectOutputStream
-PrintWriter
-PrintStream

需要注意的是任何高階I/O要使用需要與低階I/O串接使用,高階I/O無法連接資料元頭或者目的地,如果API中此I/O類別的"建構子"需要連接一個低階I/O,則表示這是高階I/O,是Java多型的展現。

各個資料流類別使用時機與方法

  • FileInputStream/FileOutputStream/FileReader/FileWriter(檔案類別):
    用在檔案的讀入與寫出,後方的建構子接的通常為檔案的位置,如果該檔案不存在則會自動建立該檔案,這是與File類別最不同的地方,當File類別new產生物件時,並不會建立檔案。
    FileOutputStream/FileWriter兩者的建構子可以設定append。
    append為true時,新增加的資料會附加在原始資料後。
    append為false時,新增加的資料會覆蓋原始資料後。(預設)
  • BufferedInputStream/BufferedOutputStream/BufferedReader/BufferedWriter(緩衝類別):
    緩衝是用以提高效率
    ,如果使用低階資料流,由於硬碟的執行效率比記憶體慢很多,因此緩衝的概念是將待處理的資料在記憶體中集滿一次解決,減少硬碟的傳輸次數,藉此達到提升效率的功能。
    但是使用緩衝將資料集中,資料傳輸到尾端可能資料量較少,因此記憶體沒有集滿,程式可能因為沒有集滿因此沒有傳輸,導致傳輸失敗,此狀況就需要使用flush()方法將尾部殘餘資料強制送出
    此外,BufferedReader之中又有一個readline()方法,此方法最好用的地方在於,一般資料流傳輸回的資料是int型別,通常我們要傳輸的資料往往是字串,如此一來,我們還需要做到型別轉換的工作。但是readline()可以將文字一行一行讀入,且回傳就是String字串,因此使用BufferedReader搭配readline()方法,不僅有緩衝的功能,同時省去行別轉換的麻煩。
  • InputStreamReader/OutputStreamWriter(位元類別):
    由於網路是位元資料因此普通的Reader/Writer無法傳輸,因此可以使用InputStreamReader/OutputStreamWriter,此類別主要可以做到類別的轉換。透過套接InputStream/OutputStream可以傳輸位元資料,可以用於網路資料讀取、爬蟲等等功用。
    另外此類別的建構子可以宣告編碼類型,因此也可以用來做不同編碼的資料傳輸。
  • PrintWriter/PrintStream(印出類別):
    印出這個類別只有輸出沒有輸入,其實PrintStream就是經常使用的System.out.println(); (方法叫用是Java提供的縮寫),PrintWriter同樣也有println()方法,用來印出資料。通常也可以用來將程式之中的字串搭配其他資料流輸出到檔案。
  • ObjectInputStream/ObjectOutputStream(物件類別):
    必須要實作序列化(Serializable)空介面,實作此介面是實現介面"貼標籤"的功能。

    物件是在執行時期才會產生,當物件消失時,所保存的屬性(資料)也會跟著消失,為了對物件永久保存,因此有對物件永久保存/傳輸的需求。
    此為高階I/O因此也需要搭配低階I/O使用。
    此資料流讀入與寫出方法如下:
    讀入:readObject()
    寫出:writeObject(Object obj)
    比較要注意的地方是寫出會將輸入的物件轉為Object類別,目的是將所有不同物件轉為同一個類別才能輸出,符合參數規則,是一種多型的展現。
    讀入的時候記得要轉為原始宣告的型別,才能進行類別方法的呼叫!

I/O資料流是公司面試、認證考試以及工作中經常會碰到的內容,本文比較多硬貨,比較好建議的學習方式是將上方的的架構圖看熟,之後在精讀下方的細節重點與操作,在使用的時候一邊搭配API文件服用。

喜歡我的文章可以幫我拍拍手,會給予我很大的鼓勵,謝謝!

#技術學習 #後端程式設計

--

--

Liu Cheng Lee
技術學習

畢業於北京大學研究所─計算機技術。曾任美團點評產品運營,希望大家一起跟我研究互聯網的相關專業內容。現任緯創資通IT部門。金融/會計/計算機專業背景。此外我也是日本旅行愛好者,喜歡研究日文分享日本小事。也喜歡學習設計跟藝術,透過記錄教學相長!是一顆小白菜,如有錯誤歡迎指正!