1

When I want to write Java code for writing text to a file, it usually looks something like this:

File logFile = new File("/home/someUser/app.log");
FileWriter writer;

try {
    writer = new FileWriter(logFile, true);

    writer.write("Some text.");

    writer.close();
} catch (IOException e) {
    e.printStackTrace();
}

But what if I'm doing a ton of write operations back-to-back? What if I'm logging dozens, hundred, even thousands of write operations per second?

In the same way that databases (annd JDBC) allow "persistent connections" (connections that remain open across multiple calls), is there a way to have "persistent streams" that do not need to be opened/closed across multiple write(String) invocations? If so, how would this work? What pitfalls/caveats would I have to be aware of? Thanks in advance!

  • 1
    BTW in your example, if `write` throws an exception, the writer is not closed right after the `write`. – Beryllium Jul 08 '13 at 19:12

2 Answers2

1

If you look at this implementation

class Logger {
    private final BufferedWriter w;

    public Logger(final File file) throws IOException {
        this.w = new BufferedWriter(new FileWriter(file));
        LoggerRegistry.register(this);
    }

    public void log(String s) throws IOException {
        synchronized (this.w) {
            this.w.write(s);
            this.w.write("\n");
        }
    }

    public void close() throws IOException {
        this.w.close();
    }
}

the file is kept open.

If you have multiple threads, you have to synchronize in the write method (but this must be considered in any case).

There are possibly these problems, if the file remains open:

  • In theory, you could run out of file handles. These might be limited (see ulimit -a on Linux systems for example): Each logger consumes one handle.

  • If you use a FileWriter without buffering, you have I/O calls for each invocation of write. This could be quite slow.

  • If you use a BufferedWriter on top of a FileWriter, you have to make sure that it gets closed properly at the end of your program, otherwise the remaining content in the buffer might not be written to the disk. So you need a try/finally block around your program which must close all loggers correctly.

Therefore you need to register all loggers. This is a simplified version (it is not thread-safe):

class LoggerRegistry {
    private final static List<Logger> loggers = new ArrayList<Logger>();

    public static void register(Logger l) {
        loggers.add(l);
    }

    public static void close() {
        for (Logger l : loggers) {
            try {
                l.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

and use this in your main program like this:

public static void main(String[] args) throws IOException {
    try {
        final Logger l = new Logger(new File("/tmp/1"));
        l.log("Hello");

        // ... 

    } finally {
        LoggerRegistry.close();
    }
}

If you have a web application, you could call the close method in a ServletContextListener (method contextDestroyed).

The biggest performance gain is probably the BufferedWriter. This advantage gets lost, if you open/close it for each write operation, since close has to call flush. So the combination of an open file together with buffering will be quite fast.

Beryllium
  • 12,808
  • 10
  • 56
  • 86
0

I am not sure how the logger frameworks manage the file connections, but one way I can suggest you is to cache the fileWriter in a static instance, without closing it. In this way, your program would hold live reference to the file and will not create new connection to write again.

One disadvantage of this approach, is that file will be held by FileWriter even if there are no write activities. This means that, the FileWriter will lock the file and while its locked, no other process can write on the file.

sanbhat
  • 17,522
  • 6
  • 48
  • 64
  • Thanks @sanbhat (+1) - when you say "*One disadvantage to this approach is that file will be held by FileWriter even if there are no write activities.*", what does this imply? Does it mean that the file is "locked"? Would multiple logging threads not be able to write to the file? What are the limitations this strategy would impose? **Most importantly** would this yield any performance increase? If not, what's the point of doing this?!? Thanks again! –  Jul 08 '13 at 18:35
  • Ahh, very nice - thanks again @sanbhat. Sorry to be picky here, but 2 things: (1) by "no other **process** can write on the log file", do you mean: other threads, other JVMs or other system-level processes, (or all 3)? For instance, could, say, a Perl script write to the same log file while it is "locked" by the Java `FileWriter`? And (2) can you point me to any Oracle/Java documentation that supports your "file lock" statements? Not that I don't believe you (I do!), but I need to go to my boss with a proof of concept and will need to cite refs. Thanks again! –  Jul 08 '13 at 18:46
  • When the file is locked by `FileWriter`, no other process (another JVM or perl) or even no other thread within same JVM can write to that file. See http://docs.oracle.com/javase/6/docs/api/java/io/RandomAccessFile.html and http://docs.oracle.com/javase/6/docs/api/java/nio/channels/FileChannel.html for more info – sanbhat Jul 08 '13 at 18:50
  • Last followup @sanbhat: would such a "persistent stream" even be worth it?!? Can you foresee performance increases? Are there scenarios where it would be more/less beneficial to use this? –  Jul 08 '13 at 18:51
  • Good thing will be to do a benchmark test. 1 by having some persistent connection, and 2 by closing streams after each write, and this test can conclude how much more performance, the persistent stream provide. – sanbhat Jul 08 '13 at 18:54
  • An open `FileWriter` does *not* lock on all operating systems! The locks which are mentioned in the comment are not related to a `FileWriter`, these are different file operations. – Beryllium Jul 08 '13 at 19:10