首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java IO 字節輸出流 FileOutputStream 完整指南:寫入模式、追加、拷貝實戰

     編輯:Java 綜合教學

Java IO 字節輸出流 FileOutputStream 完整指南:寫入模式、追加、拷貝實戰

`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 通道
}

二、關鍵行為詳解

1. 覆蓋 vs 追加

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"

2. `write(int b)` 只寫低 8 位

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` 裝飾或手動位移拆分。

3. 字節數組寫入邊界

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"
}

三、高效拷貝:輸入流 + 輸出流組合

1. 基礎拷貝 (緩衝區循環)

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); // 關鍵:使用實際讀取長度
        }
    }
}

2. 裝飾器模式:BufferedOutputStream (推薦生產)

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() 自動調用
    }
}

3. NIO 通道零拷貝 (大文件最快)

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);
        }
    }
}

4. JDK 7+ Files.copy 一行代碼

Files.copy(src.toPath(), dest.toPath(), 
           StandardCopyOption.REPLACE_EXISTING, 
           StandardCopyOption.COPY_ATTRIBUTES);
// 內部自動選擇最高效方式 (transferTo 或 緩衝區循環)

四、效能對比 (100MB 文件拷貝)

方式耗時CPU 佔用推薦場景
單字節 read/write~30 秒極高教學
手寫緩衝區循環 8KB~1.2 秒中學習、特殊處理
BufferedInputStream/OutputStream~1.0 秒中通用生產首選
FileChannel.transferTo (零拷貝)~0.3 秒低大文件、高吞吐
Files.copy~0.3 秒低現代代碼首選

五、進階場景與最佳實踐

1. 線程安全追加寫入 (多線程日志)

`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<>();
// 啟動專用寫入線程消費隊列寫入文件

2. 原子性寫入 (防止讀到半寫文件)

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("原子重命名失敗");
    }
}

3. 指定文件權限 (POSIX)

// JDK 7+ Files.newOutputStream 支持屬性
Files.newOutputStream(path, 
    StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING,
    PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-r-----")));

4. 大文件斷點續傳 / 分片寫入

// RandomAccessFile 支持隨機定位寫入
try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
    raf.seek(offset); // 跳到指定位置
    raf.write(buffer, 0, len);
}
// 或 FileChannel.position(offset).write(ByteBuffer)

六、常見錯誤與避坑

  • ❌ 忘記 `append=true` 導致日志被覆蓋。
  • ❌ `write(int)` 寫入超出 255 的值,實際截斷低 8 位。
  • ❌ 忽略 `write(byte[], off, len)` 的 `len`,寫入整個緩衝區導致殘留數據。
  • ❌ 未 `flush()` / `close()` 導致數據停留在 OS 緩存,進程崩潰丟失數據。
  • ❌ 多線程無同步並發寫入同一文件,內容交錯、損壞。
  • ❌ 路徑遍歷漏洞:用戶控制文件名未校驗 `../`,導致任意文件寫入。

七、總結

  1. 覆蓋模式:`new FileOutputStream(file)` — 重新寫入。
  2. 追加模式:`new FileOutputStream(file, true)` — 日志、增量數據。
  3. 寫入單位:優先 `write(byte[], off, len)` 批量寫入,避免單字節。
  4. 裝飾器:`BufferedOutputStream` 自動緩衝,生產首選。
  5. 大文件拷貝:`Files.copy` 或 `FileChannel.transferTo` 零拷貝最快。
  6. 資源管理:`try-with-resources` 保證關閉刷新。

輸出流是數據持久化的大門,選對模式、用好緩衝、守住資源關閉,讓文件寫入既快又穩。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved