-
-
Save teleclient/72a781d6295cdbdd16238d57fe2e662c to your computer and use it in GitHub Desktop.
Implementation of "tail -n1" in Java, using RandomAccessFile and a byte[] buffer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.BufferedReader; | |
import java.io.ByteArrayInputStream; | |
import java.io.File; | |
import java.io.IOException; | |
import java.io.InputStreamReader; | |
import java.io.RandomAccessFile; | |
/** | |
* Simple partial implementations of the <code>tail(1)</code> UNIX command. | |
* | |
* @author Antonio García-Domínguez | |
*/ | |
public final class Tail { | |
/** Size of the buffer used by the functions in this class. */ | |
private final static int BUFSIZE = 1024; | |
private Tail() { | |
// do nothing | |
} | |
/** | |
* Reads the last line of a file, decoding it using the default platform encoding. | |
* Line terminator characters are stripped. | |
*/ | |
public static String lastLine(File f) throws IOException { | |
final byte[] buffer = new byte[BUFSIZE]; | |
final RandomAccessFile rf = new RandomAccessFile(f, "r"); | |
final long fSize = rf.length(); | |
try { | |
long start = Math.max(0, fSize - BUFSIZE); | |
long end = fSize; | |
String line = ""; | |
int firstNewline; | |
// We need to loop, in case the last line doesn't fit entirely in the buffer | |
do { | |
rf.seek(start); | |
final int readBytes = rf.read(buffer, 0, (int)(end - start)); | |
if (readBytes <= 0) return line; | |
// Go backwards, looking for the first CR or LF | |
for ( | |
firstNewline = readBytes - 1; | |
firstNewline >= 0 && buffer[firstNewline] != '\n' && buffer[firstNewline] != '\r'; | |
--firstNewline | |
); | |
// Is there anything to read past the first CR/LF? | |
if (firstNewline < readBytes - 1) { | |
final BufferedReader sR = new BufferedReader( | |
new InputStreamReader( | |
new ByteArrayInputStream(buffer, firstNewline + 1, readBytes - firstNewline - 1))); | |
line = sR.readLine() + line; | |
} | |
if (firstNewline < 0) { | |
if (start > 0) { | |
// We couldn't find a CR/LF in the buffer: keep going back | |
end = start; | |
start = Math.max(0, start - BUFSIZE); | |
} | |
else { | |
// There wasn't a single CR/LF in the file | |
return line; | |
} | |
} | |
} while (firstNewline < 0); | |
return line; | |
} finally { | |
rf.close(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import static org.junit.Assert.assertEquals; | |
import java.io.File; | |
import java.io.FileWriter; | |
import java.io.IOException; | |
import org.junit.Test; | |
/** | |
* Tests for the Java <code>tail(1)</code> ports. | |
* | |
* @author Antonio García Domínguez | |
*/ | |
public class TailTest { | |
@Test | |
public void emptyFile() throws Exception { | |
assertEquals("", computeLastLine("")); | |
} | |
@Test | |
public void noLineBreaks() throws Exception { | |
assertEquals("x", computeLastLine("x")); | |
} | |
@Test | |
public void exactlyAtLF() throws Exception { | |
assertEquals("", computeLastLine("x\n")); | |
} | |
@Test | |
public void exactlyAtCR() throws Exception { | |
assertEquals("", computeLastLine("x\r")); | |
} | |
@Test | |
public void exactlyAtCRLF() throws Exception { | |
assertEquals("", computeLastLine("x\r\n")); | |
} | |
@Test | |
public void twoLines() throws Exception { | |
assertEquals("y", computeLastLine("x\ny")); | |
} | |
@Test | |
public void twoLinesAtLF() throws Exception { | |
assertEquals("", computeLastLine("x\ny\n")); | |
} | |
@Test | |
public void threeLines() throws Exception { | |
assertEquals("z", computeLastLine("x\ry\rz")); | |
} | |
@Test | |
public void largeFile() throws Exception { | |
final String line = "I am the very, very, very long line #"; | |
final StringBuilder sb = new StringBuilder(); | |
for (int i = 0; i < 10000; ++i) { | |
if (i > 0) sb.append("\n"); | |
sb.append(line + i); | |
} | |
assertEquals("I am the very, very, very long line #9999", computeLastLine(sb.toString())); | |
} | |
@Test | |
public void largeFileOneLine() throws Exception { | |
final StringBuilder sb = new StringBuilder(); | |
for (int i = 0; i < 10000; ++i) { | |
sb.append(" banana"); | |
} | |
final String bananas = sb.toString(); | |
assertEquals(bananas, computeLastLine(bananas)); | |
} | |
private String computeLastLine(String text) throws IOException { | |
final File tmp = File.createTempFile("tail", ".txt"); | |
final FileWriter fW = new FileWriter(tmp); | |
fW.write(text); | |
fW.close(); | |
final String lastLine = Tail.lastLine(tmp); | |
tmp.delete(); | |
return lastLine; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment