Java?IO流實(shí)戰(zhàn)之文件復(fù)制、文本讀寫、對象序列化詳細(xì)解析
前言
IO 流是 Java 操作數(shù)據(jù)傳輸?shù)暮诵墓ぞ?,從簡單的文件?fù)制到復(fù)雜的對象持久化,都離不開 IO 流的靈活運(yùn)用。本文將通過三個實(shí)戰(zhàn)場景 ——文件復(fù)制、文本讀寫、對象序列化存儲,帶你從原理到代碼徹底掌握 IO 流的實(shí)戰(zhàn)技巧,每個場景都配備直觀圖示和優(yōu)化方案,讓你在實(shí)際開發(fā)中少走彎路。
一、實(shí)戰(zhàn)場景一:文件復(fù)制 —— 字節(jié)流的核心應(yīng)用
文件復(fù)制是 IO 流最基礎(chǔ)也最常用的場景,無論是圖片、視頻還是文檔,本質(zhì)上都是字節(jié)數(shù)據(jù)的傳輸。我們需要解決的核心問題是:如何高效地將源文件的字節(jié)數(shù)據(jù)傳輸?shù)侥繕?biāo)文件。
1.1 基礎(chǔ)實(shí)現(xiàn):字節(jié)流直接讀寫
最原始的文件復(fù)制可以通過FileInputStream(字節(jié)輸入流)和FileOutputStream(字節(jié)輸出流)實(shí)現(xiàn),原理是逐字節(jié)或按字節(jié)數(shù)組讀取源文件,再寫入目標(biāo)文件。
public class FileCopyBasic {
public static void main(String[] args) {
// 源文件和目標(biāo)文件路徑
String sourcePath = "source.jpg";
String targetPath = "target_basic.jpg";
// 聲明流對象(try-with-resources語法自動關(guān)閉資源)
try (FileInputStream fis = new FileInputStream(sourcePath);
FileOutputStream fos = new FileOutputStream(targetPath)) {
byte[] buffer = new byte[1024]; // 緩沖區(qū)(一次讀1024字節(jié))
int len; // 實(shí)際讀取的字節(jié)數(shù)
// 循環(huán)讀取:當(dāng)len=-1時表示讀取完畢
while ((len = fis.read(buffer)) != -1) {
// 寫入緩沖區(qū)中的有效數(shù)據(jù)(避免寫入空字節(jié))
fos.write(buffer, 0, len);
}
System.out.println("基礎(chǔ)字節(jié)流復(fù)制完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
原理圖示:字節(jié)流通過緩沖區(qū)批量傳輸數(shù)據(jù),減少直接操作磁盤的次數(shù)(直接逐字節(jié)讀寫效率極低,必須用數(shù)組緩沖)。

1.2 優(yōu)化方案:緩沖流提升效率
基礎(chǔ)字節(jié)流雖然能完成復(fù)制,但read()和write()仍會頻繁觸發(fā)系統(tǒng)調(diào)用。BufferedInputStream和BufferedOutputStream通過內(nèi)置緩沖區(qū)(默認(rèn) 8KB)進(jìn)一步減少 IO 次數(shù),效率更高。
public class FileCopyWithBuffer {
public static void main(String[] args) {
String sourcePath = "source.jpg";
String targetPath = "target_buffer.jpg";
// 緩沖流包裝基礎(chǔ)字節(jié)流
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourcePath));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetPath))) {
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
// 緩沖流會自動刷新,大文件可手動調(diào)用bos.flush()避免數(shù)據(jù)滯留
}
System.out.println("緩沖流復(fù)制完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
為什么緩沖流更快?基礎(chǔ)字節(jié)流的read()每次都從磁盤讀數(shù)據(jù),而緩沖流會一次性讀取大量數(shù)據(jù)到內(nèi)置緩沖區(qū),后續(xù)read()直接從內(nèi)存緩沖區(qū)獲取,大幅減少磁盤 IO 次數(shù)(磁盤 IO 速度遠(yuǎn)低于內(nèi)存)。
1.3 進(jìn)階方案:NIO 的零拷貝復(fù)制
Java NIO 的FileChannel提供了transferTo()方法,支持零拷貝(數(shù)據(jù)直接從內(nèi)核緩沖區(qū)傳輸?shù)侥繕?biāo)文件,不經(jīng)過用戶態(tài)),是大文件復(fù)制的最優(yōu)選擇。
public class FileCopyWithChannel {
public static void main(String[] args) {
String sourcePath = "source.mp4";
String targetPath = "target_channel.mp4";
try (FileChannel inChannel = new FileInputStream(sourcePath).getChannel();
FileChannel outChannel = new FileOutputStream(targetPath).getChannel()) {
// 傳輸數(shù)據(jù):從輸入通道到輸出通道,每次最多傳輸Integer.MAX_VALUE字節(jié)
long position = 0;
long size = inChannel.size();
while (position < size) {
position += inChannel.transferTo(position, Integer.MAX_VALUE, outChannel);
}
System.out.println("NIO通道復(fù)制完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
三種方案對比:
| 實(shí)現(xiàn)方式 | 核心類 | 優(yōu)點(diǎn) | 缺點(diǎn) | 適用場景 |
|---|---|---|---|---|
| 基礎(chǔ)字節(jié)流 | FileInputStream | 簡單直接 | 效率低(頻繁 IO) | 小文件、簡單場景 |
| 緩沖字節(jié)流 | BufferedInputStream | 內(nèi)置緩沖區(qū),效率較高 | 仍有用戶態(tài) / 內(nèi)核態(tài)切換 | 中大型文件 |
| NIO 通道 | FileChannel | 零拷貝,效率最高 | 代碼稍復(fù)雜 | 超大文件、性能敏感場景 |
二、實(shí)戰(zhàn)場景二:文本讀寫 —— 字符流與編碼處理
文本文件(如.txt、.java)由字符組成,直接用字節(jié)流讀寫可能因編碼問題導(dǎo)致亂碼。字符流(Reader/Writer)專門處理字符數(shù)據(jù),能自動完成字節(jié)與字符的轉(zhuǎn)換。
2.1 基礎(chǔ)字符流:FileReader/FileWriter
FileReader和FileWriter是字符流的基礎(chǔ)實(shí)現(xiàn),默認(rèn)使用系統(tǒng)編碼(可能導(dǎo)致跨平臺亂碼),適合簡單場景。
public class TextReadWriteBasic {
public static void main(String[] args) {
String sourceTxt = "source.txt";
String targetTxt = "target_basic.txt";
// 字符流讀寫文本
try (FileReader fr = new FileReader(sourceTxt);
FileWriter fw = new FileWriter(targetTxt)) {
char[] buffer = new char[1024]; // 字符緩沖區(qū)
int len;
while ((len = fr.read(buffer)) != -1) {
fw.write(buffer, 0, len);
}
System.out.println("基礎(chǔ)字符流讀寫完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2 解決編碼問題:InputStreamReader/OutputStreamWriter
當(dāng)文本文件編碼(如 UTF-8)與系統(tǒng)默認(rèn)編碼不一致時,必須用InputStreamReader和OutputStreamWriter指定編碼,避免亂碼。
public class TextReadWriteWithCharset {
public static void main(String[] args) {
String sourceTxt = "source_utf8.txt";
String targetTxt = "target_utf8.txt";
String charset = "UTF-8"; // 明確指定編碼
// 字節(jié)流→字符流(指定編碼)
try (InputStreamReader isr = new InputStreamReader(
new FileInputStream(sourceTxt), charset);
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream(targetTxt), charset)) {
char[] buffer = new char[1024];
int len;
while ((len = isr.read(buffer)) != -1) {
osw.write(buffer, 0, len);
}
System.out.println("指定編碼的字符流讀寫完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
編碼轉(zhuǎn)換原理:文本文件存儲的是字節(jié)(如 UTF-8 編碼的漢字占 3 字節(jié)),InputStreamReader按指定編碼將字節(jié)轉(zhuǎn)換為字符,OutputStreamWriter再將字符轉(zhuǎn)換為目標(biāo)編碼的字節(jié)。

2.3 高效文本處理:緩沖字符流與特殊操作
BufferedReader和BufferedWriter是字符流的緩沖增強(qiáng)版,提供readLine()(逐行讀?。┖?code>newLine()(跨平臺換行)等實(shí)用方法,是文本處理的首選。
public class TextReadWriteWithBuffer {
public static void main(String[] args) {
String sourceTxt = "article.txt";
String targetTxt = "article_copy.txt";
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(sourceTxt), "UTF-8"));
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(targetTxt), "UTF-8"))) {
String line; // 存儲每行內(nèi)容
// 逐行讀取(readLine()不包含換行符)
while ((line = br.readLine()) != null) {
bw.write(line); // 寫入一行內(nèi)容
bw.newLine(); // 換行(自動適配系統(tǒng)換行符:\n或\r\n)
}
System.out.println("緩沖字符流逐行讀寫完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
關(guān)鍵技巧:
readLine()適合處理按行結(jié)構(gòu)化的文本(如日志、CSV);newLine()比硬編碼\n更通用,避免跨平臺問題;- 大文件讀寫時,緩沖字符流的效率遠(yuǎn)高于基礎(chǔ)字符流。
三、實(shí)戰(zhàn)場景三:對象序列化 —— 對象的持久化存儲
序列化是將對象轉(zhuǎn)換為字節(jié)序列以便存儲或傳輸?shù)倪^程,反序列化則是將字節(jié)序列恢復(fù)為對象。Java 通過Serializable接口和對象流(ObjectInputStream/ObjectOutputStream)實(shí)現(xiàn)這一功能。
3.1 序列化的基本實(shí)現(xiàn)
步驟 1:定義可序列化的類(實(shí)現(xiàn)Serializable接口)
import java.io.Serializable;
import java.util.Date;
// 實(shí)現(xiàn)Serializable接口(標(biāo)記接口,無方法)
public class User implements Serializable {
// 序列化版本號(強(qiáng)烈建議顯式聲明,避免類結(jié)構(gòu)變化導(dǎo)致反序列化失?。?
private static final long serialVersionUID = 1L;
private String username;
private int age;
private transient String password; // transient修飾的字段不參與序列化
private Date registerTime;
// 構(gòu)造器、getter、setter、toString()
public User(String username, int age, String password) {
this.username = username;
this.age = age;
this.password = password;
this.registerTime = new Date();
}
@Override
public String toString() {
return "User{username='" + username + "', age=" + age +
", password='" + password + "', registerTime=" + registerTime + "}";
}
}
步驟 2:使用對象流進(jìn)行序列化和反序列化
import java.io.*;
public class ObjectSerialization {
public static void main(String[] args) {
String filePath = "user.ser";
User user = new User("zhangsan", 25, "123456");
// 1. 序列化:將對象寫入文件
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(filePath))) {
oos.writeObject(user);
System.out.println("序列化完成:" + user);
} catch (IOException e) {
e.printStackTrace();
}
// 2. 反序列化:從文件恢復(fù)對象
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(filePath))) {
User deserializedUser = (User) ois.readObject();
System.out.println("反序列化結(jié)果:" + deserializedUser);
// 注意:password為null(transient字段未被序列化)
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
輸出結(jié)果:
序列化完成:User{username='zhangsan', age=25, password='123456', registerTime=...}
反序列化結(jié)果:User{username='zhangsan', age=25, password='null', registerTime=...}
3.2 序列化的核心知識點(diǎn)
Serializable 接口:是一個標(biāo)記接口(無任何方法),僅用于告訴 JVM 該類可以被序列化。若類未實(shí)現(xiàn)此接口,序列化時會拋出
NotSerializableException。serialVersionUID 的作用:用于驗(yàn)證序列化和反序列化的類版本是否一致。若不顯式聲明,JVM 會根據(jù)類結(jié)構(gòu)自動生成,類結(jié)構(gòu)(如增減字段)變化會導(dǎo)致版本號改變,反序列化失敗。建議所有可序列化類顯式聲明此常量。
transient 關(guān)鍵字:被
transient修飾的字段不參與序列化,反序列化時會被賦予默認(rèn)值(如 null、0)。適合存儲敏感信息(如密碼)或無需持久化的臨時數(shù)據(jù)。父類序列化規(guī)則:若父類未實(shí)現(xiàn)
Serializable,則父類必須有默認(rèn)無參構(gòu)造器,否則反序列化時會報錯(無法初始化父類字段)。
序列化流程圖示:

3.3 序列化的應(yīng)用場景與限制
應(yīng)用場景:
- 對象持久化(如存儲到文件、數(shù)據(jù)庫 BLOB 字段);
- 網(wǎng)絡(luò)傳輸(如 RPC 框架中對象的跨服務(wù)傳輸);
- 深拷貝(通過序列化 + 反序列化實(shí)現(xiàn)對象的完全復(fù)制)。
限制:
- 不能序列化靜態(tài)字段(靜態(tài)字段屬于類,不屬于對象);
- 循環(huán)引用的對象可序列化(JVM 會處理引用關(guān)系);
- 序列化后的字節(jié)流與 JVM 相關(guān),跨語言兼容性差(可考慮 JSON、Protobuf 等跨語言格式)。
四、IO 流實(shí)戰(zhàn)總結(jié)與最佳實(shí)踐
通過三個場景的實(shí)戰(zhàn),我們可以總結(jié)出 IO 流使用的核心原則:
選擇合適的流類型:
- 字節(jié)流:處理非文本文件(圖片、視頻、二進(jìn)制數(shù)據(jù));
- 字符流:處理文本文件(需注意編碼);
- 對象流:處理對象的序列化 / 反序列化。
優(yōu)先使用緩沖流:緩沖流(
BufferedXXX)通過內(nèi)置緩沖區(qū)減少 IO 次數(shù),效率遠(yuǎn)高于基礎(chǔ)流,幾乎所有場景都應(yīng)優(yōu)先使用。資源管理必須嚴(yán)謹(jǐn):始終使用try-with-resources 語法(自動關(guān)閉資源),避免流未關(guān)閉導(dǎo)致的資源泄漏(尤其是文件流和網(wǎng)絡(luò)流)。
關(guān)注編碼問題:文本處理時必須明確指定編碼(如 UTF-8),避免依賴系統(tǒng)默認(rèn)編碼導(dǎo)致的亂碼。
大文件優(yōu)化:大文件復(fù)制優(yōu)先用 NIO 的
FileChannel.transferTo(),利用零拷貝提升性能;大文件讀寫避免一次性加載到內(nèi)存,應(yīng)分塊處理。
IO 流是 Java 開發(fā)的基礎(chǔ)技能,掌握這些實(shí)戰(zhàn)技巧不僅能解決日常開發(fā)問題,更能幫助你理解 IO 操作的底層原理。希望本文的三個實(shí)戰(zhàn)場景能讓你對 IO 流的運(yùn)用更加得心應(yīng)手,歡迎在評論區(qū)分享你的實(shí)戰(zhàn)經(jīng)驗(yàn)!

總結(jié)
到此這篇關(guān)于Java IO流實(shí)戰(zhàn)之文件復(fù)制、文本讀寫、對象序列化詳細(xì)解析的文章就介紹到這了,更多相關(guān)Java IO流文件復(fù)制、文本讀寫、對象序列化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Data JPA中的Specification動態(tài)查詢詳解
Specification是一個設(shè)計(jì)模式,用于企業(yè)級應(yīng)用開發(fā)中,其主要目的是將業(yè)務(wù)規(guī)則從業(yè)務(wù)邏輯中分離出來,在數(shù)據(jù)查詢方面,Specification可以定義復(fù)雜的查詢,使其更易于重用和測試,這篇文章主要介紹了Spring Data JPA中的Specification動態(tài)查詢詳解,需要的朋友可以參考下2023-07-07
Java之關(guān)于基本數(shù)據(jù)類型和引用數(shù)據(jù)類型的存放位置
這篇文章主要介紹了Java之關(guān)于基本數(shù)據(jù)類型和引用數(shù)據(jù)類型的存放位置,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07
mybatis-plus QueryWrapper自定義查詢條件的實(shí)現(xiàn)
這篇文章主要介紹了mybatis-plus QueryWrapper自定義查詢條件的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
SpringBoot3 響應(yīng)式網(wǎng)絡(luò)請求客戶端的實(shí)現(xiàn)
本文主要介紹了SpringBoot3 響應(yīng)式網(wǎng)絡(luò)請求客戶端的實(shí)現(xiàn),文章詳細(xì)闡述了如何使用SpringBoot3的網(wǎng)絡(luò)請求客戶端進(jìn)行HTTP請求和處理響應(yīng),并提供了示例代碼和說明,具有一定的參考價值,感興趣的可以了解一下2023-08-08
Kafka Java Producer代碼實(shí)例詳解
這篇文章主要介紹了Kafka Java Producer代碼實(shí)例詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-06-06

