源本科技 | 码上会

Java 字节输出流

2026/01/30
30
0

学习目标

  • 理解 FileOutputStream 的定位与适用场景

  • 掌握五种构造方式,特别是追加模式的使用

  • 能够正确写入二进制数据(如图片、音频)

  • 熟悉核心写入方法与资源管理机制

  • 了解与 NIO 的集成能力


什么是 FileOutputStream

FileOutputStream 是 Java 中用于向文件写入原始字节数据的输出流,继承自 OutputStream。它是处理二进制文件(如图像、音频、视频、压缩包)的标准工具。

重要提示:虽然可以用它写入文本,但不推荐!应使用 FileWriterOutputStreamWriter(可指定编码),避免跨平台乱码问题。

核心特性

  • 字节级操作:每次写入 1 个或多个 byte(8 位)

  • 无缓冲:直接写入磁盘,频繁小量写入性能较差

  • 平台无关:自动适配不同操作系统的文件系统

  • 支持追加模式:通过构造器参数控制覆盖或追加

  • 支持 NIO:可通过 getChannel() 获取 FileChannel 进行高效 I/O


类声明

public class FileOutputStream extends OutputStream
  • 继承 OutputStream:获得 write()flush()close() 等基础方法

  • 实现 CloseableFlushable:支持 try-with-resources 和强制刷新

  • 封装底层文件写入:将 Java 字节流映射到操作系统文件句柄


构造器

构造器

说明

覆盖 / 追加行为

FileOutputStream(String name)

通过文件路径字符串创建

覆盖原文件

FileOutputStream(File file)

通过 File 对象创建

覆盖原文件

FileOutputStream(String name, boolean append)

指定路径 + 追加模式

append=true追加

FileOutputStream(File file, boolean append)

File 对象 + 追加模式

append=true追加

FileOutputStream(FileDescriptor fd)

通过文件描述符创建(高级用法)

取决于描述符状态

// 覆盖模式(默认)
FileOutputStream fos1 = new FileOutputStream("log.txt");
FileOutputStream fos2 = new FileOutputStream(new File("data.bin"));

// 追加模式
FileOutputStream fos3 = new FileOutputStream("log.txt", true);
FileOutputStream fos4 = new FileOutputStream(new File("backup.zip"), true);

// 从标准输出(极少使用)
FileOutputStream fos5 = new FileOutputStream(FileDescriptor.out);

最佳实践:明确指定 append 参数,避免意外覆盖重要数据


核心方法

写入方法

方法

说明

void write(int b)

写入单个字节(低 8 位有效)

void write(byte[] b)

写入整个字节数组

void write(byte[] b, int off, int len)

从偏移 off 开始,写入 len 个字节

控制与查询

方法

返回值

说明

void flush()

void

强制将缓冲数据写入磁盘
(对 FileOutputStream 无实际作用,因无缓冲)

void close()

void

关闭流并释放系统资源

FileChannel getChannel()

FileChannel

获取关联的 NIO 通道

FileDescriptor getFD()

FileDescriptor

获取底层文件描述符

注意:FileOutputStream 本身无缓冲,所以 flush() 是空操作。若需缓冲,应包装 BufferedOutputStream


基础示例

写入文本数据

import java.io.*;

public class FileOutputStreamExample {
    public static void main(String[] args) {
        String message = "Hello, Java FileOutputStream!";
        
        try (FileOutputStream fos = new FileOutputStream("output.txt")) {
            // 将字符串转为字节数组(使用平台默认编码)
            byte[] data = message.getBytes();
            
            // 写入文件
            fos.write(data);
            
            System.out.println("数据已成功写入 output.txt");
        } catch (IOException e) {
            System.err.println("写入失败: " + e.getMessage());
        }
    }
}

警告:getBytes() 使用系统默认编码,跨平台可能乱码!生产环境应使用:

byte[] data = message.getBytes(StandardCharsets.UTF_8);

正确用法

二进制文件写入(图片保存)

以下程序演示如何将字节数组保存为图片文件:

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;

public class SaveImage {
    public static void main(String[] args) {
        try {
            // 假设我们有一个字节数组(例如从网络下载的图片数据)
            byte[] imageData = Files.readAllBytes(Paths.get("source.jpg"));
            
            // 保存为新文件
            try (FileOutputStream fos = new FileOutputStream("saved_image.jpg")) {
                fos.write(imageData); // 直接写入原始字节
            }
            
            System.out.println("图片已保存为 saved_image.jpg");
        } catch (IOException e) {
            System.err.println("文件操作失败: " + e.getMessage());
        }
    }
}

追加模式

日志记录

import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class LogWriter {
    private static final String LOG_FILE = "app.log";
    
    public static void log(String message) {
        String timestamp = LocalDateTime.now()
            .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        String logEntry = "[" + timestamp + "] " + message + System.lineSeparator();
        
        try (FileOutputStream fos = new FileOutputStream(LOG_FILE, true)) {
            fos.write(logEntry.getBytes()); // 追加到文件末尾
        } catch (IOException e) {
            System.err.println("日志写入失败: " + e.getMessage());
        }
    }
    
    public static void main(String[] args) {
        log("应用程序启动");
        log("用户登录成功");
        log("数据处理完成");
        System.out.println("日志已追加到 app.log");
    }
}

app.log 内容:

[2026-01-30 13:45:01] 应用程序启动
[2026-01-30 13:45:02] 用户登录成功
[2026-01-30 13:45:03] 数据处理完成

重点总结

  • FileOutputStream 专为二进制数据写入设计,文本写入请用 FileWriter

  • 五种构造器,追加模式通过 boolean append 参数控制

  • 核心方法:write()(单字节 / 数组)、close()getChannel()

  • 无内置缓冲,性能敏感场景需包装 BufferedOutputStream

  • 必须使用 try-with-resources 确保资源释放

  • 通过 getChannel() 支持 NIO 高级功能(内存映射、异步 I/O)


思考题

  1. 为什么 FileOutputStreamflush() 方法实际上不做任何事情?在什么情况下我们需要手动调用 flush()

  2. 如果向一个已存在的文件写入数据时未指定追加模式,会发生什么?如何避免意外覆盖重要文件?

  3. 在高并发日志系统中,直接使用 FileOutputStream 追加写入是否安全?为什么?应如何改进?