`FileOutputStream` 是文件字節輸出的基石。掌握其構造模式、寫入方法、追加寫入、高效拷貝技巧,是文件持久化、日志記錄、報表導出、文件上傳的基本功。
public class FileOutputStream extends OutputStream {
// 1. 覆蓋模式 (默認):文件不存在創建,存在則**截斷長度為 0** (清空內容)
public FileOutputStream(File file) throws FileNotFoundException;
public FileOutputStream(String name) throws FileNotFoundException;
public FileOutputStream(FileDescriptor fdObj);
// 2. 追加模式:文件不存在創建,存在則**定位到末尾追加**
public FileOutputStream(File file, boolean append) throws FileNotFoundException;
public FileOutputStream(String name, boolean append) throws FileNotFoundException;
// 3. 核心寫入方法
public native void write(int b) throws IOException; // 寫入單字節 (低 8 位)
public void write(byte[] b) throws IOException; // 寫入整個數組
public void write(byte[] b, int off, int len) throws IOException; // 寫入數組片段
public void close() throws IOException;
public FileChannel getChannel(); // NIO 通道
}
File file = new File("log.txt");
// 覆蓋模式 (默認)
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write("第一行\n".getBytes());
}
// 文件內容: "第一行\n"
// 追加模式
try (FileOutputStream fos = new FileOutputStream(file, true)) {
fos.write("第二行\n".getBytes());
}
// 文件內容: "第一行\n第二行\n"
try (FileOutputStream fos = new FileOutputStream("test.bin")) {
fos.write(0x123); // 實際寫入 0x23 (低 8 位)
fos.write(256); // 實際寫入 0x00
fos.write('A'); // 寫入 0x41 (ASCII 65)
fos.write(0x11223344); // 實際寫入 0x44
}
若需寫入 `int`/`long` 完整字節,請使用 `DataOutputStream` 裝飾或手動位移拆分。
String data = "Hello World";
byte[] bytes = data.getBytes(StandardCharsets.UTF_8); // 指定編碼
try (FileOutputStream fos = new FileOutputStream("out.txt")) {
fos.write(bytes); // 寫入全部
fos.write(bytes, 0, 5); // 寫入前 5 字節 "Hello"
fos.write(bytes, 6, bytes.length-6); // 寫入 "World"
}
public void copyFile(File src, File dest) throws IOException {
try (FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest)) {
byte[] buffer = new byte[8192]; // 8KB 緩衝區
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len); // 關鍵:使用實際讀取長度
}
}
}
public void copyFileBuffered(File src, File dest) throws IOException {
try (FileInputStream fis = new FileInputStream(src);
BufferedInputStream bis = new BufferedInputStream(fis, 8192);
FileOutputStream fos = new FileOutputStream(dest);
BufferedOutputStream bos = new BufferedOutputStream(fos, 8192)) {
byte[] buffer = new byte[8192];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
// bos.flush() 由 close() 自動調用
}
}
public void copyFileNIO(File src, File dest) throws IOException {
try (FileChannel in = FileChannel.open(src.toPath(), StandardOpenOption.READ);
FileChannel out = FileChannel.open(dest.toPath(),
StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
// OS 級零拷貝,不進入 JVM 堆內存
long transferred = 0;
long size = in.size();
while (transferred < size) {
transferred += in.transferTo(transferred, size - transferred, out);
}
}
}
Files.copy(src.toPath(), dest.toPath(),
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.COPY_ATTRIBUTES);
// 內部自動選擇最高效方式 (transferTo 或 緩衝區循環)
| 方式 | 耗時 | CPU 佔用 | 推薦場景 |
|---|---|---|---|
| 單字節 read/write | ~30 秒 | 極高 | 教學 |
| 手寫緩衝區循環 8KB | ~1.2 秒 | 中 | 學習、特殊處理 |
| BufferedInputStream/OutputStream | ~1.0 秒 | 中 | 通用生產首選 |
| FileChannel.transferTo (零拷貝) | ~0.3 秒 | 低 | 大文件、高吞吐 |
| Files.copy | ~0.3 秒 | 低 | 現代代碼首選 |
`FileOutputStream` 本身非線程安全。多線程追加需同步或用併發工具:
// 方案一:同步鎖 (簡單、低併發)
private final Object lock = new Object();
public void appendLog(String line) {
synchronized (lock) {
try (FileOutputStream fos = new FileOutputStream(logFile, true)) {
fos.write((line + "\n").getBytes(StandardCharsets.UTF_8));
} catch (IOException e) { /* handle */ }
}
}
// 方案二:單寫入線程 + 阻塞隊列 (高併發、高吞吐)
private final BlockingQueue queue = new LinkedBlockingQueue<>();
// 啟動專用寫入線程消費隊列寫入文件
public void atomicWrite(File target, byte[] data) throws IOException {
File temp = File.createTempFile("tmp", ".part", target.getParentFile());
try (FileOutputStream fos = new FileOutputStream(temp)) {
fos.write(data);
}
// 原子重命名 (同文件系統下 POSIX 保證原子)
if (!temp.renameTo(target)) {
throw new IOException("原子重命名失敗");
}
}
// JDK 7+ Files.newOutputStream 支持屬性
Files.newOutputStream(path,
StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING,
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-r-----")));
// RandomAccessFile 支持隨機定位寫入
try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
raf.seek(offset); // 跳到指定位置
raf.write(buffer, 0, len);
}
// 或 FileChannel.position(offset).write(ByteBuffer)
輸出流是數據持久化的大門,選對模式、用好緩衝、守住資源關閉,讓文件寫入既快又穩。