Java IO 是處理數據傳輸的核心子系統,「一切皆流」貫穿文件、網絡、進程間通信。本文系統梳理 BIO (阻塞 IO)、NIO (非阻塞/新 IO)、AIO (異步 IO) 三代模型,深度解析流體系架構、核心類設計模式、編碼轉換、序列化、壓縮、內存映射、零拷貝等進階主題,並給出生產環境選型指南。
| 維度 | 分類 | 抽象基類 | 典型實現 | 處理單位 |
|---|---|---|---|---|
| 按數據類型 | 字節流 | InputStream / OutputStream | FileInputStream、SocketInputStream | byte (8bit) |
| 字符流 | Reader / Writer | FileReader、BufferedReader | char (16bit,按編碼) | |
| 按流向 | 輸入流 | InputStream / Reader | 讀取數據源 | — |
| 輸出流 | OutputStream / Writer | 寫入目標 | — |
StreamDecoder/Encoder,按字符集 (UTF-8/GBK) 將字節解碼為字符或編碼為字節Java IO 大量使用 裝飾者模式 動態組合功能:
// 經典鏈式裝飾
InputStream in = new FileInputStream("data.bin");
in = new BufferedInputStream(in); // 添加緩衝
in = new DataInputStream(in); // 添加基本類型讀取
in = new GZIPInputStream(in); // 添加解壓
in = new ObjectInputStream(in); // 添加反序列化
核心優勢:職責單一、靈活組合、避免繼承爆炸。注意:關閉流時只需關閉最外層,會鏈式調用 close()。
| 類 | 類型 | 關鍵特性 | 構造參數 |
|---|---|---|---|
| FileInputStream | 字節輸入 | 讀取原始字節,支援 getChannel() 獲取 FileChannel | File/String/FileDescriptor |
| FileOutputStream | 字節輸出 | append=true 追寫模式,getChannel() 支援 NIO | File/String/FileDescriptor, append |
| FileReader | 字符輸入 | 默認平台編碼,**不推薦** (不可指定編碼) | File/String |
| FileWriter | 字符輸出 | 默認平台編碼,**不推薦** | File/String, append |
最佳實踐:棄用 FileReader/Writer,改用 InputStreamReader/OutputStreamWriter 顯式指定編碼:
// 讀取 UTF-8 文本
try (Reader r = new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(r)) {
String line;
while ((line = br.readLine()) != null) { /* process */ }
}
// 寫入 UTF-8 文本
try (Writer w = new OutputStreamWriter(new FileOutputStream("out.txt"), StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(w)) {
bw.write("內容");
bw.newLine(); // 跨平台換行
}
readLine()/newLine()),字符流必加read(byte[])/write(byte[])// InputStreamReader: 字節流 → 字符流 (解碼)
InputStreamReader isr = new InputStreamReader(inputStream, "UTF-8");
// OutputStreamWriter: 字符流 → 字節流 (編碼)
OutputStreamWriter osw = new OutputStreamWriter(outputStream, "GBK");
常見坑:Windows 筆記本默認 GBK,Linux 服務器默認 UTF-8,**跨平台必須顯式指定編碼**,否則中文亂碼。
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) {
oos.writeObject(user); // 必須實現 Serializable
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) {
User user = (User) ois.readObject();
}
核心要點:
Serializable (標記接口),強烈建議顯式聲明 serialVersionUIDtransient 字段不序列化,static 字段屬於類不序列化Unsafe.allocateInstance 直接分配內存Deflater(DEFAULT_COMPRESSION) 可調 (1-9)
// 管道輸出流 (生產者)
PipedOutputStream pos = new PipedOutputStream();
// 管道輸入流 (消費者) - 必須連接
PipedInputStream pis = new PipedInputStream(pos);
// 或者先創建輸入流再連接
PipedInputStream pis2 = new PipedInputStream();
PipedOutputStream pos2 = new PipedOutputStream(pis2);
注意:單線程同時讀寫會死鎖,**必須雙線程**,緩衝區默認 1KB,可構造時指定。
SequenceInputStream sis = new SequenceInputStream(
Collections.enumeration(Arrays.asList(
new FileInputStream("part1"),
new FileInputStream("part2"),
new FileInputStream("part3")
))
);
// 依次讀取三個文件,視為單一流
DataInput/DataOutput,支持 seek() 定位、getFilePointer() 獲取位置"r" (只讀)、"rw" (讀寫)、"rws" (同步元數據)、"rwd" (同步數據)
// 創建
ByteBuffer buf = ByteBuffer.allocate(1024); // 堆內存
ByteBuffer direct = ByteBuffer.allocateDirect(1024); // 直接內存 (堆外)
// 核心屬性
capacity // 容量,不可變
limit // 寫模式=capacity,讀模式=已寫數據量
position // 下一個讀寫位置
mark // 標記位置,reset() 恢復
// 狀態機
put() 寫入 → position 增加
flip() 切換讀模式 → limit=position, position=0
get() 讀取 → position 增加
clear() 切換寫模式 → position=0, limit=capacity (數據未清除)
compact() 未讀數據前移 → 保留未讀數據繼續寫入
直接內存優勢:避免堆內存↔堆外內存拷貝,**零拷貝關鍵**,但分配/回收開銷大,適合大塊、長壽命數據 (Netty 默認池化直接內存)。
| Channel | 類型 | 非阻塞 | 用途 |
|---|---|---|---|
| FileChannel | 文件 | 否 (可配置) | 文件讀寫、內存映射、零拷貝 |
| SocketChannel | TCP 客戶端 | 是 | 網絡通信 |
| ServerSocketChannel | TCP 服務端 | 是 | 接受連接 |
| DatagramChannel | UDP | 是 | 數據報通信 |
| Pipe.SinkChannel/SourceChannel | 管道 | 是 | 線程間通信 |
核心操作:read(ByteBuffer)/write(ByteBuffer)/configureBlocking(false)/register(Selector, ops)
Selector selector = Selector.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_ACCEPT);
while (selector.select() > 0) { // 阻塞等待就緒事件
Set keys = selector.selectedKeys();
Iterator it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove(); // 必須移除,否則重複處理
if (key.isAcceptable()) { /* ServerSocketChannel.accept() */ }
if (key.isReadable()) { /* SocketChannel.read(buf) */ }
if (key.isWritable()) { /* SocketChannel.write(buf) */ }
}
}
Reactor 模式:單線程 Selector 輪詢分發,業務處理可提交線程池 (Netty 模型)。
// 傳統 BIO:4 次拷貝 + 4 次上下文切換
// 磁盤 → 內核緩衝區 → 用戶緩衝區 → 內核 Socket 緩衝區 → 網卡
// 零拷貝 (Linux 2.4+ sendfile):
FileChannel in = FileChannel.open(Paths.get("large.file"), StandardOpenOption.READ);
SocketChannel out = SocketChannel.open();
out.connect(new InetSocketAddress("host", 8080));
long transferred = 0;
long size = in.size();
while (transferred < size) {
transferred += in.transferTo(transferred, size - transferred, out);
}
// 內核態直接拷貝:磁盤 → 內核緩衝區 → Socket 緩衝區 → 網卡
// **2 次拷貝 + 2 次上下文切換**,CPU 完全不參與數據搬運
適用場景:大文件下載、視頻流媒體、靜態資源服務器 (Nginx、Netty 默認啟用)。
FileChannel channel = FileChannel.open(Paths.get("big.data"), StandardOpenOption.READ, StandardOpenOption.WRITE);
// 將文件映射到進程虛擬地址空間,直接操作內存即操作文件
MappedByteBuffer mbb = channel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);
// 讀寫如同操作內存數組
mbb.putInt(0, 123);
int val = mbb.getInt(0);
// OS 頁面管理自動同步到磁盤 (msync),可強制 mbb.force()
優勢:超大文件 (GB 級) 隨機讀寫極快,**突破堆內存限制** (映射區不計入堆);缺點:映射/取消映射開銷大、地址空間佔用、Windows 文件鎖定無法刪除、跨進程同步複雜。
AsynchronousFileChannel afc = AsynchronousFileChannel.open(Paths.get("data.txt"), StandardOpenOption.READ);
ByteBuffer buf = ByteBuffer.allocate(1024);
// 回調模式
afc.read(buf, 0, null, new CompletionHandler() {
@Override public void completed(Integer result, Void attachment) {
buf.flip(); // 處理數據
}
@Override public void failed(Throwable exc, Void attachment) {
exc.printStackTrace();
}
});
// Future 模式
Future future = afc.read(buf, 0);
Integer bytesRead = future.get(); // 阻塞等待
現狀:Linux 實現基於 epoll 模擬 (非真正內核 AIO),Windows IOCP 原生支持,**生產極少直接用**,Netty 4.x 放棄 AIO 回歸 NIO。
File f = new File("/data/logs/app.log");
// 路徑
f.getAbsolutePath(); // 絕對路徑
f.getCanonicalPath(); // 規範路徑 (解析 . .. 符號鏈接)
f.toPath(); // 轉 Path (NIO.2)
// 屬性
f.length(); // 字節大小
f.lastModified(); // 修改時間
f.exists(); f.isFile(); f.isDirectory(); f.isHidden();
// 創建/刪除
f.createNewFile(); // 創建文件
f.mkdir(); f.mkdirs(); // 創建目錄
f.delete(); // 刪除 (目錄需為空)
f.deleteOnExit(); // JVM 退出時刪除
// 列舉
f.list(); // 文件名數組
f.listFiles(); // File 對象數組
f.listFiles((dir, name) -> name.endsWith(".log")); // 過濾器
f.listFiles(File::isDirectory); // 只列目錄
// 遞歸刪除目錄
void deleteDir(File dir) {
File[] files = dir.listFiles();
if (files != null) {
for (File f : files) {
if (f.isDirectory()) deleteDir(f);
f.delete();
}
}
dir.delete();
}
// 文件查找 (NIO.2 更優雅)
Files.walkFileTree(Paths.get("/data"), new SimpleFileVisitor() {
@Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (file.toString().endsWith(".log")) System.out.println(file);
return FileVisitResult.CONTINUE;
}
});
Path path = Paths.get("/data", "logs", "app.log");
// 讀取所有行 (自動關閉)
List lines = Files.readAllLines(path, StandardCharsets.UTF_8);
// 流式讀取 (大文件)
try (Stream stream = Files.lines(path)) {
stream.filter(l -> l.contains("ERROR")).forEach(System.out::println);
}
// 寫入
Files.write(path, "content".getBytes(StandardCharsets.UTF_8),
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
// 複制/移動/刪除
Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING);
Files.move(src, dest, StandardCopyOption.ATOMIC_MOVE);
Files.deleteIfExists(path);
// 獲取屬性
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
attrs.size(); attrs.creationTime(); attrs.lastModifiedTime(); attrs.isDirectory();
| 場景 | 推薦方案 | 核心類/參數 | 避坑指南 |
|---|---|---|---|
| 小文本讀寫 (配置、日志) | 字符流 + 緩衝 | Files.readAllLines / BufferedReader/Writer | 必須指定 UTF-8,try-with-resources 自動關閉 |
| 大文本文件流式處理 | 字符流 + 緩衝 + 行讀取 | Files.lines() / BufferedReader.readLine() | 避免 readAllLines 導致 OOM |
| 二進制文件讀寫 (圖片、視頻) | 字節流 + 緩衝 | BufferedInputStream/OutputStream (8KB+) | 不可用字符流,注意緩衝區大小調優 |
| 大文件複製/傳輸 | 零拷貝 | FileChannel.transferTo() | 文件 > 1MB 時效果顯著,需檢查 OS 支持 |
| 超大文件隨機讀寫 | 內存映射 | MappedByteBuffer (READ_WRITE) | 配合 -XX:MaxDirectMemorySize 限制堆外內存 |
| 高併發網絡服務 | NIO + Reactor | Selector + SocketChannel / Netty | 避免自寫 NIO 服務端,直接用 Netty |
| 跨進程通信 (同機) | 內存映射文件 / Unix Domain Socket | MappedByteBuffer / Netty UDS | 避免管道流 (性能差、無持久化) |
| 對象序列化 (內部 RPC) | 高性能二進制協議 | Protobuf / Kryo / Hessian | 禁用 Java 原生序列化 (不安全、慢) |
| 配置文件讀取 | Properties / YAML | Files.newInputStream + Properties.load() | Spring Boot 建議用 @ConfigurationProperties |
Java IO 經歷了 BIO (流/阻塞) → NIO (Channel/Buffer/Selector/非阻塞) → AIO (異步回調) 三代演進。核心設計模式為裝飾者模式靈活組合功能。現代開發建議:
transferTo 一鍵達標深入理解流模型、緩衝機制、零拷貝原理,能讓你在性能調優、異常排查、架構選型中做出最優決策。