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

发布于 2024-12-03 13:53:56 字数 2922 浏览 0 评论 0原文

我需要使用 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 毫秒

I need to write(append) huge string to flat file using java nio. The encoding is ISO-8859-1.

Currently we are writing as shown below. Is there any better way to do the same ?

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();
}

EDIT: Tried both options. Following are the results.

@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;
}

Using ByteBuffer and Channel: took 4402 ms

Using buffered Writer : Took 563 ms

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

深海里的那抹蓝 2024-12-10 13:53:56

更新

从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);

我还没有与其他解决方案进行基准测试,但是使用打开-写入-关闭文件的内置实现应该是快速和代码相当小。

UPDATED:

Since Java11 there is a specific method to write strings using java.nio.file.Files:

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

We can also customize the writing with:

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

ORIGINAL ANSWER:

There is a one-line solution, using Java nio:

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

I have not benchmarked this solution with the others, but using the built-in implementation for open-write-close file should be fast and the code is quite small.

把人绕傻吧 2024-12-10 13:53:56

我认为如果不对您的软件进行基准测试,您将无法得到严格的答案。在适当的条件下,NIO 可能会显着加快应用程序的速度,但也可能会使速度变慢。
这里有一些要点:

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

如果您喜欢一些前沿...返回IO Trails对于一些 NIO2 :D。

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

干杯,

更新 1:

由于 @EJP 告诉我直接缓冲区对于这个问题效率不高,所以我自己对其进行了基准测试,并最终得到了一个使用内存映射文件的不错的 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+)解决方案来解决问题的人,我会遵循 <一个href="https://stackoverflow.com/questions/7366266/best-way-to-write-string-to-file-using-java-nio/7367952?noredirect=1#comment95943399_7366266">@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);

I don't think you will be able to get a strict answer without benchmarking your software. NIO may speed up the application significantly under the right conditions, but it may also make things slower.
Here are some points:

  • Do you really need strings? If you store and receive bytes from you database you can avoid string allocation and encoding costs all together.
  • Do you really need rewind and flip? Seems like you are creating a new buffer for every string and just writing it to the channel. (If you go the NIO way, benchmark strategies that reuse the buffers instead of wrapping / discarding, I think they will do better).
  • Keep in mind that wrap and allocateDirect may produce quite different buffers. Benchmark both to grasp the trade-offs. With direct allocation, be sure to reuse the same buffer in order to achieve the best performance.
  • And the most important thing is: Be sure to compare NIO with BufferedOutputStream and/or BufferedWritter approaches (use a intermediate byte[] or char[] buffer with a reasonable size as well). I've seen many, many, many people discovering that NIO is no silver bullet.

If you fancy some bleeding edge... Back to IO Trails for some NIO2 :D.

And here is a interesting benchmark about file copying using different strategies. I know it is a different problem, but I think most of the facts and author conclusions also apply to your problem.

Cheers,

UPDATE 1:

Since @EJP tiped me that direct buffers wouldn't be efficient for this problem, I benchmark it myself and ended up with a nice NIO solution using nemory-mapped files. In my Macbook running OS X Lion this beats BufferedOutputStream by a solid margin. but keep in mind that this might be OS / Hardware / VM specific:

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);
    }
} 

I admit that I cheated a little by calculating the total size to append (around 26 MB) beforehand. This may not be possible for several real world scenarios. Still, you can always use a "big enough appending size for the operations and later truncate the file.

UPDATE 2 (2019):

To anyone looking for a modern (as in, Java 11+) solution to the problem, I would follow @DodgyCodeException's advice and use 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);
拒绝两难 2024-12-10 13:53:56

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

注意,不要按照注释中的建议使用 PrintWriter,因为这会吞掉异常。 PW 实际上仅适用于您不关心的控制台和日志文件。

A BufferedWriter around a FileWriter will almost certainly be faster than any NIO scheme you can come up with. Your code certainly isn't optimal, with a new ByteBuffer per write, and then doing pointless operations on it when it is about to go out of scope, but in any case your question is founded on a misconception. NIO doesn't 'offload the memory footprint to the OS' at all, unless you're using FileChannel.transferTo/From(), which you can't in this instance.

NB don't use a PrintWriter as suggested in comments, as this swallows exceptions. PW is really only for consoles and log files where you don't care.

送君千里 2024-12-10 13:53:56

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

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();
    }
}

Here is a short and easy way. It creates a file and writes the data relative to your code project:

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();
    }
}
回忆追雨的时光 2024-12-10 13:53:56

这对我有用:

//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();

This works for me:

//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();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文