【问题标题】:Best way to write String to file using java nio使用 java nio 将字符串写入文件的最佳方法
【发布时间】:2011-09-09 19:01:50
【问题描述】:

我需要使用 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;
}

使用 ByteBuffer 和 Channel:耗时 4402 毫秒

使用缓冲写入器:耗时 563 毫秒

【问题讨论】:

  • 我认为至少还有三种方法可以在 Java 中将字符串写入文本文件。尝试在 SO 中搜索,有很多答案可以满足您的需求:)
  • @evilone。我理解有很多方法。如果人们拥有这些知识并且不介意分享,我不想测试所有可能的方法并进行分析并重新发明轮子。
  • @nobody。 “更好”是什么意思?快点?清洁器?就个人而言,我会遵循可读性路径并使用普通的 IO PrintWritter。你为什么要选择蔚来?
  • NIO 不会“将内存占用卸载到操作系统”。而且它肯定不会比你使用它的 BufferedWriter 快。
  • 更新:在Java 11中,只需使用Files.writeString一行即可。

标签: java file-io character-encoding nio


【解决方案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);

我没有用其他解决方案对这个解决方案进行基准测试,但是使用 open-write-close 文件的内置实现应该很快而且代码很小。

【讨论】:

  • @mtyson,链接中建议的解决方案是相同的,但使用某些参数的默认值。有什么意义?
  • 顺便说一句,有人否决了这个答案,很高兴知道为什么¯_(ツ)_/¯
  • @Roberto 我也想投反对票。看来,人们甚至没有检查语法就对其进行了投票。因为函数 write 将 Charset 作为第三个参数,而不是另一个标准选项。如果是这样,您的两个选项中的哪一个应该排在第四位。换句话说,你应该至少在两个地方更正你的调用。
  • 嗨@Gangnus,调用是正确的,write() 方法接受它们:docs.oracle.com/javase/7/docs/api/java/nio/file/…
  • @Roberto 对不起,这是我的错。我不明白为什么它在我的代码中不起作用 - 现在它起作用了。谢谢你。至于我,我也不明白为什么不解释为什么会被否决。
【解决方案2】:

如果不对您的软件进行基准测试,我认为您将无法获得严格的答案。 NIO 可以在适当的条件下显着加快应用程序的速度,但也可能使事情变慢。 以下是几点:

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

如果您喜欢一些前沿技术...返回IO Trails 获取一些 NIO2:D。

这里是interesting benchmark about file copying using different strategies。我知道这是一个不同的问题,但我认为大多数事实和作者的结论也适用于您的问题。

干杯,

更新 1:

由于@EJP 提示我直接缓冲区不会有效解决这个问题,我自己对它进行了基准测试,并最终得到了一个使用 nemory-mapped 文件的不错的 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's advice 并使用 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);

【讨论】:

  • 我自己做了比较。已更新问题。感谢您的精彩见解。
  • 很高兴为您服务。你介意告诉我们什么是最快的方法;)?
  • BufferedWriter 之一。您能否告诉使用 nio 编写的有效方法(我的意思是翻转缓冲区,创建 MappedByteBuffer 等等)?
  • '这篇文章'是关于复制文件的。 OP 正在将字符串写入文件。在这种情况下,直接缓冲区无济于事。
  • @user207421 (以前的 EJP)已经 8 年了......似乎尽管我的答案上面的第 4 个要点仍然困扰着你。坦率地说,我不会删除关于优化 NIO 性能的正确、完美合理(虽然有点过时)的答案——老实说,我认为没有理由反对。尽管如此,鉴于 OP - 遵循我和您的建议 - 已经发现普通的旧 Buffered IO 是回到过去的方式,我强烈鼓励您使用经典 IO 代码示例更新您的答案,如果没有别的,作为新 Java 开发人员的历史课。
【解决方案3】:

FileWriter 周围的 BufferedWriter 几乎肯定会比您想出的任何 NIO 方案都要快。您的代码肯定不是最佳的,每次写入都有一个新的 ByteBuffer,然后在它即将超出范围时对其进行无意义的操作,但无论如何您的问题都是建立在误解之上的。 NIO 根本不会“将内存占用转移到操作系统”,除非您使用 FileChannel.transferTo/From(),在这种情况下您不能这样做。

注意不要使用 cmets 中建议的 PrintWriter,因为这会吞下异常。 PW 真的只适用于你不关心的控制台和日志文件。

【讨论】:

  • @EJP 感谢您的意见。是的,我看到代码有问题。 java.io 非常新,并将事物链接在一起。看起来有很多东西要学。
【解决方案4】:

这是一个简短而简单的方法。它会创建一个文件并写入与您的代码项目相关的数据:

private void writeToFile(String filename, String data) {
    Path p = Paths.get(".", filename);
    try (OutputStream os = new BufferedOutputStream(
        Files.newOutputStream(p, StandardOpenOption.CREATE, StandardOpenOption.APPEND))) {
        os.write(data.getBytes(), 0, data.length());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

【讨论】:

  • 你把io和nio混在一起。
【解决方案5】:

这对我有用:

//Creating newBufferedWritter for writing to file
BufferedWritter napiš = Files.newBufferedWriter(Paths.get(filePath));
                    napiš.write(what);
//Don't forget for this (flush all what you write to String write):                     
                    napiš.flush();

【讨论】:

    猜你喜欢
    • 2011-06-28
    • 2016-10-19
    • 2010-09-15
    • 2011-04-10
    • 2021-09-08
    • 2011-08-26
    • 1970-01-01
    • 2011-05-16
    相关资源
    最近更新 更多