Java字節(jié)流FileInputStream和FileOutputStream從入門到實踐
一、前言
在 Java 中,文件的讀寫操作是最基礎也是最高頻的 I/O 場景之一。字節(jié)流(Byte Stream)作為 Java I/O 體系的兩大分支之一,負責處理所有二進制數(shù)據(jù)的傳輸,圖片、音頻、視頻以及任何非文本文件都離不開它。FileInputStream 和 FileOutputStream 是字節(jié)流中最核心的兩個實現(xiàn)類,掌握它們就掌握了 Java 文件 I/O 的基石。
本文將圍繞這兩個類的構造、核心方法、使用方式、底層原理以及常見坑點展開,確??赐旰竽軐懗鼋训奈募x寫代碼。
二、字節(jié)流簡介
Java 的 I/O 流分為兩大類:
- 字節(jié)流(Byte Stream):以
InputStream/OutputStream為抽象父類,按字節(jié)(8 bit)讀寫,適合所有類型的文件。 - 字符流(Character Stream):以
Reader/Writer為抽象父類,按字符(16 bit)讀寫,適合純文本文件。
FileInputStream 和 FileOutputStream 分別繼承自 InputStream 和 OutputStream,是操作文件的直接入口。
InputStream (抽象)
└── FileInputStream ← 從文件讀取字節(jié)
OutputStream (抽象)
└── FileOutputStream ← 向文件寫入字節(jié)
三、核心 API 速覽
3.1 FileInputStream
| 方法簽名 | 說明 |
|---|---|
FileInputStream(String name) | 根據(jù)文件路徑創(chuàng)建輸入流 |
FileInputStream(File file) | 根據(jù) File 對象創(chuàng)建輸入流 |
int read() | 讀取一個字節(jié),返回 0~255 的 int,讀到末尾返回 -1 |
int read(byte[] b) | 讀取最多 b.length 個字節(jié)到數(shù)組,返回實際讀取的字節(jié)數(shù) |
int read(byte[] b, int off, int len) | 讀取最多 len 個字節(jié)到數(shù)組的指定偏移位置 |
long skip(long n) | 跳過并丟棄 n 個字節(jié) |
int available() | 返回可讀的字節(jié)數(shù)估計值 |
void close() | 關閉流,釋放系統(tǒng)資源 |
3.2 FileOutputStream
| 方法簽名 | 說明 |
|---|---|
FileOutputStream(String name) | 根據(jù)路徑創(chuàng)建輸出流(覆蓋模式) |
FileOutputStream(String name, boolean append) | 指定是否追加模式 |
FileOutputStream(File file) | 根據(jù) File 對象創(chuàng)建輸出流 |
void write(int b) | 寫入一個字節(jié)(只取 int 的低 8 位) |
void write(byte[] b) | 寫入整個字節(jié)數(shù)組 |
void write(byte[] b, int off, int len) | 寫入數(shù)組的一部分 |
void flush() | 刷新緩沖區(qū)(字節(jié)流 flush 是空實現(xiàn),但遵循約定) |
void close() | 關閉流,釋放系統(tǒng)資源 |
四、代碼實現(xiàn)
4.1 基礎用法:單個字節(jié)讀寫(不推薦用于大文件)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileInputStreamDemo {
public static void main(String[] args) throws IOException {
// 用 try-with-resources 自動關閉流(JDK 7+)
try (FileInputStream fis = new FileInputStream("source.txt");
FileOutputStream fos = new FileOutputStream("dest.txt")) {
int data;
while ((data = fis.read()) != -1) {
fos.write(data);
}
}
System.out.println("文件復制完成");
}
}
缺點:每次只讀寫一個字節(jié),每次 read()/write() 都涉及一次 native 調用,大文件下性能極差。
4.2 推薦用法:字節(jié)數(shù)組批量讀寫
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopyWithBuffer {
public static void main(String[] args) {
String source = "photo.jpg";
String dest = "photo_copy.jpg";
try (FileInputStream fis = new FileInputStream(source);
FileOutputStream fos = new FileOutputStream(dest)) {
byte[] buffer = new byte[8192]; // 8KB 緩沖區(qū)
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
// 注意:必須使用 write(buffer, 0, len) 而不是 write(buffer)
// 因為最后一次讀取可能不足 8192 字節(jié)
}
System.out.println("復制完成");
} catch (IOException e) {
System.err.println("文件操作失敗: " + e.getMessage());
}
}
}
4.3 追加模式寫入
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class FileAppendDemo {
public static void main(String[] args) {
try (FileOutputStream fos = new FileOutputStream("log.txt", true)) {
String line = "[" + System.currentTimeMillis() + "] 用戶登錄成功\n";
fos.write(line.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileOutputStream 的第二個參數(shù) append 為 true 時,寫入內(nèi)容追加到文件末尾而非覆蓋。
五、執(zhí)行流程與底層原理
以 read(byte[]) 為例,一次讀取的完整鏈路如下:
- Java 應用層 調用
FileInputStream.read(byte[]) - JDK 內(nèi)部 調用
native readBytes方法 - JNI 進入 C 層,調用操作系統(tǒng)的
ReadFile(Windows)或read()(Linux/POSIX) - 內(nèi)核 從磁盤讀取數(shù)據(jù)到內(nèi)核空間緩沖區(qū)
- 內(nèi)核 → 用戶空間 數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到 Java 傳入的 byte 數(shù)組
- 返回實際讀取的字節(jié)數(shù)(-1 表示 EOF)
這個過程中涉及一次用戶態(tài) ↔ 內(nèi)核態(tài)切換和一次數(shù)據(jù)拷貝。這也是為什么批量讀(減少 native 調用次數(shù))比逐字節(jié)讀快幾個數(shù)量級的原因。
六、常見問題與注意事項
6.1 流必須關閉
未關閉流會導致文件句柄泄漏,最終可能觸發(fā) Too many open files 異常。
? 正確做法:優(yōu)先使用 try-with-resources(JDK 7+),它會自動調用 close(),即使在異常發(fā)生時也能正確關閉。
6.2write(byte[])和write(byte[], 0, len)的區(qū)別
fos.write(buffer) 會把整個 buffer 寫入文件。如果最后一次讀取不足 buffer 長度,會寫入上一次殘留的臟數(shù)據(jù)。永遠使用 write(buffer, 0, len)。
6.4 字符編碼問題
字節(jié)流不涉及編碼轉換,寫入什么字節(jié)就讀出什么字節(jié)。如果要在字節(jié)流上處理文本,必須外部指定字符集:
// 寫入時指定編碼
fos.write("中文".getBytes(StandardCharsets.UTF_8));
// 讀取時指定編碼
String text = new String(bytes, StandardCharsets.UTF_8);
6.5 FileNotFoundException 不等于文件不存在
FileInputStream 構造時如果文件不存在會拋 FileNotFoundException。但如果文件是個目錄,或者路徑包含不可讀的目錄,同一異常也會拋出。不要只把它當成"文件不存在"來處理。
七、最佳實踐
- 能用字符流的場景(純文本)用字符流,否則用字節(jié)流。FileReader / FileWriter 內(nèi)部封裝了字節(jié)轉換,更省心。
- 大文件優(yōu)先用 BufferedInputStream / BufferedOutputStream 包裝,它們在內(nèi)部自動維護更大的緩沖區(qū),減少 native 調用。
- 版本兼容檢查 — FileInputStream 和 FileOutputStream 從 JDK 1.0 就有,不存在兼容性問題。
- Files.copy() 可以替代 — 對于單純復制文件,
java.nio.file.Files.copy()一行代碼搞定,底層使用更高效的 FileChannel。
// NIO 一行復制
Files.copy(Path.of("source.txt"), Path.of("dest.txt"), StandardCopyOption.REPLACE_EXISTING);
八、總結
FileInputStream和FileOutputStream是 Java 字節(jié)流體系中最基礎的文件讀寫類,支持所有文件類型。- 批量讀寫(字節(jié)數(shù)組)比逐字節(jié)讀寫性能高出幾個數(shù)量級。
- 始終使用
try-with-resources確保流被正確關閉。 - 寫入時注意
write(buffer, 0, len)而非write(buffer),避免臟數(shù)據(jù)。 - 純文本場景優(yōu)先考慮
FileReader/FileWriter或 NIO 的Files工具類。
掌握字節(jié)流是理解 Java I/O 體系的第一步,也是編寫高可靠文件操作代碼的必備基礎。
推薦標簽:
- Java IO
- FileInputStream
- FileOutputStream
- 字節(jié)流
- Java 文件操作
到此這篇關于Java字節(jié)流FileInputStream和FileOutputStream從入門到實踐的文章就介紹到這了,更多相關Java字節(jié)流FileInputStream和FileOutputStream內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring Boot 整合 Minio 實現(xiàn)高效文件存儲解決方案(本地和線上)
Minio 是一個高性能的分布式對象存儲系統(tǒng),專為云原生應用而設計,本文將詳細介紹如何在Spring Boot項目中集成Minio實現(xiàn)高效文件存儲,實現(xiàn)文件的上傳、下載、刪除等核心功能,感興趣的朋友一起看看吧2025-08-08
spring boot整合CAS Client實現(xiàn)單點登陸驗證的示例
本篇文章主要介紹了spring boot整合CAS Client實現(xiàn)單點登陸驗證的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01

