:::info <a href="/@TFA101/CherryJava" target="_self"> < :notebook_with_decorative_cover: [共享目錄] Java 2 學習筆記</a> ::: # 模組8-9 循序存取媒體:I/O鍊、Reader/Writer鍊 本筆記會提到的內容: * I/O和Reader/Writer鍊是什麼 * 串接時的注意事項 * 操作的大致順序 * 資源釋放的順序 * 位元與字元資料流的轉換 ## I/O鍊 資料流分低階的「節點資料流」與高階的「處理資料流」,節點資料流負責與源頭及目的地相接,處理資料流再對節點資料流接回來的資料進行特殊的讀寫或加工,如此串接形成的結構就稱為「鍊」。 I/O鍊顧名思義就是代表以I/O資料流處理資料的輸入、輸出。 ### 重要注意事項 不管是輸入或輸出資料流都會遇到一些必須處理的例外(Exception),看是要在類別throws出去給呼叫者,還是直接做try catch處理,這邊暫時先不討論。 使用I/O鍊本身的注意事項如下: 1. Source和Dest一定要用**低階資料流**來存取。 <div style="margin-bottom:16px;" title="保持間距用"></div> 2. 使用高階資料流的建構子包住低階資料流的物件,建造高階資料流的物件後,再**用此高階資料流產生的物件,去控制低階資料流**的動作,如: ```=java FileOutputStream fos = new FileOutputStream("輸入檔案路徑\\檔案名稱"); BufferedOutputStream bos = new BufferedOutputStream(fos); bos.write(); // 輸出資料 bos.flush(); // 強制沖出所有暫存區的資料 bos.closed(); // 關閉BufferedOutputStream bos資料流 fos.closed(); // 關閉FileOutputStream fis資料流 ``` <div style="margin-bottom:16px;" title="保持間距用"></div> 3. 高階I/O類別可以再與其他高階I/O類別串接,但輸入資料流只能接輸入資料流,輸出資料流只能接輸出資料流。 > 不然要胃食道逆流? ## InputStream Chain 流程操作大致如下: 1. **建立一個低階I/O輸入資料流物件** ```java=1 FileInputStream fis = new FileInputStream("輸入檔案路徑\\檔案名稱"); ``` 2. **建立一個高階I/O輸入資料流物件(建構子包住低階I/O物件所產生)** ```java=2 BufferedInputStream bis = new BufferedInputStream(fis); ``` 3. **使用高階I/O輸入資料流操作各種功能** ```java=3 while(檢測條件) { System.out.print((char)bis.read()); // 與高階資料流BufferedInputStream的物件bis讀取字元byte code並轉成可讀取的字元 System.out.print(bis.readLine()); // BufferedInputStream的特殊功能:整行讀取 } ``` 使用輸入資料流時記得要[在while迴圈做條件判定](https://hackmd.io/@RtaWwakYTeOSfTzbFkNCTw/r1ooxuUmu#read-階段的檢測讀入動作)再來執行,不然只會讀一個字或一行就不會繼續了啊喂。 4. **關閉資料流,釋放資源(越晚建立,越早關閉)** ```java=7 bis.close(); // 關閉BufferedInputStream bis資料流 fis.close(); // 關閉FileInputStream fis資料流 ``` ## OutputStream Chain 流程操作大致如下: 1. **建立一個低階I/O輸出資料流物件** ```java=1 FileOutputStream fos = new FileOutputStream("輸入檔案路徑\\檔案名稱"); ``` 2. **建立一個高階I/O輸出資料流物件(建構子包住低階I/O物件所產生)** ```java=2 BufferedOutputStream bos = new BufferedOutputStream(fos); ``` 3. **輸出之前麻煩不要忘了會幫你輸出的 PrintSteam 資料流** 這~~東西~~類別就是給我們用來輸出8 bits資料的,麻煩不要忘記它,而且我們常用的 `System.out.println();` 就是這個老大哥的方法,JDK 1.0就誕生了啊! (啊Read和Write麻煩使用對16 bits字元支援更好的 PrintWriter) ```java=3 PrintStream ps = new PrintStream(bos); ``` 4. **print; 或 println; 印出資料** ```java=4 ps.println("Hello World!"); ``` 把它想成 `System.out.println();` 在做事也可以。 ```java=5 ps.flush(); ``` 必要的時候可以把管線裡面的資料沖出來,確保資料完整輸出。 5. **關閉資料流,釋放資源(越晚建立,越早關閉)** ```java=6 ps.close(); bos.close(); fos.close(); ``` ## Reader/Writer Chain 怎麼辦,我懶得寫了,反正就是一樣啦(?)。 和I/O鍊的差別只在於輸入、輸出的抽象父類別分別改為Reader及Writer。 至於操作資料的方法,輸入可以使用 `readLine()` (BufferedReader的方法),輸出應該使用 `write();` (PrintWriter的方法)。 ## 位元與字元資料流轉換 因為網路I/O與Console I/O是位元資料流,所以Reader/Writer不能直接存取,需要進行資料流的轉換。 ### InputStreamReader byte輸入為char的管線。 ```java=1 FileInputStream fis = new FileInputStream("輸入檔案路徑\\檔案名稱"); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); ``` ### OutputStreamWriter char輸出為byte的管線。 ```java=1 FileOutputStream fos = new FileOutputStream("輸出檔案路徑\\檔案名稱"); OutputStreamReader osr = new OutputStreamReader(fos); OutputStreamWriter osr = new OutputStreamWriter(OutputStream in); ```