首頁/ 汽車/ 正文

用 Java 的 IO 流進行讀寫檔案操作

前面四篇文章給大家彙總了一下

Java 程式設計中那些繞不開的介面

,給大家介紹了 Java 的幾個基礎 Interface 以及內建的函式式介面,接下來兩篇文章我們簡單的過一下Java IO 方面的基礎,本篇文章會先大概說下 Java 的 IO 流,以及怎麼用它們讀寫檔案,下一篇會講一些檔案系統相關的操作,比如怎麼建立刪除檔案或者目錄這樣的常用操作。話不多說,一起開始我們的學習吧!

前言

在計算機領域裡 IO,有時也寫作 I/O,是Input / Output的縮寫,也就是輸入和輸出。這裡的輸入和輸出是指不同系統之間的資料輸入和輸出,比如讀寫檔案資料,讀寫網路資料等等。

本文內容大綱如下:

用 Java 的 IO 流進行讀寫檔案操作

Java 有哪些IO框架

Java 中有三代 IO 框架,分別是第一代的同步阻塞 IO (也叫 BIO, Blocking IO),第二代的NIO ,可以構建多路複用的、同步非阻塞 IO 程式,同時提供了更接近作業系統底層的高效能資料操作方式。第三代 NIO2 有的地方也叫 AIO,即Async IO,進一步支援了非同步IO。

這些 IO 框架都是針對檔案的,網路通訊同樣屬於 IO 行為,但是被 Java 單獨放在了 java。net 包下,不在這裡說的 IO 體系內。

這個教程中我們來學習 Java IO 體系中最簡單和易於理解的同步阻塞 IO,後面有了這裡的知識積累後再去進一步學習 NIO 和 AIO。

BIO 簡介

同步阻塞 IO 即 BIO(blocking IO),指的主要是傳統的 java。io 包,它基於流模型實現。java。io 包提供了我們最熟知的一些 IO 功能,比如我們已經介紹過的 File 物件提供的檔案和目錄操作,還有一大塊就是透過輸入輸出流讀寫檔案等。

BIO 互動方式是同步、阻塞的方式,也就是說,在讀取輸入流或者寫入輸出流時,在完成之前,執行緒會一直阻塞在那裡。多個 IO 呼叫的執行順序是線性順序。不過 BIO 的優點是程式碼比較簡單、直觀,雖然不適合在高併發場景下使用,但足夠應對普通場景,同時也更容易學習和掌握。

IO 流

IO 流是 Java IO 中的核心概念。流是在概念上表示無窮無盡的資料流。IO 流連線到資料來源或資料的目的地,連線到資料來源的叫輸入流,連線到資料目的地的叫輸出流。 Java 程式不能直接從資料來源讀取和向資料來源寫入,只能藉助 IO 流從輸入流中讀取資料,向輸出流中寫入資料。

Java IO 中的流可以是基於位元組的(讀取和寫入位元組)也可以基於字元的(讀取和寫入字元),所以分為位元組流和字元流,兩類流根據流的方向都可以再細分出輸入流和輸出流。

位元組流

輸入位元組流:InputStream 輸出位元組流:OutputStream

字元流

輸入字元流:Reader 輸出字元流:Writer

用 Java 的 IO 流進行讀寫檔案操作

這裡有一點可能容易讓人迷惑的是,IO中的輸入和輸出指的是相當於程式的輸入和輸出,程式向外輸出內容,會向輸出流裡寫入,雖然寫入操作看似是輸入,但相對於程式本身而言它是在向外輸出內容。所以程式寫的是OutputStream 讀的是InputStream。

位元組流

位元組流主要操作位元組資料或二進位制物件。 位元組流有兩個核心抽象類:InputStream 和 OutputStream。所有的位元組流類都繼承自這兩個抽象類。 ##

用 Java 的 IO 流進行讀寫檔案操作

字元流和字元流 字元流操作的是字元,字元流有兩個核心類:Reader 類和 Writer 。所有的字元流類都繼承自這兩個抽象類。

用 Java 的 IO 流進行讀寫檔案操作

位元組流、字元流怎麼選擇

位元組流和字元流都有 read()、write()、flush()、close() 這樣的方法,這決定了它們的操作方式近似。

位元組流的資料是位元組(二進位制物件)。主要核心類是 InputStream 類和 OutputStream 類。

字元流的資料是字元,主要核心類是 Reader 類和 Writer 類。

所有的檔案在硬碟或傳輸時都是以位元組方式儲存的,例如圖片,影音檔案等都是按位元組方式儲存的。字元流無法讀寫這些檔案。

所以,除了純文字資料檔案使用字元流以外,其他檔案型別都應該使用位元組流方式。

位元組流到字元流的轉換可以使用 InputStreamReader 和 OutputStreamWriter。使用 InputStreamReader 可以將輸入位元組流轉化為輸入字元流,使用OutputStreamWriter可以將輸出位元組流轉化為輸出字元流。

下面我們透過一個讀寫文字檔案的程式來演示一下位元組流到字元流的轉換以及 Java IO 的基本操作。

實踐——Java IO 讀寫文字檔案

Java IO 中的類非常多,對應的方法也很多,一一羅列會導致內容過於枯燥,所以我們寫兩個用 IO 流寫檔案和讀檔案的例子,來展示下怎麼使用 IO 流讀寫檔案。

下面主要使用的是 FileOutputStream / FileInputStream 把 IO 流繫結到 File 物件上,然後將這兩個位元組流透過OutputStreamReader / InputStreamReader 轉換為字元流,並設定字元編碼,最後再用 PrintWriter / BufferedReader 給位元組流增加緩衝更能,讓程式能更方便地以行為單位操作 IO 流。

理解和掌握了這兩個基本的用法後,其他 IO 流的使用也就不是什麼難事兒了。

寫檔案示例程式

我們先來個寫檔案的示例小程式,在這個程式裡面除了用到了 Java 檔案、位元組輸出流等相關的知識外,還會用到我們前面在 Java 異常通關指南里講過的幫助我們自動回收已開啟資源的 try-with-resource形式的異常處理,Java 互動式獲取命令列輸入的 Scanner工具等。算是對我們專欄以前知識的一個實踐應用和複習。

如果你對這些知識還有點生疏或者忘記了,也不用先著急回看,在這個示例程式的註釋裡會把這些知識點進行相關提示,下面也有對程式每個重要部分的詳細解釋,我們先來看例子。

這個例子裡我們執行程式後,Java 程式會在命令列介面等待使用者的輸入,先讓使用者從命令列介面輸入想要儲存內容的檔案的名字,再讓使用者輸入內容。內容支援多行輸入,直到遇到空行,程式會認為輸入完畢,然後 Java 用使用者指定的名字在專案目錄下建立一個檔案,最後把程式讀取到的所有內容輸入,寫到檔案裡去。

package com。learnfile;import java。io。*;import java。nio。charset。StandardCharsets;import java。util。Scanner;public class WriteToFilesAppMain { private static final Scanner in = new Scanner(System。in); public static void main(String[] args) throws IOException { File targetFile = createFile(); writeToFile(targetFile); System。out。println(“程式執行結束”); } private static void writeToFile(File targetFile) throws IOException { // 使用 try with resource 自動回收開啟的資源 try ( // 建立一個outputstream 建立一個從程式到檔案的byte資料傳輸流 FileOutputStream fos = new FileOutputStream(targetFile); // 建立一個可以使用outputstream的Writer,並制定字符集,這樣程式就能一個一個字元地寫入 OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets。UTF_8); // 使用PrintWriter, 可以方便的寫入一行字元 PrintWriter pw = new PrintWriter(osw); ) { System。out。println(“輸入的內容會實時寫入檔案,如果輸入空行則結束”); while (true) { String lineToWrite = in。nextLine()。trim(); System。out。println(“輸入內容為:” + lineToWrite); if (lineToWrite。trim()。isBlank()) { System。out。println(“輸入結束”); break; } else { pw。println(lineToWrite); // 真正用的時候不要寫一行就flush() 這裡只是演示 pw。flush(); } } // 平時用的時候放在外面 flush // pw。flush(); } catch (Exception ex) { ex。printStackTrace(); } } private static File createFile() throws IOException { System。out。println(“請輸入檔名:”); String fileName = in。nextLine()。trim(); File f = new File(“。” + File。separator + fileName +“。txt”); if (f。isFile()) { System。out。println(“目標檔案存在,刪除:” + f。delete()); } System。out。println(f。createNewFile()); return f; }}複製程式碼

這個示例程式裡我們需要重點關注以下幾個方面的知識點

上面例程裡使用了Scanner,以命令列互動的方式讓我們能輸入程式將要建立檔案的名稱和要往檔案裡寫入的內容。

Java 的 IO流在使用完成後需要統一呼叫close()方法把流關閉掉。IO流的關閉會讓程式釋放出它們佔用的記憶體資源,而且字元流操作時使用了緩衝區,並在關閉字元流時會強制將緩衝區內容輸出,如果不關閉流,則緩衝區的內容是無法輸出的。

如果想在不關閉流時,就將緩衝區的內容輸出到檔案,可以呼叫流的flush()方法強制清空流使用的緩衝區。

上面的示例程式裡我們使用了try-with-resource形式的異常處理,把資源的關閉交給了Java—— 在資源被使用完成後或者程式出現異常終止執行時都會由 Java 自動關閉在try-with-resource中開啟的流資源。

把字串內容寫入到檔案的程式中,我們首先使用了 FileOutputStream 把目標檔案繫結到位元組輸出流,再用 OutputStreamWriter 建立一個可以使用OutputStream的Writer,並指定其字符集為UTF_8,這樣程式就能一個字元一個字元地寫入檔案啦。

接下來程式在OutputStreamWriter 字元流的基礎上建立了 PrintWriter,使用 PrintWriter 可以讓程式方便地寫入字串,並且也可以透過它的 println 方法來自動處理換行符。

用 Java 程式完成檔案的寫入操作後,我們再來看看,給定一個檔案,怎麼用 Java 程式讀取器中的內容。

讀文字示例程式

我們先在要執行程式的目錄下,新增一個測試用的名為 file。txt 的文字檔案,如果是用Intelij IDEA 這樣的 IDE 工具執行程式的話,可以在專案根目錄下新增這個檔案。

建立好檔案後,在檔案裡隨便輸入幾行內容用於測試:

aaa一二三bbb四五六ccc七八九ddd錕斤拷燙燙屯屯複製程式碼

接下來我們用 Java 程式讀取這個檔案裡的內容,這次我們則是會用到 IO 輸入流相關的幾個類: FileInputStream, InputStreamReader, BufferedReader等;他們的具體功能作用我寫在了程式的註釋裡

package com。learnfile;import java。io。*;import java。nio。charset。StandardCharsets;public class ReadStringFromFileAppMain { private static final String SOURCE_FILE_NAME = “file1。txt”; public static void main(String[] args) { File sourceFile = new File(“。” + File。separator + SOURCE_FILE_NAME); ReadLineFromFile(sourceFile); } private static void ReadLineFromFile(File sourceFile) { try ( // 建立從檔案到程式的資料輸入(input)流 FileInputStream fis = new FileInputStream(sourceFile); // 用 InputStreamReader 包裝 byte流,並指定字元編碼,讓它能夠將讀出的byte轉為字元 InputStreamReader isr = new InputStreamReader(fis, StandardCharsets。UTF_8); // 增加緩衝功能,輸入輸出效率更高,並且可以一次讀一行 BufferedReader reader = new BufferedReader(isr) ) { String line = null; while ((line = reader。readLine()) != null) { System。out。println(line。trim()。toUpperCase()); } // 還可以從reader裡獲取 Stream,然後透過流操作+Lambda的形式完成 // reader。lines()。map(String::trim)。map(String::toUpperCase)。forEach(System。out::println); } catch (Exception ex) { ex。printStackTrace(); } }}複製程式碼

上面這個示例程式會用 Java 的 IO 輸入流從檔案中每次讀取一行,並把行內容應用 toUpperCase() 方法後輸出到終端,程式裡開啟的流資源的方式仍然是交給try-with-resource機制自動處理,這裡不再贅述。

和輸出流一樣,在讀取檔案內容的程式中我們也做了位元組流到字元流的轉換,先用 FileInputStream 把檔案繫結到了位元組流上,然後透過 InputStreamReader 把位元組流轉換為字元流,在這個過程中也是設定了字元編碼,最後又用 BufferedReader 為字元流增加緩衝功能,這樣讀取的效率會更高一些,透過它程式可以一次讀取檔案的一整行。

透過 BufferedReader 的 readLine()遍歷或者 lines() 獲取Stream 後進行流處理,都可以用 BufferedReader 完成檔案內容的遍歷讀取,上面在程式程式碼和註釋裡我們演示了兩種遍歷 BufferedReader 的方法。

總結

這篇文章我們介紹了 Java IO流裡面的各種流,比較難記,常用的怎麼把檔案轉成檔案流,檔案流轉成位元組和字元流以及設計字元的編碼都給大家介紹了,把文章中提供的兩個例子看明白差不多就算入門了。

下一篇文章我們在總結梳理一下 Java 中常用的目錄和檔案操作

相關文章

頂部