48

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

nobody
  • 1,909
  • 5
  • 31
  • 45
  • I think there's at least three ways more to write string to text file in Java. Try to search here in SO, there's plenty of answers for your needs :) – evilone Sep 09 '11 at 19:04
  • 4
    @evilone. I understand there are many ways. I didn't want to test all those possible ways and do the profiling and reinvent the wheel, if people have that knowledge and don't mind sharing. – nobody Sep 09 '11 at 19:06
  • 2
    @nobody. What do you mean by "better"? Faster? Cleaner? Personally, I would follow the readability path and go with plain IO [PrintWritter](http://download.oracle.com/javase/6/docs/api/java/io/PrintWriter.html). Why do you want to go with NIO anyway? – Anthony Accioly Sep 09 '11 at 19:36
  • @Anthony Accioly Faster. Don't mind the cleanliness. Want to go for nio to offload the memory footprint to OS and not jvm. Also heard nio is faster, haven't done the profiling myself though. – nobody Sep 09 '11 at 19:41
  • 2
    NIO doesn't 'offload the memory footprint to the OS'. And it certainly won't be faster than a BufferedWriter the way you're using it. – user207421 Sep 09 '11 at 22:27
  • 4
    Update: in Java 11, just do it with one line using [Files.writeString](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#writeString(java.nio.file.Path,java.lang.CharSequence,java.nio.charset.Charset,java.nio.file.OpenOption...)). – DodgyCodeException Feb 07 '19 at 11:59

5 Answers5

60

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.

Roberto
  • 8,586
  • 3
  • 42
  • 53
  • @mtyson, The proposed solution in the link is the same, but using the default values for some parameters. What is the point ? – Roberto Oct 30 '17 at 09:24
  • 1
    BTW Someone has downvoted this answer, It would be nice to know why ¯\_(ツ)_/¯ – Roberto Oct 30 '17 at 12:28
  • @Roberto I want to downvote it, too. It seems, people upvoted it without checking even the syntax. Because the function write takes Charset as the third parameter, not another Standard option. And if so, what of your two options should stay on the fourth place. In other words, you should correct your call minimally in two places. – Gangnus Nov 28 '17 at 09:17
  • 1
    Hi @Gangnus, the call is correct, write() method accepts both of them: https://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#write(java.nio.file.Path,%20byte[],%20java.nio.file.OpenOption...) – Roberto Nov 28 '17 at 13:00
  • @Roberto Sorry, it is my fault. I don't understand why it didn't work in my code - now it works. Thank you. As for me, I too, don't understand the downvoting without explanation why. – Gangnus Nov 28 '17 at 13:25
22

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);
Community
  • 1
  • 1
Anthony Accioly
  • 21,918
  • 9
  • 70
  • 118
  • 1
    Did the comparison my self. Have updated the question. Thanks for this wonderful insight. – nobody Sep 09 '11 at 22:56
  • Glad to be of service. Would you mind telling us what was the fastest approach ;)? – Anthony Accioly Sep 09 '11 at 23:22
  • The BufferedWriter one. Could you please tell the effective way to write using nio(I mean flipping the buffer, creating a MappedByteBuffer and all) ? – nobody Sep 10 '11 at 00:33
  • Check [this](http://stackoverflow.com/questions/1605332/java-nio-filechannel-versus-fileoutputstream-performance-usefulness) post. You will be able to squeeze a lot of performance by allocating a direct buffer and reusing it. You will also be able to improve the IO version by using a intermediate byte[] array as buffer. Mess around with the buffer size and see how it affects your benchmark. Also try fetching the text column of your database direct as a byte array, this will avoid the encoding step. – Anthony Accioly Sep 10 '11 at 04:13
  • One more thing, use `messageToWrite` variables representative to your database contents. Writing a 20 characters String over and over again may give very different results than writing several different hundreds or (or thousands) lines strings. Again, avoiding Strings and encoding might squeeze that last tad of performance. – Anthony Accioly Sep 10 '11 at 04:20
  • 1
    'This post' is about copying a file. The OP is writing Strings to a file. A direct buffer cannot help in this situation. – user207421 Sep 10 '11 at 08:26
  • @EJP Again. You are right. See my update. OP, do you care to run the benchmark in your box and share the results for further reference? – Anthony Accioly Sep 11 '11 at 00:06
  • As you've noticed, you can't use a memory mapped file for output in general, because you can't grow it. Preallocating the file size is fine if you know what it is, or know an absolute upper bound, but does the OP know that? – user207421 Sep 11 '11 at 03:34
  • Upper bound is not a big deal. Since OP is using Strings that fits into memory, it is no rocket science to preallocate a reasonable size. Also, within big loops you can always replace the MappedByteBuffer with a new slice once in a while when you are running low of remaining space. I can see tons of applications where I could've used MMFs for output optimization. And appending [more than a few tens of kb](http://download.oracle.com/javase/7/docs/api/java/nio/channels/FileChannel.html#map%28java.nio.channels.FileChannel.MapMode,%20long,%20long%29) is definitively one of them. – Anthony Accioly Sep 11 '11 at 14:59
  • buffer.put accept only byte value ,when i am converting it into byte using getByte() method its also takes too much time to process it? do you have any solution? – Rinkesh Jul 25 '13 at 07:57
  • @rinkesh If you want to work at character level, go for [CharBuffer](http://docs.oracle.com/javase/7/docs/api/java/nio/CharBuffer.html), or better yet, [BufferedWriter](http://docs.oracle.com/javase/7/docs/api/java/io/BufferedWriter.html). Or, if possible, read and write as bits, which, when [done right](http://stackoverflow.com/a/304059/66457700) will speed up things significatively. – Anthony Accioly Jul 25 '13 at 12:38
  • @AnthonyAccioly i have to write large data itrating using list so how to boost up performance right now m using BufferWriter – Rinkesh Jul 25 '13 at 12:46
  • You don't need any of this. You can solve it entirely within `java.io` in about 3 lines of code. – user207421 May 08 '19 at 10:58
  • 1
    @user207421 (formerly EJP) It has been 8 years already... It seems that despite the 4th bullet point above my answer still bothers you. Frankly, I'm not going to delete a correct, perfect reasonable (although slightly outdated) answer about optimizing NIO performance - and honestly I see no reason for the downvote. Nevertheless, given that the OP - following both mine and your advice - has figured out that plain old Buffered IO was the way to go back in the day, I deeply encourage you to update your answer with classic IO code example, if nothing else, as a history lesson for new Java Devs. – Anthony Accioly May 08 '19 at 13:06
9

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.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • @EJP Thanks for the input. Yeah I see code has problems. Very new to java.io and chaining things together. Looks like there's lot to learn. – nobody Sep 10 '11 at 00:22
0

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

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();
László Papp
  • 51,870
  • 39
  • 111
  • 135