初學 Java 時,IO 體系往往是讓人最頭疼的部分。網上的資料要麼只有結構圖缺乏實戰,要麼停留在舊版本,未涵盖 Java 7+ 的新特性。本文將從文件操作入手,層層遞進至字節流、字符流、對象序列化、特殊功能流等核心知識點,並附帶完整可運行的代碼示例,幫你建立清晰的 IO 知識體系。
流的本質是對文件的處理。Java 通過 java.io.File 類提供了豐富的文件和目錄操作方法,如創建、刪除、重命名、獲取屬性等。以下示例展示常用操作:
package com.hxw.io;
import java.io.*;
public class FileExample {
public static void main(String[] args) {
createFile();
}
public static void createFile() {
File f = new File("E:/電腦桌面/jar/files/create.txt");
try {
// 僅當文件不存在時創建
f.createNewFile();
System.out.println("該分區大小:" + f.getTotalSpace() / (1024*1024*1024) + "G");
// 創建多級目錄
f.mkdirs();
// f.delete(); // 刪除文件/目錄
System.out.println("文件名:" + f.getName());
System.out.println("父目錄路徑:" + f.getParent());
} catch (Exception e) {
e.printStackTrace();
}
}
}
關鍵點: File 對象只代表路徑,不佔用實際磁盤空間;真正的讀寫需要流來完成。
字節流以字節為單位,適合處理圖片、視頻等二進制文件。FileInputStream 是最基礎的文件輸入流。下面的例子統計文件字節長度,但逐字節讀取效率極低:
public class FileCount {
public static void main(String[] args) {
int count = 0;
InputStream streamReader = null;
try {
streamReader = new FileInputStream(new File("D:/David/Java/java 高級進階/files/tiger.jpg"));
while (streamReader.read() != -1) {
count++;
}
System.out.println("---長度是:" + count + " 字節");
} catch (IOException e) {
e.printStackTrace();
} finally {
try { if (streamReader != null) streamReader.close(); } catch (IOException e) { e.printStackTrace(); }
}
}
}
痛點: 6萬多字節意味著 6 萬次系統調用。解決方案是使用緩衝區:read(byte[] b) 一次讀取塊數據,或直接包裝 BufferedInputStream。
markSupported():判斷是否支持標記mark(readlimit):在當前位置打標記,允許回退的最大字節數reset():回到標記位置FileInputStream 不支持,BufferedInputStream 支持。這在解析器、編譯器等需要回溯的場景很有用。
輸出流同理,默認無緩衝。使用 byte 數組作為緩衝區可大幅提升性能:
public class FileCopy {
public static void main(String[] args) {
byte[] buffer = new byte[512];
int numberRead = 0;
FileInputStream input = null;
FileOutputStream out = null;
try {
input = new FileInputStream("D:/David/Java/java 高級進階/files/tiger.jpg");
out = new FileOutputStream("D:/David/Java/java 高級進階/files/tiger2.jpg");
while ((numberRead = input.read(buffer)) != -1) {
out.write(buffer, 0, numberRead); // 關鍵:只寫實際讀取的字節數
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 關閉流...
}
}
}
實現對象的持久化與傳輸。類必須實現 Serializable 接口(標記接口,無方法):
class Student implements Serializable {
private String name;
private int age;
// 構造器、toString()...
}
public class ObjetStream {
public static void main(String[] args) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.txt"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.txt"))) {
oos.writeObject(new Student("gg", 22));
oos.writeObject(new Student("tt", 18));
for (int i = 0; i < 2; i++) {
System.out.println(ois.readObject());
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
當只需存儲對象的字段(基本類型)而非整個對象時,更輕量、跨語言兼容性更好:
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.dat"));
dos.writeUTF(member.getName());
dos.writeInt(member.getAge());
dos.flush();
DataInputStream dis = new DataInputStream(new FileInputStream("data.dat"));
String name = dis.readUTF();
int age = dis.readInt();
允許將讀取的字節推回流中,常用於詞法分析器需「向前看一個字符」的場景:
PushbackInputStream push = new PushbackInputStream(new ByteArrayInputStream("hello,world".getBytes()));
int temp;
while ((temp = push.read()) != -1) {
if (temp == ',') {
push.unread(temp); // 推回逗號
temp = push.read(); // 重新讀取
System.out.print("(回退" + (char)temp + ")");
} else {
System.out.print((char)temp);
}
}
將多個 InputStream 串聯成一個邏輯流,順序讀取:
Vector v = new Vector<>();
v.add(new FileInputStream("text1.txt"));
v.add(new FileInputStream("text2.txt"));
SequenceInputStream sis = new SequenceInputStream(v.elements());
// 讀取 sis 即可依次讀完所有文件
Java 內部用 Unicode,字符流自動處理編碼轉換。核心類:Reader/Writer 及其子類。
char[] buffer = new char[512];
try (FileReader reader = new FileReader("copy1.txt");
PrintWriter writer = new PrintWriter(System.out)) {
int len;
while ((len = reader.read(buffer)) != -1) {
writer.write(buffer, 0, len);
}
}
鏈式調用(裝飾器模式)增強功能。BufferedReader.readLine() 按行讀取極其方便:
public static void concatenateFile(String... fileNames) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("copy2.txt"))) {
for (String name : fileNames) {
try (BufferedReader reader = new BufferedReader(new FileReader(name))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine(); // 跨平台換行
}
}
}
}
}
Console.readPassword() 禁用回顯,返回 char[],用完可手動清零,比 String 更安全。
將字符流解析為 Token(標識符、數字、字符串、註釋)。默認規則可自定義:
StreamTokenizer st = new StreamTokenizer(new BufferedReader(new FileReader("copy1.txt")));
// 將引號、斜杠視為普通字符,不作特殊處理
st.ordinaryChar('\''); st.ordinaryChar('"'); st.ordinaryChar('/');
while (st.nextToken() != StreamTokenizer.TT_EOF) {
switch (st.ttype) {
case StreamTokenizer.TT_WORD: System.out.println("單詞:" + st.sval); break;
case StreamTokenizer.TT_NUMBER: System.out.println("數字:" + st.nval); break;
case StreamTokenizer.TT_EOL: break;
default: System.out.println("標點:" + (char)st.ttype);
}
}
實測發現: 單獨小數點視為數字 0.0;中文連寫視為一個詞;引號內整體為一 Token(除非調用 ordinaryChar)。
Serializable 更可控的序列化接口Java IO 體系龐大但脈絡清晰:文件 → 字節流/字符流 → 裝飾器增強 → 特殊功能流。掌握核心抽象類(InputStream/OutputStream/Reader/Writer)、裝飾器模式(Buffered*/Data*/Object*)及資源關閉模式(try-with-resources),即可應對絕大多數場景。