We often want to read the last n lines of a file without incurring the overhead of reading through the whole file to get to them. Using a RandomAccessFile is useful in that we can seek to the end of the file and work backwards. Below is such an approach. The resulting string(s) will be decoded in the platform default encoding. This can be changed by passing a charset parameter to the String constructor.

The source code can be downloaded HERE.

 

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.ByteArrayOutputStream;

public class TailN {
    public static void main(String[] args) throws Exception {
        TailN tailN = new TailN();
        File file = new File(args[0]);
        System.out.println(tailN.readFromLast(file, Integer.parseInt(args[1])));
    }

    public String readFromLast(File file, int howMany) throws IOException {
        int numLinesRead = 0;
        StringBuilder builder = new StringBuilder();
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
            try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
                long fileLength = file.length() - 1;
                /*
                 * Set the pointer at the end of the file. If the file is empty, an IOException
                 * will be thrown
                 */
                randomAccessFile.seek(fileLength);

                for (long pointer = fileLength; pointer >= 0; pointer--) {
                    randomAccessFile.seek(pointer);
                    byte b = (byte) randomAccessFile.read();
                    if (b == '\n') {
                        numLinesRead++;
                        // (Last line often terminated with a line separator)
                        if (numLinesRead == (howMany + 1))
                            break;
                    }
                    baos.write(b);
                    fileLength = fileLength - pointer;
                }
                /*
                 * Since line is read from the last so it is in reverse order. Use reverse
                 * method to make it ordered correctly
                 */
                byte[] a = baos.toByteArray();
                int start = 0;
                int mid = a.length / 2;
                int end = a.length - 1;

                while (start < mid) {
                    byte temp = a[end];
                    a[end] = a[start];
                    a[start] = temp;
                    start++;
                    end--;
                } // End while
                return new String(a).trim();
            } // End inner try-with-resources
        } // End outer try-with-resources

    } // End method
}