Author: ggregory Date: Fri Jun 1 17:27:21 2012 New Revision: 1345279 URL: http://svn.apache.org/viewvc?rev=1345279&view=rev Log: [IO-332] Improve tailer's reading performance.
Modified: commons/proper/io/trunk/src/changes/changes.xml commons/proper/io/trunk/src/main/java/org/apache/commons/io/input/Tailer.java Modified: commons/proper/io/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/io/trunk/src/changes/changes.xml?rev=1345279&r1=1345278&r2=1345279&view=diff ============================================================================== --- commons/proper/io/trunk/src/changes/changes.xml (original) +++ commons/proper/io/trunk/src/changes/changes.xml Fri Jun 1 17:27:21 2012 @@ -47,6 +47,9 @@ The <action> type attribute can be add,u <body> <!-- The release date is the date RC is cut --> <release version="2.4" date="2012-TDB-TDB" description=""> + <action issue="IO-332" dev="ggregory" type="fix" due-to="liangly"> + Improve tailer's reading performance. + </action> <action issue="IO-329" dev="ggregory" type="fix" due-to="tivv"> FileUtils.writeLines uses unbuffered IO. </action> Modified: commons/proper/io/trunk/src/main/java/org/apache/commons/io/input/Tailer.java URL: http://svn.apache.org/viewvc/commons/proper/io/trunk/src/main/java/org/apache/commons/io/input/Tailer.java?rev=1345279&r1=1345278&r2=1345279&view=diff ============================================================================== --- commons/proper/io/trunk/src/main/java/org/apache/commons/io/input/Tailer.java (original) +++ commons/proper/io/trunk/src/main/java/org/apache/commons/io/input/Tailer.java Fri Jun 1 17:27:21 2012 @@ -106,6 +106,15 @@ import org.apache.commons.io.IOUtils; */ public class Tailer implements Runnable { + private static final String RAF_MODE = "r"; + + private static final int DEFAULT_BUFSIZE = 4096; + + /** + * Buffer on top of RandomAccessFile. + */ + private byte inbuf[]; + /** * The file which will be tailed. */ @@ -158,16 +167,30 @@ public class Tailer implements Runnable * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. */ public Tailer(File file, TailerListener listener, long delay, boolean end) { + this(file, listener, delay, end, DEFAULT_BUFSIZE); + } + + /** + * Creates a Tailer for the given file, with a specified buffer size. + * @param file the file to follow. + * @param listener the TailerListener to use. + * @param delay the delay between checks of the file for new content in milliseconds. + * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. + * @param bufSize Buffer size + */ + public Tailer(File file, TailerListener listener, long delay, boolean end, int bufSize) { this.file = file; this.delay = delay; this.end = end; + + this.inbuf = new byte[bufSize]; // Save and prepare the listener this.listener = listener; listener.init(this); } - + /** * Creates and starts a Tailer for the given file. * @@ -175,10 +198,11 @@ public class Tailer implements Runnable * @param listener the TailerListener to use. * @param delay the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. + * @param buffer size. * @return The new tailer */ - public static Tailer create(File file, TailerListener listener, long delay, boolean end) { - Tailer tailer = new Tailer(file, listener, delay, end); + public static Tailer create(File file, TailerListener listener, long delay, boolean end, int bufSize) { + Tailer tailer = new Tailer(file, listener, delay, end, bufSize); Thread thread = new Thread(tailer); thread.setDaemon(true); thread.start(); @@ -186,6 +210,19 @@ public class Tailer implements Runnable } /** + * Creates and starts a Tailer for the given file with default buffer size. + * + * @param file the file to follow. + * @param listener the TailerListener to use. + * @param delay the delay between checks of the file for new content in milliseconds. + * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. + * @return The new tailer + */ + public static Tailer create(File file, TailerListener listener, long delay, boolean end) { + return create(file, listener, delay, end, DEFAULT_BUFSIZE); + } + + /** * Creates and starts a Tailer for the given file, starting at the beginning of the file * * @param file the file to follow. @@ -238,7 +275,7 @@ public class Tailer implements Runnable // Open the file while (run && reader == null) { try { - reader = new RandomAccessFile(file, "r"); + reader = new RandomAccessFile(file, RAF_MODE); } catch (FileNotFoundException e) { listener.fileNotFound(); } @@ -252,11 +289,10 @@ public class Tailer implements Runnable // The current position in the file position = end ? file.length() : 0; last = System.currentTimeMillis(); - reader.seek(position); + reader.seek(position); } } - while (run) { // Check the file length to see if it was rotated @@ -271,7 +307,7 @@ public class Tailer implements Runnable try { // Ensure that the old file is closed iff we re-open it successfully RandomAccessFile save = reader; - reader = new RandomAccessFile(file, "r"); + reader = new RandomAccessFile(file, RAF_MODE); position = 0; // close old file explicitly rather than relying on GC picking up previous RAF IOUtils.closeQuietly(save); @@ -293,9 +329,9 @@ public class Tailer implements Runnable } else if (FileUtils.isFileNewer(file, last)) { - /* This can happen if the file is truncated or overwritten - * with the exact same length of information. In cases like - * this, the file position needs to be reset + /* + * This can happen if the file is truncated or overwritten with the exact same length of + * information. In cases like this, the file position needs to be reset */ position = 0; reader.seek(position); // cannot be null here @@ -335,31 +371,22 @@ public class Tailer implements Runnable * @throws java.io.IOException if an I/O error occurs. */ private long readLines(RandomAccessFile reader) throws IOException { + StringBuilder sb = new StringBuilder(); + long pos = reader.getFilePointer(); - String line = readLine(reader); - while (line != null) { - pos = reader.getFilePointer(); - listener.handle(line); - line = readLine(reader); - } - reader.seek(pos); // Ensure we can re-read if necessary - return pos; - } + long rePos = pos; // position to re-read - /** - * Version of readline() that returns null on EOF rather than a partial line. - * @param reader the input file - * @return the line, or null if EOF reached before '\n' is seen. - * @throws IOException if an error occurs. - */ - private String readLine(RandomAccessFile reader) throws IOException { - StringBuffer sb = new StringBuffer(); - int ch; + int num; boolean seenCR = false; - while((ch=reader.read()) != -1) { - switch(ch) { + while (run && ((num = reader.read(inbuf)) != -1)) { + for (int i = 0; i < num; i++) { + byte ch = inbuf[i]; + switch (ch) { case '\n': - return sb.toString(); + listener.handle(sb.toString()); + sb = new StringBuilder(); + rePos = pos + i + 1; + break; case '\r': seenCR = true; break; @@ -368,9 +395,15 @@ public class Tailer implements Runnable sb.append('\r'); seenCR = false; } - sb.append((char)ch); // add character, not its ascii value + sb.append((char) ch); // add character, not its ascii value + } } + + pos = reader.getFilePointer(); } - return null; + + reader.seek(rePos); // Ensure we can re-read if necessary + return rePos; } + }