使用java nio将字符串写入文件的最佳方法

2022-09-01 00:29:33

我需要使用java nio将巨大的字符串写入(追加)到平面文件中。编码为 ISO-8859-1。

目前,我们正在编写如下图所示。有没有更好的方法来做同样的事情?

public void writeToFile(Long limit) throws IOException{
     String fileName = "/xyz/test.txt";
     File file = new File(fileName);        
     FileOutputStream fileOutputStream = new FileOutputStream(file, true);  
     FileChannel fileChannel = fileOutputStream.getChannel();
     ByteBuffer byteBuffer = null;
     String messageToWrite = null;
     for(int i=1; i<limit; i++){
         //messageToWrite = get String Data From database
         byteBuffer = ByteBuffer.wrap(messageToWrite.getBytes(Charset.forName("ISO-8859-1")));
         fileChannel.write(byteBuffer);         
     }
     fileChannel.close();
}

编辑:尝试了两个选项。以下是结果。

@Test
public void testWritingStringToFile() {
    DiagnosticLogControlManagerImpl diagnosticLogControlManagerImpl = new DiagnosticLogControlManagerImpl();
    try {
        File file = diagnosticLogControlManagerImpl.createFile();
        long startTime = System.currentTimeMillis();
        writeToFileNIOWay(file);
        //writeToFileIOWay(file);
        long endTime = System.currentTimeMillis();
        System.out.println("Total Time is  " + (endTime - startTime));
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

/**
 *
 * @param limit
 *            Long
 * @throws IOException
 *             IOException
 */
public void writeToFileNIOWay(File file) throws IOException {
    FileOutputStream fileOutputStream = new FileOutputStream(file, true);
    FileChannel fileChannel = fileOutputStream.getChannel();
    ByteBuffer byteBuffer = null;
    String messageToWrite = null;
    for (int i = 1; i < 1000000; i++) {
        messageToWrite = "This is a test üüüüüüööööö";
        byteBuffer = ByteBuffer.wrap(messageToWrite.getBytes(Charset
            .forName("ISO-8859-1")));
        fileChannel.write(byteBuffer);
    }
}

/**
 *
 * @param limit
 *            Long
 * @throws IOException
 *             IOException
 */
public void writeToFileIOWay(File file) throws IOException {
    FileOutputStream fileOutputStream = new FileOutputStream(file, true);
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
        fileOutputStream, 128 * 100);
    String messageToWrite = null;
    for (int i = 1; i < 1000000; i++) {
        messageToWrite = "This is a test üüüüüüööööö";
        bufferedOutputStream.write(messageToWrite.getBytes(Charset
            .forName("ISO-8859-1")));
    }
    bufferedOutputStream.flush();
    fileOutputStream.close();
}

private File createFile() throws IOException {
    File file = new File(FILE_PATH + "test_sixth_one.txt");
    file.createNewFile();
    return file;
}

使用字节缓冲器和通道:花费 4402 毫秒

使用缓冲写入器:花费563毫秒


答案 1

更新时间

由于Java11有一个特定的方法来编写字符串::java.nio.file.Files

Files.writeString(Paths.get(file.toURI()), "My string to save");

我们还可以使用以下命令自定义写作:

Files.writeString(Paths.get(file.toURI()), 
                  "My string to save", 
                   StandardCharsets.UTF_8,
                   StandardOpenOption.CREATE,
                   StandardOpenOption.TRUNCATE_EXISTING);

原答

有一个单行解决方案,使用Java nio:

java.nio.file.Files.write(Paths.get(file.toURI()), 
                          "My string to save".getBytes(StandardCharsets.UTF_8),
                          StandardOpenOption.CREATE,
                          StandardOpenOption.TRUNCATE_EXISTING);

我还没有将此解决方案与其他解决方案进行基准测试,但是对打开 - 写入 - 关闭文件使用内置实现应该很快并且代码非常小。


答案 2

我不认为你能够在不对你的软件进行基准测试的情况下得到一个严格的答案。在适当的条件下,NIO可能会显着加快应用程序的速度,但它也可能使事情变慢。以下是一些要点:

  • 你真的需要字符串吗?如果从数据库存储和接收字节,则可以避免字符串分配和编码成本。
  • 你真的需要和吗?似乎您正在为每个字符串创建一个新的缓冲区,然后将其写入通道。(如果你走NIO的方式,重用缓冲区而不是包装/丢弃的基准策略,我认为它们会做得更好)。rewindflip
  • 请记住,和 allocateDirect 可能会产生完全不同的缓冲区。对两者进行基准测试,以掌握权衡取舍。使用直接分配时,请确保重用相同的缓冲区,以实现最佳性能。wrap
  • 最重要的是:确保将NIO与BufferedOutputStream和/或BufferedWritter方法进行比较(也使用具有合理大小的中间或缓冲区)。我看到过很多很多人发现NIO不是银弹。byte[]char[]

如果你喜欢一些前沿...回到IO Trails,了解一些NIO2:D。

这是一个关于使用不同策略的文件复制的有趣基准。我知道这是一个不同的问题,但我认为大多数事实和作者结论也适用于你的问题。

干杯

更新 1:

由于@EJP提示我直接缓冲区对这个问题效率不高,所以我自己对其进行了基准测试,并最终使用nemory映射文件获得了一个不错的NIO解决方案。在我运行OS X Lion的Macbook中,这以坚实的差距击败了我。但请记住,这可能是特定于操作系统/硬件/虚拟机的:BufferedOutputStream

public void writeToFileNIOWay2(File file) throws IOException {
    final int numberOfIterations = 1000000;
    final String messageToWrite = "This is a test üüüüüüööööö";
    final byte[] messageBytes = messageToWrite.
            getBytes(Charset.forName("ISO-8859-1"));
    final long appendSize = numberOfIterations * messageBytes.length;
    final RandomAccessFile raf = new RandomAccessFile(file, "rw");
    raf.seek(raf.length());
    final FileChannel fc = raf.getChannel();
    final MappedByteBuffer mbf = fc.map(FileChannel.MapMode.READ_WRITE, fc.
            position(), appendSize);
    fc.close();
    for (int i = 1; i < numberOfIterations; i++) {
        mbf.put(messageBytes);
    }
} 

我承认我通过事先计算要附加的总大小(约26 MB)来作弊。对于几个实际方案,这可能是不可能的。不过,您始终可以使用“足够大的追加大小”进行操作,并在以后截断文件。

更新2(2019):

对于任何想要一个现代的(如Java 11 +)解决方案的人,我会遵循@DodgyCodeException的建议并使用java.nio.file.Files.writeString

String fileName = "/xyz/test.txt";
String messageToWrite = "My long string";
Files.writeString(Paths.get(fileName), messageToWrite, StandardCharsets.ISO_8859_1);