how to write to java file in the best way?

What is the best way to write to a file in a parallel thread in Java?

  • I have a program that performs lots of calculations and reports them to a file frequently, I know that frequent write operations can slow a program down a lot, so to avoid it I'd like to have a second thread dedicated to the writing operations. Right now I'm doing it with this class I wrote (the impatient can skip to the end of the question): public class ParallelWriter implements Runnable { private File file; private BlockingQueue<Item> q; private int indentation; public ParallelWriter( File f ){ file = f; q = new LinkedBlockingQueue<Item>(); indentation = 0; } public ParallelWriter append( CharSequence str ){ try { CharSeqItem item = new CharSeqItem(); item.content = str; item.type = ItemType.CHARSEQ; q.put(item); return this; } catch (InterruptedException ex) { throw new RuntimeException( ex ); } } public ParallelWriter newLine(){ try { Item item = new Item(); item.type = ItemType.NEWLINE; q.put(item); return this; } catch (InterruptedException ex) { throw new RuntimeException( ex ); } } public void setIndent(int indentation) { try{ IndentCommand item = new IndentCommand(); item.type = ItemType.INDENT; item.indent = indentation; q.put(item); } catch (InterruptedException ex) { throw new RuntimeException( ex ); } } public void end(){ try { Item item = new Item(); item.type = ItemType.POISON; q.put(item); } catch (InterruptedException ex) { throw new RuntimeException( ex ); } } public void run() { BufferedWriter out = null; Item item = null; try{ out = new BufferedWriter( new FileWriter( file ) ); while( (item = q.take()).type != ItemType.POISON ){ switch( item.type ){ case NEWLINE: out.newLine(); for( int i = 0; i < indentation; i++ ) out.append(" "); break; case INDENT: indentation = ((IndentCommand)item).indent; break; case CHARSEQ: out.append( ((CharSeqItem)item).content ); } } } catch (InterruptedException ex){ throw new RuntimeException( ex ); } catch (IOException ex) { throw new RuntimeException( ex ); } finally { if( out != null ) try { out.close(); } catch (IOException ex) { throw new RuntimeException( ex ); } } } private enum ItemType { CHARSEQ, NEWLINE, INDENT, POISON; } private static class Item { ItemType type; } private static class CharSeqItem extends Item { CharSequence content; } private static class IndentCommand extends Item { int indent; } } And then I use it by doing: ParallelWriter w = new ParallelWriter( myFile ); new Thread(w).start(); /// Lots of w.append(" things ").newLine(); w.setIndent(2); w.newLine().append(" more things "); /// and finally w.end(); While this works perfectly well, I'm wondering: Is there a better way to accomplish this?

  • Answer:

    Your basic approach looks fine. I would structure the code as follows: import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.Writer; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; public interface FileWriter { FileWriter append(CharSequence seq); FileWriter indent(int indent); void close(); } class AsyncFileWriter implements FileWriter, Runnable { private final File file; private final Writer out; private final BlockingQueue<Item> queue = new LinkedBlockingQueue<Item>(); private volatile boolean started = false; private volatile boolean stopped = false; public AsyncFileWriter(File file) throws IOException { this.file = file; this.out = new BufferedWriter(new java.io.FileWriter(file)); } public FileWriter append(CharSequence seq) { if (!started) { throw new IllegalStateException("open() call expected before append()"); } try { queue.put(new CharSeqItem(seq)); } catch (InterruptedException ignored) { } return this; } public FileWriter indent(int indent) { if (!started) { throw new IllegalStateException("open() call expected before append()"); } try { queue.put(new IndentItem(indent)); } catch (InterruptedException ignored) { } return this; } public void open() { this.started = true; new Thread(this).start(); } public void run() { while (!stopped) { try { Item item = queue.poll(100, TimeUnit.MICROSECONDS); if (item != null) { try { item.write(out); } catch (IOException logme) { } } } catch (InterruptedException e) { } } try { out.close(); } catch (IOException ignore) { } } public void close() { this.stopped = true; } private static interface Item { void write(Writer out) throws IOException; } private static class CharSeqItem implements Item { private final CharSequence sequence; public CharSeqItem(CharSequence sequence) { this.sequence = sequence; } public void write(Writer out) throws IOException { out.append(sequence); } } private static class IndentItem implements Item { private final int indent; public IndentItem(int indent) { this.indent = indent; } public void write(Writer out) throws IOException { for (int i = 0; i < indent; i++) { out.append(" "); } } } } If you do not want to write in a separate thread (maybe in a test?), you can have an implementation of FileWriter which calls append on the Writer in the caller thread.

trutheality at Stack Overflow Visit the source

Was this solution helpful to you?

Other answers

Using a LinkedBlockingQueue is a pretty good idea. Not sure I like some of the style of the code... but the principle seems sound. I would maybe add a capacity to the LinkedBlockingQueue equal to a certain % of your total memory.. say 10,000 items.. this way if your writing is going too slow, your worker threads won't keep adding more work until the heap is blown.

bwawok

One good way to exchange data with a single consumer thread is to use an Exchanger. You could use a StringBuilder or ByteBuffer as the buffer to exchange with the background thread. The latency incurred can be around 1 micro-second, doesn't involve creating any objects and which is lower using a BlockingQueue. From the example which I think is worth repeating here. class FillAndEmpty { Exchanger<DataBuffer> exchanger = new Exchanger<DataBuffer>(); DataBuffer initialEmptyBuffer = ... a made-up type DataBuffer initialFullBuffer = ... class FillingLoop implements Runnable { public void run() { DataBuffer currentBuffer = initialEmptyBuffer; try { while (currentBuffer != null) { addToBuffer(currentBuffer); if (currentBuffer.isFull()) currentBuffer = exchanger.exchange(currentBuffer); } } catch (InterruptedException ex) { ... handle ... } } } class EmptyingLoop implements Runnable { public void run() { DataBuffer currentBuffer = initialFullBuffer; try { while (currentBuffer != null) { takeFromBuffer(currentBuffer); if (currentBuffer.isEmpty()) currentBuffer = exchanger.exchange(currentBuffer); } } catch (InterruptedException ex) { ... handle ...} } } void start() { new Thread(new FillingLoop()).start(); new Thread(new EmptyingLoop()).start(); } }

Peter Lawrey

I know that frequent write operations can slow a program down a lot Probably not as much as you think, provided you use buffering.

EJP

Related Q & A:

Just Added Q & A:

Find solution

For every problem there is a solution! Proved by Solucija.

  • Got an issue and looking for advice?

  • Ask Solucija to search every corner of the Web for help.

  • Get workable solutions and helpful tips in a moment.

Just ask Solucija about an issue you face and immediately get a list of ready solutions, answers and tips from other Internet users. We always provide the most suitable and complete answer to your question at the top, along with a few good alternatives below.