Java – 写入文件

评论 0 浏览 0 2014-10-02

1. 概述

在本教程中,我们将探索使用 Java 写入文件的不同方法。我们将使用 BufferedWriterPrintWriterFileOutputStreamDataOutputStreamRandomAccessFileFileChannel 和 Java 7 Files 实用程序类。

我们还将研究在写入时锁定文件,并讨论写入文件的一些最终要点。

本教程是Java“回归基础”系列的一部分。

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

然后我们可以附加一个字符串到现有文件

@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$

请注意,我们不仅将原始 String 写入文件,而且还使用 printf 方法写入一些格式化文本。

我们可以使用 FileWriterBufferedWriter 甚至 System.out 创建编写器。

4. 使用FileOutputStream写入

现在让我们看看如何使用FileOutputStream将二进制数据写入文件。

以下代码将 String 转换为字节,并使用 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写入

接下来,让我们看看如何使用DataOutputStreamString写入文件:

@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 更快。以下代码使用 FileChannel 将 String 写入文件

@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

使用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. 写入临时文件

现在让我们尝试写入临时文件。以下代码创建一个临时文件并向其中写入一个 String

@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
  • 如果我们尝试写入一个不存在的文件,该文件将首先被创建,并且不会抛出异常。
  • 使用流后关闭流非常重要,因为它不会隐式关闭以释放与其关联的任何资源。
  • 在输出流中,close() 方法在释放资源之前调用 flush(),这会强制将所有缓冲的字节写入流中。

看看常见的使用习惯,我们可以看到,例如PrintWriter用于写入格式化文本,FileOutputStream用于写入二进制数据,DataOutputStream 用于写入原始数据类型,RandomAccessFile用于写入特定位置,FileChannel用于在较大文件中更快地写入。这些课程确实允许更多,但这是一个很好的起点。

12. 结论

本文介绍了使用 Java 将数据写入文件的多种选项。

所有这些示例和代码片段的实现可以在 在 GitHub 上

最后更新2024-03-04
0 个评论