Java的基本使用之IO
1、IO的基本介紹
IO是指 Input/Output,即輸入和輸出。以內存為中心:
-
Input 指從外部讀入數據到內存,例如把文件從磁盤讀取到內存,從網絡讀取數據到內存等等。
-
Output 指把數據從內存輸出到外部,例如把數據從內存寫入到文件,把數據從內存輸出到網絡等等。
Java 代碼是在內存中運行的,所以數據也必須讀到內存才能對其進行處理,數據在 Java 代碼中的最終表示方式無非是 byte數組,字符串等這些 Java 數據類型,都必須存放在內存里。
從 Java 代碼來看,輸入實際上就是從外部,例如硬盤上的某個文件,把內容讀到內存,并且以 Java 提供的某種數據類型表示,例如,byte[],String,這樣,后續代碼才能處理這些數據。因為內存有“易失性”的特點,所以必須把處理后的數據以某種方式輸出,例如,寫入到文件。Output 實際上就是把 Java 表示的數據格式,例如,byte[],String等輸出到某個地方。
IO 流是一種順序讀寫數據的模式,它的特點是單向流動。數據類似自來水一樣在水管中流動,所以我們把它稱為IO流。IO流以byte(字節)為最小單位,因此也稱為字節流。
1.1、流的分類
按操作數據單位不同分為:字節流、字符流
按數據流的流向不同分為:輸入流、輸出流
按流的角色不同分為:節點流、處理流

Java 的 IO 流共涉及40多個類,但都是從 InputStream、OutputStream、Reader、Writer這四個抽象基類派生的。由這四個類派生出來的子類的名稱都以其父類名作為后綴。

1.2、輸入/輸出字節流(InputStream / OutputStream)
IO 流以 byte(字節)為最小單位,因此也稱為字節流。 在Java中,InputStream代表輸入字節流,OuputStream代表輸出字節流,這是最基本的兩種IO流。
例如,我們要從磁盤讀入一個文件,包含6個字節,就相當于讀入了6個字節的數據。

這6個字節是按順序讀入的,所以是輸入字節流。
反過來,我們把6個字節從內存寫入磁盤文件,就是輸出字節流:

1.3、字符流(Reader / Writer)
如果我們需要讀寫的是字符,并且字符不全是單字節表示的 ASCII 字符,那么,按照字符 char 來讀寫顯然更方便,這種流稱為字符流。Java提供了Reader和Writer表示字符流,字符流傳輸的最小數據單位是char。
例如,我們把 char[] 數組 “Hi你好” 這4個字符用Writer字符流寫入文件,并且使用UTF-8編碼,得到的最終文件內容是8個字節,英文字符 H 和 i 各占一個字節,中文字符 “你好” 各占3個字節。反過來,我們用Reader讀取以UTF-8編碼的這8個字節,會從Reader中得到Hi你好這4個字符。因此,Reader和Writer本質上是一個能自動編解碼的InputStream和OutputStream。使用Reader,數據源雖然是字節,但我們讀入的數據都是char類型的字符,原因是Reader內部把讀入的byte做了解碼,轉換成了char。使用InputStream,我們讀入的數據和原始二進制數據一模一樣,是byte[]數組,但是我們可以自己把二進制byte[]數組按照某種編碼轉換為字符串。
究竟使用Reader還是InputStream,要取決于具體的使用場景。如果數據源不是文本,就只能使用InputStream,如果數據源是文本,使用Reader更方便一些。Writer和OutputStream是類似的。
1.4、同步和異步IO
同步IO是指,讀寫IO時代碼必須等待數據返回后才繼續執行后續代碼,它的優點是代碼編寫簡單,缺點是CPU執行效率低。
而異步IO是指,讀寫IO時僅發出請求,然后立刻執行后續代碼,它的優點是CPU執行效率高,缺點是代碼編寫復雜。
Java標準庫的包java.io提供了同步IO,InputStream、OutputStream、Reader和Writer都是同步IO的抽象類,對應的具體實現類,以文件為例,有FileInputStream、FileOutputStream、FileReader和FileWriter。
java.nio則是異步IO。
2、File 對象
Java的標準庫java.io提供了File對象來操作文件和目錄。File對象既可以表示文件,也可以表示目錄。File 能新建、刪除、重命名文件和目錄,但 File 不能訪問文件內容本身,即只能操作文件或者目錄,但不能操作文件內容本身。如果需要訪問文件內容本身,需使用輸入/輸出流。
2.1、構造File對象(new File())
要構造一個File對象,需要傳入文件路徑:
import java.io.*; public class Main { public static void main(String[] args) { File f = new File("C:\\Windows\\notepad.exe"); System.out.println(f); } }
構造File對象時,既可以傳入絕對路徑,也可以傳入相對路徑。File 對象中傳入的相對路徑是相對于你當前 Java 文件所處的項目的根目錄而言的,而不是相對于當前 Java 文件的路徑。
(可以使用 \ 或者 / 作為路徑分隔符。因為 \ 在Java中是轉義字符,所以使用 \ 作為分隔符時需使用兩個 \ 即 \\ )
//絕對路徑:以根目錄開頭的完整路徑 File f = new File("C:\\Windows\\notepad.exe"); File f = new File("C:/Windows/notepad.exe"); //傳入相對路徑時,相對路徑前面加上當前項目的根目錄就是絕對路徑。比如下面當前目錄是C:\Docs File f1 = new File("sub\\javac"); // 則絕對路徑是C:\Docs\sub\javac File f3 = new File(".\\sub\\javac"); // 絕對路徑是C:\Docs\sub\javac File f3 = new File("..\\sub\\javac"); // 絕對路徑是C:\sub\javac
2.2、創建和刪除文件(createNewFile()、delete())
當File對象表示一個文件時,可以通過 File 對象的 createNewFile() 方法來創建一個新文件,如果創建成功,該方法將返回 true,如果該文件已經存在則返回 false,如果指定的目錄不存在將會拋出異常,不會自動創建目錄。
//比如當前的項目根目錄為F:\JavaSE_WorkSpace\demo01 File file = new File(".\\path\\file.txt"); //F:\JavaSE_WorkSpace\demo01\path\file.txt if (file.createNewFile()) { //如果文件的目錄不存在將會報錯 System.out.println("創建成功"); }
可以用 delete() 方法來刪除一個文件,當刪除成功時返回 true,否則返回 false,該方法不會因為沒有找到目錄而報錯
File file = new File(".\\path\\file.txt"); if (file.delete()) { System.out.println("刪除成功"); }
有些時候,程序需要讀寫一些臨時文件,File對象提供了createTempFile()來創建一個臨時文件,該方法將會在默認的臨時文件目錄下創建文件,比如:C:\Users\張三\AppData\Local\Temp\ 目錄下。
可以用deleteOnExit()在JVM退出時自動刪除該臨時文件。
File f = File.createTempFile("tmp-", ".txt"); // 提供臨時文件的前綴和后綴
//f.deleteOnExit(); // 該方法將會在JVM退出時自動刪除該文件
2.3、創建目錄
和文件操作類似,File對象如果表示一個目錄,可以通過以下方法創建和刪除目錄:
boolean mkdir():創建當前File對象表示的目錄。只能創建一層目錄,如果父級不存在需要一層層調用先創建父級再創建子級目錄boolean mkdirs():創建當前File對象表示的目錄。該方法可以創建多層,即在必要時會自動將不存在的父目錄也創建出來;boolean delete():刪除當前File對象表示的目錄,當前目錄必須為空才能刪除成功。
2.4、遍歷文件和目錄(list()、listFiles())
當File對象表示一個目錄時,可以使用list()和listFiles()列出目錄下的文件和子目錄名。
list() 方法返回目錄下的文件或者子目錄的名稱字符串數組。listFiles() 方法返回目錄下的文件或者子目錄的 File 對象數組,listFiles() 提供了一系列重載方法,可以過濾不想要的文件和目錄。
public static void main(String[] args) throws IOException { File f = new File("F:\\JavaSE_WorkSpace2\\day01"); File[] fs1 = f.listFiles(); // 得到該目錄下的所有文件和子目錄 printFiles(fs1); File[] fs2 = f.listFiles(new FilenameFilter() { //限制僅列出.exe文件 public boolean accept(File dir, String name) { return name.endsWith(".exe"); // 返回true表示接受該文件 } }); printFiles(fs2); } //封裝一個循環遍歷函數 static void printFiles(File[] files) { if (files != null) { for (File f : files) { System.out.println(f); } } System.out.println("遍歷結束"); }
2.5、File對象的一些其他方法
File對象既可以表示文件,也可以表示目錄。特別要注意的是,構造一個File對象,即使傳入的文件或目錄不存在,代碼也不會出錯,因為構造一個File對象,并不會導致任何磁盤操作。只有當我們調用File對象的某些方法的時候,才真正進行磁盤操作。
例如,調用isFile(),判斷該File對象是否是一個已存在的文件,調用isDirectory(),判斷該File對象是否是一個已存在的目錄。
用File對象獲取到一個文件時,還可以進一步判斷文件的權限和大小:
boolean canRead():是否可讀;boolean canWrite():是否可寫;boolean canExecute():是否可執行;long length():文件字節大小。
對目錄而言,是否可執行表示能否列出它包含的文件和子目錄。
File f1 = new File("C:\\Windows"); System.out.println(f1.isFile()); //false System.out.println(f1.isDirectory()); //true
2.6、三種類型的路徑(絕對路徑、相對路徑、規范路徑)
File 對象有三種形式表示的路徑,包括絕對路徑、相對路徑和規范路徑,規范路徑就是去掉相對路徑中的 . 和 .. 符號,得出一個絕對路徑。
請注意,File 對象中傳入的相對路徑是相對于你當前 Java 文件所處的項目的根目錄而言的,而不是相對于當前 Java 文件的路徑。
//比如目前的Java文件存儲在 E:\workspace\demo01 項目的 default 包下 File f = new File(".\\path\\file.txt"); System.out.println(f.getPath()); //獲取創建File對象時傳入的路徑 .\path\file3.txt System.out.println(f.getAbsolutePath()); //獲取絕對路徑 E:\workspace\demo01\.\path\file3.txt System.out.println(f.getCanonicalPath()); //獲取規范路徑 E:\workspace\demo01\path\file3.txt
3、輸入字節流(InputStream)
InputStream 就是 Java 標準庫提供的最基本的輸入流。它位于 java.io 這個包里。java.io 包提供了所有同步IO的功能。
InputStream 并不是一個接口,而是一個抽象類,它是所有輸入流的超類。這個抽象類定義的一個最重要的方法就是 int read(),簽名如下:
//該方法會讀取輸入流的下一個字節,并返回字節表示的int值(0~255,一個字節的十進制范圍就是0~255)。如果已讀到末尾,返回-1表示不能繼續讀取了。 public abstract int read() throws IOException;
read() 方法是阻塞(Blocking)的,在調用InputStream的read()方法讀取數據時,必須等到 read() 方法返回后才會繼續執行下一段代碼。
3.1、讀取文件(文件輸入字節流 FileInputStream)
FileInputStream是InputStream的一個子類。顧名思義,FileInputStream就是從文件流中讀取數據。下面的代碼演示了如何完整地讀取一個FileInputStream的所有字節:
public void readFile() throws IOException { //拋出IO異常 InputStream input = null; try { input = new FileInputStream("src/readme.txt"); int n; while ((n = input.read()) != -1) { // 利用while同時讀取并判斷 System.out.println(n); } } finally { //在finally里保證InputStream一定能關閉 if (input != null) { input.close(); } //通過close()方法來關閉流 } }
在計算機中,類似文件、網絡端口這些資源,都是由操作系統統一管理的。應用程序在運行的過程中,如果打開了一個文件進行讀寫,完成后要及時地關閉,以便讓操作系統把資源釋放掉。否則,應用程序占用的資源會越來越多,不但白白占用內存,還會影響其他應用程序的運行。
InputStream和OutputStream都是通過close()方法來關閉流。關閉流就會釋放對應的底層資源。
在讀取或寫入IO流的過程中,可能會發生錯誤,例如,文件不存在導致無法讀取,沒有寫權限導致寫入失敗,等等,這些底層錯誤由Java虛擬機自動封裝成IOException異常并拋出。因此,所有與IO操作相關的代碼都必須正確處理IOException。我們可以在 finally 里面調用 close 來保證不管 InputStream 是否發生錯誤都能正常關閉。
我們可以用Java 7引入的新的try(resource)的語法來讓編譯器自動關閉資源。只需要編寫try語句,編譯器就會自動查看看try(resource = ...)中的對象是否實現了java.lang.AutoCloseable接口,如果實現了,就自動加上finally語句并調用close()方法為我們關閉資源。
public void readFile() throws IOException { try (InputStream input = new FileInputStream("src/readme.txt")) { int n; while ((n = input.read()) != -1) { System.out.println(n); } } // 編譯器會在此自動為我們寫入finally并調用close() }
3.2、利用緩沖區讀取文件
在讀取流的時候,一次讀取一個字節并不是最高效的方法。很多流支持一次性讀取多個字節到緩沖區,對于文件和網絡流來說,利用緩沖區一次性讀取多個字節效率往往要高很多。InputStream提供了兩個重載方法來支持讀取多個字節:
int read(byte[] b):讀取若干字節并填充到byte[]數組,返回一次性讀取的字節數int read(byte[] b, int off, int len):指定byte[]數組的偏移量和最大填充數
利用上述方法一次讀取多個字節時,需要先定義一個byte[]數組作為緩沖區,read()方法會盡可能多地讀取字節到緩沖區, 但不會超過緩沖區的大小。read()方法的返回值不再是字節的int值,而是返回實際讀取了多少個字節。如果返回-1,表示沒有更多的數據了。
public void readFile() throws IOException { try (InputStream input = new FileInputStream("src/readme.txt")) { // 定義1000個字節大小的緩沖區。(如果定義的緩沖區字節數組太小,則讀出來可能有亂碼。比如緩沖區是10個字節,一個中文是2-4個字節,一次性讀不完則可能只讀到某個中文的字節數的一半導致出現亂碼) byte[] buffer = new byte[1000]; int n; while ((n = input.read(buffer)) != -1) { // 讀取到緩沖區 System.out.println("一次性讀取了" + n + "個字節"); System.out.println(new String(buffer, 0, n)); //輸出字符串 } } }
4、輸出字節流(OutputStream)
OutputStream是Java標準庫提供的最基本的輸出流,OutputStream也是抽象類,它是所有輸出流的超類。這個抽象類定義的一個最重要的方法就是void write(int b):
//該方法會根據參數的十進制的值表示的字節來寫入 public abstract void write(int b) throws IOException;
和InputStream類似,OutputStream也提供了close()方法關閉輸出流,以便釋放系統資源。
OutputStream還提供了一個flush()方法,該方法能將緩沖區的內容進行輸出。在緩沖區寫滿了時,OutputStream會自動調用該方法。在調用close()方法關閉OutputStream之前,也會自動調用flush()方法。當然,我們也可以主動去調用該方法。
4.1、將字節寫入文件(文件輸出字節流 FileOutputStream)
我們可以調用 write(int n) 方法來將十進制 n 代表的字節寫入某個文件當中。當該文件不存在時,該方法會自動創建一個文件并且寫入。當文件已經存在時,該方法會創建一個新的同名文件進行覆蓋并寫入。當目錄不存在時會報錯。
public void writeFile() throws IOException { OutputStream output = new FileOutputStream("out/readme.txt"); output.write(72); // H output.write(101); // e output.write(108); // l output.write(108); // l output.write(111); // o output.flush(); //write方法只是將數據寫到內存中,該方法會將內存中的數據寫到硬盤中。可以不手動調用該方法,因為在調用close()時也會自動調用flush()方法 output.close(); }
我們可以使用 write(byte[] arr) 方法來一次寫入多個字節:
public void writeFile() throws IOException { try (OutputStream output = new FileOutputStream("out/readme.txt")) { output.write("Hello".getBytes("UTF-8")); output.write("你好啊,你是哪位".getBytes("UTF-8")); } // 編譯器會自動在此為我們寫入finally并調用close() }
和InputStream的read()方法一樣,OutputStream的write()方法也是阻塞的,即必須等到這些方法返回后才會繼續執行下一段代碼。
4.2、將某個文件的內容復制到另一個文件
文件的字節流是非常通用的,因為字節流直接使用的是二進制。文件的字節流不僅可以用來操作文檔,還可以用來操作任何的其他類型的文件比如圖片、壓縮包等等。所以下面的方法對于其他類型的文件也是通用的,將路徑和文件名修改即可。
File fileIN = new File("d:/TEST/MyFile2.txt"); //定義輸入文件 File fileOUT = new File("d:/TEST/MyFile3.txt"); //定義輸出文件 FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream(fileIN); //輸入流連接到輸入文件 fos = new FileOutputStream(fileOUT); //輸出流連接到輸出文件 byte[] arr = new byte[10]; //該數組用來存入從輸入文件中讀取到的數據 int len; //變量len用來存儲每次讀取數據后的返回值 while( ( len=fis.read(arr) ) != -1 ) { fos.write(arr, 0, len); }//while循環:每次從輸入文件讀取數據后,都寫入到輸出文件中
} catch (IOException e) { e.printStackTrace(); } //關閉流 try { fis.close(); fos.close(); } catch (IOException e) { e.printStackTrace(); }
5、輸入字符流(Reader)
Reader是Java的IO庫提供的另一個輸入流接口,他以字符 char 為單位進行讀取。
字節輸入流InputStream和字符輸入流Reader的區別:
java.io.Reader是所有字符輸入流的超類,它最主要的方法是 read(),該方法讀取字符流的下一個字符,并返回字符表示的int,范圍是0~65535。如果已讀到末尾,則返回-1。
public int read() throws IOException;
(讀取文本文件通常使用字符流,而像視頻、圖片、音頻等文件都是二進制數據,需要使用字節流讀取。當然文本文件也是可以通過字節流來讀取和寫入的。字節流更通用,字符流只不過是對字節流進行了封裝,查表操作)
5.1、文件輸入字符流(FileReader)
FileReader是Reader的一個子類,它可以打開文件并獲取Reader。
下面代碼示例一個一個字符讀取文件:
public void readFile() throws IOException { // 創建一個FileReader對象: Reader reader = new FileReader("src/readme.txt"); for (;;) { int n = reader.read(); // 反復調用read()方法,直到返回-1 if (n == -1) { //n為-1表示已經讀到末尾 break; } System.out.println((char)n); // 分別打印出讀取出的單個字符 } reader.close(); // 關閉流 }
Reader還提供了一次性讀取若干字符并填充到char[]數組的方法:read(char[] c),此時它返回的是一次實際讀入的字符個數,最大不能超過char[] c字符數組的長度
public int read(char[] c) throws IOException //該方法返回的是一次實際讀入的字符個數,最大不會超過char[]數組的長度。返回-1表示流結束。
使用char[]數組進行讀取:
try { Reader reader = new FileReader("./test.txt"); char[] buffer = new char[10]; int n; while ((n = reader.read(buffer)) != -1) { //此時會一次讀取10個字符,直到讀取完畢時會返回-1 System.out.println("read " + n + " chars."); System.out.println(new String(buffer, 0, n)); } reader.close(); } catch (Exception e) { e.printStackTrace(); }
FileReader是默認按中文系統的 GBK 來編碼的,所以在讀取UTF-8文件時可能會出現亂碼情況,因為在 UTF-8 -> GBK -> UTF-8 的過程中編碼會出現損失從而造成結果不能還原成最初的字符。
為了避免讀出來是亂碼,我們可以在創建FileReader時指定編碼。(注意:FileReader 構造函數帶編碼是在 Java 11 中加入,即JDK11以下的版本不支持。在低版本 JDK 中如果需要指定編碼讀取文件,我們可以使用 InputStreamReader 來讀取文件,該類的構造函數可以指定編碼格式)
public void readFile() throws IOException { try (Reader reader = new FileReader("src/readme.txt", StandardCharsets.UTF_8)) { char[] buffer = new char[1000]; int n; while ((n = reader.read(buffer)) != -1) { System.out.println("read " + n + " chars."); } } }
5.2、字節流轉成字符流(InputStreamReader,可指定讀取時使用的編碼格式)
大部分的字符流 Reader 實際上是基于字節流 InputStream 構造的,因為Reader需要從InputStream中讀入字節流(byte),然后,根據編碼設置,再轉換為char就可以實現字符流。如果我們查看FileReader的源碼,它在內部實際上持有一個FileInputStream。
InputStreamReader就可以把任何InputStream轉換為Reader。InputStreamReader 是字符流Reader的子類,是字節流通向字符流的橋梁。 該類讀取字節,并使用指定的字符集(編碼格式)將其解碼為字符。它的字符集可以由名稱指定,也可以接受平臺的默認字符集。
你可以在構造器重指定編碼的方式,如果不指定的話將采用底層操作系統的默認編碼方式,例如 GBK 等。
構造方法如下:
參數:InputStream in 字節輸入流,用來讀取文件中保存的字節,String charsetName 指定的編碼表名稱,不區分大小寫,可以是utf-8/UTF-8,gbk/GBK,...不指定默認使用UTF-8
InputStreamReader(InputStream in) //創建一個使用默認字符集的 InputStreamReader。 InputStreamReader(InputStream in, String charsetName) //創建使用指定字符集的 InputStreamReader。
該類繼承于 Reader 類,繼承了父類的共性成員方法:
int read() //讀取單個字符并返回。 int read(char[] cbuf) //一次讀取多個字符,將字符讀入數組。 void close() //關閉該流并釋放與之關聯的所有資源。
使用InputStreamReader讀取文件代碼示例如下:
先創建一個InputStreamReader對象,構造方法中傳遞字節輸入流和指定的編碼表名稱。然后使用InputStreamReader對象中的方法read讀取文件,最后釋放資源。注意:構造方法中指定的編碼表名稱要和文件的編碼相同,否則會發生亂碼
public static void main(String[] args) throws IOException { //創建一個字節流FileInputStream InputStream input = new FileInputStream("./readme.txt"); //創建InputStreamReader對象,構造方法中傳遞字節輸入流和指定的編碼表名稱。InputStreamReader將會將字節流按照UTF-8進行解碼為字符流 Reader reader = new InputStreamReader(input, "UTF-8"); //使用InputStreamReader對象中的方法read讀取文件 int len = 0; while ((len = reader.read()) != -1) { System.out.println((char) len); //分別打印出讀取的單個字符 } // 3.釋放資源 reader.close(); }
5.3、按行讀取文本文件(BufferedReader)
BufferedReader 是緩沖字符輸入流。它繼承于Reader。BufferedReader 的作用是為其他字符輸入流添加一些緩沖功能。
java.io.BufferedReader和java.io.BufferedWriter類各擁有8192字符的緩沖區。當BufferedReader在讀取文本文件時,會先盡量從文件中讀入字符數據并置入緩沖區,而之后若使用read()方法,會先從緩沖區中進行讀取。如果緩沖區數據不足,才會再從文件中讀取,使用BufferedWriter時,寫入的數據并不會先輸出到目的地,而是先存儲至緩沖區中。如果緩沖區中的數據滿了,才會一次對目的地進行寫出。
構造方法:
BufferedReader br = new BufferReader(Reader in); //Reader in是輸入字符流
主要方法有:
int read();//讀取單個字符。 int read(char[] cbuf,int off,int len); //將字符讀入到數組的某一部分。返回讀取的字符數。達到尾部 ,返回-1。 String readLine(); //讀取文本的一行內容
BufferedReader 的 readLine() 方法使用起來特別方便,每次讀回來的都是一行,避免了需要手動拼接字符串的繁瑣
使用BufferedReader讀取文件代碼示例:
import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; public class Main { public static void main(String[] args) throws IOException{ //BufferedReader可以按行讀取文件 FileInputStream inputStream = new FileInputStream("d://a.txt"); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); //可以在構造InputStreamReader類時指定使用UTF-8來讀取以避免出現亂碼情況 String str = null; while((str = bufferedReader.readLine()) != null){ System.out.println(str); } //close inputStream.close(); bufferedReader.close(); } }
6、輸出字符流(Writer)
Writer就是帶編碼轉換器的OutputStream,它把char轉換為byte并輸出。

Writer是所有字符輸出流的超類,它提供的方法主要有:
- 寫入一個字符(0~65535):
void write(int c); - 寫入字符數組的所有字符:
void write(char[] c); - 寫入String表示的所有字符:
void write(String s)。
6.1、文件輸出字符流(FileWriter)
FileWriter就是向文件中寫入字符流的Writer。它的使用方法和FileReader類似:
try { Writer writer = new FileWriter("./readme.txt"); writer.write('H'); // 寫入單個字符 writer.write("Hello".toCharArray()); // 寫入char[] writer.write("你好啊hello~"); // 直接寫入String writer.flush(); //可不寫 writer.close(); } catch (Exception e) { e.printStackTrace(); }
6.2、字節流轉成字符流(OutputStreamWriter,可指定輸出時的編碼)
大部分的 Writer 實際上是基于OutputStream構造的,它接收char,然后在內部自動轉換成一個或多個byte,并寫入OutputStream,OutputStreamWriter就是一個將任意的OutputStream轉換為Writer的轉換器。
構造方法:參數:OutputStream out:字節輸出流,可以用來寫轉換之后的字節到文件中,String charsetName:指定的編碼表名稱,不區分大小寫,可以是utf-8/UTF-8,gbk/GBK,...不指定將默認使用UTF-8
OutputStreamWriter(OutputStream out) //創建使用默認字符編碼的 OutputStreamWriter。 OutputStreamWriter(OutputStream out, String charsetName) //創建使用指定字符集的 OutputStreamWriter。
該類繼承 Writer 類,繼承了父類的共性成員方法
void write(int c) //寫入單個字符。 void write(char[] cbuf) //寫入字符數組。 abstract void write(char[] cbuf, int off, int len) //寫入字符數組的某一部分,off數組的開始索引,len寫的字符個數。 void write(String str) //寫入字符串。 void write(String str, int off, int len) //寫入字符串的某一部分,off字符串的開始索引,len寫的字符個數。 void flush() //刷新該流的緩沖。 void close() //關閉此流,但要先刷新它。
使用 OutputStreamWriter 輸出字符代碼示例:
public static void main(String[] args) throws IOException { //1.創建OutputStreamWriter對象,構造方法中傳遞字節輸出流和指定的編碼表名稱 //OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\utf_8.txt"),"utf-8"); //可指定編碼格式 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\utf_8.txt"));//不指定將默認使用UTF-8
//2.使用OutputStreamWriter對象中的方法write,把字符轉換為字節存儲緩沖區中(編碼) osw.write("你好");
//3.使用OutputStreamWriter對象中的方法flush,把內存緩沖區中的字節刷新到文件中(使用字節流寫字節的過程) osw.flush(); //4.釋放資源 osw.close(); }
6.3、BufferedWriter
BufferedReader和BufferedWriter都是帶有默認緩沖區的字符輸入輸出流,其效率相較于沒有緩沖區要高。使用BufferedWriter時,寫入的數據并不會先輸出到目的地,而是先存儲至緩沖區中。如果緩沖區中的數據滿了,才會一次對目的地進行寫出。
BufferedWriter通過字符數組來緩沖數據,當緩沖區滿或者用戶調用flush()函數時,它就會將緩沖區的數據寫入到輸出流中。
構造方法:
bufferedWriter bf = new bufferedWriter(Writer out ); //Writer out是輸出的字符流
主要方法:
void write(char ch);//寫入單個字符。 void write(char []cbuf,int off,int len) //寫入字符數據的某一部分。 void write(String s,int off,int len) /寫入字符串的某一部分。 void newLine() //寫入一個行分隔符。 void flush(); //刷新該流中的緩沖。將緩沖數據寫到目的文件中去。 void close(); //關閉此流,再關閉前會先刷新他。
使用BufferWriter往文件輸出內容代碼示例:
public static void outMsg(String msg, boolean flag, String filename) { try { File file = new File(filename); OutputStreamWriter write = new OutputStreamWriter(new FileOutputStream(file, flag), "UTF-8"); //可以在聲明FileOutputStreamWriter時指定編碼 BufferedWriter writer = new BufferedWriter(write); //聲明BufferedWriter時需傳入字符輸入流 writer.write(msg); writer.flush(); writer.close(); } catch (Exception e) { e.printStackTrace(); } }
9、SXSSWorkbook和XSSWorkbook
在日常開發使用中,我們建議使用SXSSWorkbook,應禁止使用 XSSWorkbook。
SXSSWorkbook和 XSSWorkbook 都是 Apache POI 庫中用于操作 Excel 2007+(.xlsx 格式)的類,但它們的設計目標和內存使用策略有很大差異,尤其在處理大型 Excel 文件時表現截然不同。
| 特性 | XSSWorkbook | SXSSWorkbook |
|---|---|---|
| 內存占用 | 高(全量加載到內存) | 低(超出閾值后寫入臨時文件) |
| 設計目標 | 適用于中小型 Excel 文件 | 專門優化大型 Excel 文件(十萬行級以上) |
| 功能完整性 | 支持所有 Excel 操作(讀寫、樣式、公式等) | 功能有限制(如不支持公式求值、部分樣式) |
| 臨時文件 | 不生成 | 會生成臨時文件,需手動清理 |
9.1、XSSWorkbook
// XSSWorkbook 示例(處理小型文件) try (XSSFWorkbook workbook = new XSSFWorkbook(); FileOutputStream fos = new FileOutputStream("small_file.xlsx")) { XSSFSheet sheet = workbook.createSheet("數據"); // 寫入 1000 行數據(內存可承受) for (int i = 0; i < 1000; i++) { XSSFRow row = sheet.createRow(i); row.createCell(0).setCellValue("行 " + i); } workbook.write(fos); } catch (IOException e) { e.printStackTrace(); }
9.2、SXSSWorkbook
// SXSSWorkbook 示例(處理大型文件) try (SXSSFWorkbook workbook = new SXSSFWorkbook(100); // 內存中保留 100 行,超出寫入臨時文件 FileOutputStream fos = new FileOutputStream("large_file.xlsx")) { SXSSFSheet sheet = workbook.createSheet("大數據"); // 寫入 100 萬行數據(內存占用可控) for (int i = 0; i < 1_000_000; i++) { SXSSFRow row = sheet.createRow(i); row.createCell(0).setCellValue("行 " + i); } workbook.write(fos); workbook.dispose(); // 清理臨時文件(關鍵!) } catch (IOException e) { e.printStackTrace(); }
SXSSWorkbook 必須調用 dispose() 方法,否則臨時文件會殘留(路徑可通過 workbook.setTempFileCreationStrategy() 自定義)。

浙公網安備 33010602011771號