1. 概述

在本文中,我们将深入探讨 使用 Java 向文件写入数据的多种方式。我们会使用到如下几个核心类和工具:

  • BufferedWriter
  • PrintWriter
  • FileOutputStream
  • DataOutputStream
  • RandomAccessFile
  • FileChannel
  • Java 7 中引入的 Files 工具类

此外,我们还会介绍如何在写入时对文件进行加锁,并总结一些实用的经验。

本教程属于 Baeldung Java “Back to Basics” 系列 的一部分。

2. 使用 BufferedWriter 写入文件

最基础的方式是使用 BufferedWriter 将字符串写入文件:

public void whenWriteStringUsingBufferedWritter_thenCorrect() 
  throws IOException {
    String str = "Hello";
    BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
    writer.write(str);
    
    writer.close();
}

最终文件内容为:

Hello

追加写入也很简单,只需将 FileWriter 构造函数的第二个参数设为 true

@Test
public void whenAppendStringUsingBufferedWritter_thenOldContentShouldExistToo() 
  throws IOException {
    String str = "World";
    BufferedWriter writer = new BufferedWriter(new FileWriter(fileName, true));
    writer.append(' ');
    writer.append(str);
    
    writer.close();
}

文件内容变为:

Hello World

3. 使用 PrintWriter 格式化写入

PrintWriter 更适合用于格式化输出,比如写入日志或结构化数据:

@Test
public void givenWritingStringToFile_whenUsingPrintWriter_thenCorrect() 
  throws IOException {
    FileWriter fileWriter = new FileWriter(fileName);
    PrintWriter printWriter = new PrintWriter(fileWriter);
    printWriter.print("Some String");
    printWriter.printf("Product name is %s and its price is %d $", "iPhone", 1000);
    printWriter.close();
}

文件输出如下:

Some String
Product name is iPhone and its price is 1000$

⚠️ 注意:PrintWriter 支持 printprintf,可以灵活处理字符串和格式化内容。

4. 使用 FileOutputStream 写入二进制数据

如果你需要写入的是字节数据,推荐使用 FileOutputStream

@Test
public void givenWritingStringToFile_whenUsingFileOutputStream_thenCorrect() 
  throws IOException {
    String str = "Hello";
    FileOutputStream outputStream = new FileOutputStream(fileName);
    byte[] strToBytes = str.getBytes();
    outputStream.write(strToBytes);

    outputStream.close();
}

文件内容:

Hello

5. 使用 DataOutputStream 写入原始数据类型

DataOutputStream 适合写入 Java 原始数据类型(如 int、double 等),常用于序列化:

@Test
public void givenWritingToFile_whenUsingDataOutputStream_thenCorrect() 
  throws IOException {
    String value = "Hello";
    FileOutputStream fos = new FileOutputStream(fileName);
    DataOutputStream outStream = new DataOutputStream(new BufferedOutputStream(fos));
    outStream.writeUTF(value);
    outStream.close();

    // verify the results
    String result;
    FileInputStream fis = new FileInputStream(fileName);
    DataInputStream reader = new DataInputStream(fis);
    result = reader.readUTF();
    reader.close();

    assertEquals(value, result);
}

6. 使用 RandomAccessFile 随机位置写入

当你需要在文件的某个特定位置写入或修改数据时,RandomAccessFile 是不二之选:

private void writeToPosition(String filename, int data, long position) 
  throws IOException {
    RandomAccessFile writer = new RandomAccessFile(filename, "rw");
    writer.seek(position);
    writer.writeInt(data);
    writer.close();
}

读取指定位置的 int 值:

private int readFromPosition(String filename, long position) 
  throws IOException {
    int result = 0;
    RandomAccessFile reader = new RandomAccessFile(filename, "r");
    reader.seek(position);
    result = reader.readInt();
    reader.close();
    return result;
}

测试写入和读取:

@Test
public void whenWritingToSpecificPositionInFile_thenCorrect() 
  throws IOException {
    int data1 = 2014;
    int data2 = 1500;
    
    writeToPosition(fileName, data1, 4);
    assertEquals(data1, readFromPosition(fileName, 4));
    
    writeToPosition(fileName2, data2, 4);
    assertEquals(data2, readFromPosition(fileName, 4));
}

7. 使用 FileChannel 提升大文件写入性能

对于大文件操作,FileChannel 比传统 IO 更高效:

@Test
public void givenWritingToFile_whenUsingFileChannel_thenCorrect() 
  throws IOException {
    RandomAccessFile stream = new RandomAccessFile(fileName, "rw");
    FileChannel channel = stream.getChannel();
    String value = "Hello";
    byte[] strBytes = value.getBytes();
    ByteBuffer buffer = ByteBuffer.allocate(strBytes.length);
    buffer.put(strBytes);
    buffer.flip();
    channel.write(buffer);
    stream.close();
    channel.close();

    // verify
    RandomAccessFile reader = new RandomAccessFile(fileName, "r");
    assertEquals(value, reader.readLine());
    reader.close();
}

8. 使用 Java 7 的 Files 工具类

Java 7 引入了新的文件操作方式,其中 Files 类非常强大:

@Test
public void givenUsingJava7_whenWritingToFile_thenCorrect() 
  throws IOException {
    String str = "Hello";

    Path path = Paths.get(fileName);
    byte[] strToBytes = str.getBytes();

    Files.write(path, strToBytes);

    String read = Files.readAllLines(path).get(0);
    assertEquals(str, read);
}

9. 写入临时文件

临时文件的创建也很常见,例如用于缓存或测试:

@Test
public void whenWriteToTmpFile_thenCorrect() throws IOException {
    String toWrite = "Hello";
    File tmpFile = File.createTempFile("test", ".tmp");
    FileWriter writer = new FileWriter(tmpFile);
    writer.write(toWrite);
    writer.close();

    BufferedReader reader = new BufferedReader(new FileReader(tmpFile));
    assertEquals(toWrite, reader.readLine());
    reader.close();
}

⚠️ 创建临时文件是关键点,写入操作与其他方式一致。

10. 写入前加锁文件

为了防止多个线程或进程同时写入同一文件,我们可以使用 FileChannel 加锁:

@Test
public void whenTryToLockFile_thenItShouldBeLocked() 
  throws IOException {
    RandomAccessFile stream = new RandomAccessFile(fileName, "rw");
    FileChannel channel = stream.getChannel();

    FileLock lock = null;
    try {
        lock = channel.tryLock();
    } catch (final OverlappingFileLockException e) {
        stream.close();
        channel.close();
    }
    stream.writeChars("test lock");
    lock.release();

    stream.close();
    channel.close();
}

⚠️ 如果文件已被锁定,将抛出 OverlappingFileLockException

11. 注意事项

在使用这些文件写入方式时,有几点值得注意:

  • ❌ 尝试读取不存在的文件会抛出 FileNotFoundException
  • ✅ 写入不存在的文件时,Java 会自动创建文件
  • ⚠️ 务必手动调用 close() 方法释放资源,尤其是输出流
  • ⚠️ close() 方法内部会调用 flush(),确保缓冲区数据被写入

不同类的使用场景总结如下:

类名 使用场景
PrintWriter 格式化文本输出
FileOutputStream 写入二进制数据
DataOutputStream 写入原始数据类型
RandomAccessFile 随机访问写入
FileChannel 大文件高性能写入

12. 总结

本文全面介绍了 Java 中向文件写入数据的多种方式,包括基础的文本写入、二进制数据处理、随机位置写入、文件加锁等高级操作。

所有示例代码均可在 GitHub 项目中找到


原始标题:Java - Write to File