Skip to content

Instantly share code, notes, and snippets.

@teleclient
Forked from agarciadom/Tail.java
Created February 18, 2021 14:00
Show Gist options
  • Save teleclient/72a781d6295cdbdd16238d57fe2e662c to your computer and use it in GitHub Desktop.
Save teleclient/72a781d6295cdbdd16238d57fe2e662c to your computer and use it in GitHub Desktop.
Implementation of "tail -n1" in Java, using RandomAccessFile and a byte[] buffer
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();
}
}
}
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