Skip to content

Commit 59ffcad

Browse files
committed
[IO-332] Improve tailer's reading performance.
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/io/trunk@1345279 13f79535-47bb-0310-9956-ffa450edef68
1 parent bcba0cd commit 59ffcad

2 files changed

Lines changed: 69 additions & 33 deletions

File tree

src/changes/changes.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ The <action> type attribute can be add,update,fix,remove.
4747
<body>
4848
<!-- The release date is the date RC is cut -->
4949
<release version="2.4" date="2012-TDB-TDB" description="">
50+
<action issue="IO-332" dev="ggregory" type="fix" due-to="liangly">
51+
Improve tailer's reading performance.
52+
</action>
5053
<action issue="IO-329" dev="ggregory" type="fix" due-to="tivv">
5154
FileUtils.writeLines uses unbuffered IO.
5255
</action>

src/main/java/org/apache/commons/io/input/Tailer.java

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@
106106
*/
107107
public class Tailer implements Runnable {
108108

109+
private static final String RAF_MODE = "r";
110+
111+
private static final int DEFAULT_BUFSIZE = 4096;
112+
113+
/**
114+
* Buffer on top of RandomAccessFile.
115+
*/
116+
private byte inbuf[];
117+
109118
/**
110119
* The file which will be tailed.
111120
*/
@@ -158,33 +167,61 @@ public Tailer(File file, TailerListener listener, long delay) {
158167
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
159168
*/
160169
public Tailer(File file, TailerListener listener, long delay, boolean end) {
170+
this(file, listener, delay, end, DEFAULT_BUFSIZE);
171+
}
172+
173+
/**
174+
* Creates a Tailer for the given file, with a specified buffer size.
175+
* @param file the file to follow.
176+
* @param listener the TailerListener to use.
177+
* @param delay the delay between checks of the file for new content in milliseconds.
178+
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
179+
* @param bufSize Buffer size
180+
*/
181+
public Tailer(File file, TailerListener listener, long delay, boolean end, int bufSize) {
161182

162183
this.file = file;
163184
this.delay = delay;
164185
this.end = end;
186+
187+
this.inbuf = new byte[bufSize];
165188

166189
// Save and prepare the listener
167190
this.listener = listener;
168191
listener.init(this);
169192
}
170-
193+
171194
/**
172195
* Creates and starts a Tailer for the given file.
173196
*
174197
* @param file the file to follow.
175198
* @param listener the TailerListener to use.
176199
* @param delay the delay between checks of the file for new content in milliseconds.
177200
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
201+
* @param buffer size.
178202
* @return The new tailer
179203
*/
180-
public static Tailer create(File file, TailerListener listener, long delay, boolean end) {
181-
Tailer tailer = new Tailer(file, listener, delay, end);
204+
public static Tailer create(File file, TailerListener listener, long delay, boolean end, int bufSize) {
205+
Tailer tailer = new Tailer(file, listener, delay, end, bufSize);
182206
Thread thread = new Thread(tailer);
183207
thread.setDaemon(true);
184208
thread.start();
185209
return tailer;
186210
}
187211

212+
/**
213+
* Creates and starts a Tailer for the given file with default buffer size.
214+
*
215+
* @param file the file to follow.
216+
* @param listener the TailerListener to use.
217+
* @param delay the delay between checks of the file for new content in milliseconds.
218+
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
219+
* @return The new tailer
220+
*/
221+
public static Tailer create(File file, TailerListener listener, long delay, boolean end) {
222+
return create(file, listener, delay, end, DEFAULT_BUFSIZE);
223+
}
224+
188225
/**
189226
* Creates and starts a Tailer for the given file, starting at the beginning of the file
190227
*
@@ -238,7 +275,7 @@ public void run() {
238275
// Open the file
239276
while (run && reader == null) {
240277
try {
241-
reader = new RandomAccessFile(file, "r");
278+
reader = new RandomAccessFile(file, RAF_MODE);
242279
} catch (FileNotFoundException e) {
243280
listener.fileNotFound();
244281
}
@@ -252,11 +289,10 @@ public void run() {
252289
// The current position in the file
253290
position = end ? file.length() : 0;
254291
last = System.currentTimeMillis();
255-
reader.seek(position);
292+
reader.seek(position);
256293
}
257294
}
258295

259-
260296
while (run) {
261297

262298
// Check the file length to see if it was rotated
@@ -271,7 +307,7 @@ public void run() {
271307
try {
272308
// Ensure that the old file is closed iff we re-open it successfully
273309
RandomAccessFile save = reader;
274-
reader = new RandomAccessFile(file, "r");
310+
reader = new RandomAccessFile(file, RAF_MODE);
275311
position = 0;
276312
// close old file explicitly rather than relying on GC picking up previous RAF
277313
IOUtils.closeQuietly(save);
@@ -293,9 +329,9 @@ public void run() {
293329

294330
} else if (FileUtils.isFileNewer(file, last)) {
295331

296-
/* This can happen if the file is truncated or overwritten
297-
* with the exact same length of information. In cases like
298-
* this, the file position needs to be reset
332+
/*
333+
* This can happen if the file is truncated or overwritten with the exact same length of
334+
* information. In cases like this, the file position needs to be reset
299335
*/
300336
position = 0;
301337
reader.seek(position); // cannot be null here
@@ -335,31 +371,22 @@ public void stop() {
335371
* @throws java.io.IOException if an I/O error occurs.
336372
*/
337373
private long readLines(RandomAccessFile reader) throws IOException {
374+
StringBuilder sb = new StringBuilder();
375+
338376
long pos = reader.getFilePointer();
339-
String line = readLine(reader);
340-
while (line != null) {
341-
pos = reader.getFilePointer();
342-
listener.handle(line);
343-
line = readLine(reader);
344-
}
345-
reader.seek(pos); // Ensure we can re-read if necessary
346-
return pos;
347-
}
377+
long rePos = pos; // position to re-read
348378

349-
/**
350-
* Version of readline() that returns null on EOF rather than a partial line.
351-
* @param reader the input file
352-
* @return the line, or null if EOF reached before '\n' is seen.
353-
* @throws IOException if an error occurs.
354-
*/
355-
private String readLine(RandomAccessFile reader) throws IOException {
356-
StringBuffer sb = new StringBuffer();
357-
int ch;
379+
int num;
358380
boolean seenCR = false;
359-
while((ch=reader.read()) != -1) {
360-
switch(ch) {
381+
while (run && ((num = reader.read(inbuf)) != -1)) {
382+
for (int i = 0; i < num; i++) {
383+
byte ch = inbuf[i];
384+
switch (ch) {
361385
case '\n':
362-
return sb.toString();
386+
listener.handle(sb.toString());
387+
sb = new StringBuilder();
388+
rePos = pos + i + 1;
389+
break;
363390
case '\r':
364391
seenCR = true;
365392
break;
@@ -368,9 +395,15 @@ private String readLine(RandomAccessFile reader) throws IOException {
368395
sb.append('\r');
369396
seenCR = false;
370397
}
371-
sb.append((char)ch); // add character, not its ascii value
398+
sb.append((char) ch); // add character, not its ascii value
399+
}
372400
}
401+
402+
pos = reader.getFilePointer();
373403
}
374-
return null;
404+
405+
reader.seek(rePos); // Ensure we can re-read if necessary
406+
return rePos;
375407
}
408+
376409
}

0 commit comments

Comments
 (0)