28

I want to read file in opposite direction from end to the start my file,

[1322110800] LOG ROTATION: DAILY
[1322110800] LOG VERSION: 2.0
[1322110800] CURRENT HOST STATE:arsalan.hussain;DOWN;HARD;1;CRITICAL - Host Unreachable (192.168.1.107)
[1322110800] CURRENT HOST STATE: localhost;UP;HARD;1;PING OK - Packet loss = 0%, RTA = 0.06 ms
[1322110800] CURRENT HOST STATE: musewerx-72c7b0;UP;HARD;1;PING OK - Packet loss = 0%, RTA = 0.27 ms

i use code to read it in this way,

String strpath="/var/nagios.log";
FileReader fr = new FileReader(strpath);
BufferedReader br = new BufferedReader(fr);
String ch;
int time=0;
String Conversion="";
do {
    ch = br.readLine();
    out.print(ch+"<br/>"); 
} while (ch != null);
fr.close();

I would prefer to read in reverse order using buffer reader

Blorgbeard
  • 101,031
  • 48
  • 228
  • 272
Salman Raza
  • 1,635
  • 6
  • 18
  • 18
  • What is the purpose of reading file from end to start? – KV Prajapati Dec 29 '11 at 06:41
  • 1
    Same type question is answered http://stackoverflow.com/questions/6011345/read-a-file-line-by-line-in-reverse-order Check out. – Zlatan Dec 29 '11 at 06:48
  • can't you just read the file and use the reverse() method in StringBuilder? – asgs Dec 29 '11 at 06:48
  • How Can we use the reverse order method??? – Salman Raza Dec 29 '11 at 06:55
  • i have seen that post but the solution is very Big need urgent and smaal solution that would be implemented – Salman Raza Dec 29 '11 at 06:56
  • @SalmanRaza - why do you need a small solution? Why can't you implement a solution yourself based on the answers below and in the linked question? Please bear in mind that SO is not a "write my code for me" service. – Stephen C Dec 29 '11 at 07:43
  • 1
    See also: [Java: Quickly read the last line of a text file?](http://stackoverflow.com/questions/686231), [Java : Read last n lines of a HUGE file](http://stackoverflow.com/questions/4121678), [How to read a file from end to the beginning?](http://stackoverflow.com/questions/8654257) – hippietrail Nov 05 '12 at 18:23

7 Answers7

64

I had the same problem as described here. I want to look at lines in file in reverse order, from the end back to the start (The unix tac command will do it).

However my input files are fairly large so reading the whole file into memory, as in the other examples was not really a workable option for me.

Below is the class I came up with, it does use RandomAccessFile, but does not need any buffers, since it just retains pointers to the file itself, and works with the standard InputStream methods.

It works for my cases, and empty files and a few other things I've tried. Now I don't have Unicode characters or anything fancy, but as long as the lines are delimited by LF, and even if they have a LF + CR it should work.

Basic Usage is :

in = new BufferedReader (new InputStreamReader (new ReverseLineInputStream(file)));

while(true) {
    String line = in.readLine();
    if (line == null) {
        break;
    }
    System.out.println("X:" + line);
}

Here is the main source:

package www.kosoft.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;

public class ReverseLineInputStream extends InputStream {

    RandomAccessFile in;

    long currentLineStart = -1;
    long currentLineEnd = -1;
    long currentPos = -1;
    long lastPosInFile = -1;

    public ReverseLineInputStream(File file) throws FileNotFoundException {
        in = new RandomAccessFile(file, "r");
        currentLineStart = file.length();
        currentLineEnd = file.length();
        lastPosInFile = file.length() -1;
        currentPos = currentLineEnd; 
    }

    public void findPrevLine() throws IOException {

        currentLineEnd = currentLineStart; 

        // There are no more lines, since we are at the beginning of the file and no lines.
        if (currentLineEnd == 0) {
            currentLineEnd = -1;
            currentLineStart = -1;
            currentPos = -1;
            return; 
        }

        long filePointer = currentLineStart -1;

         while ( true) {
             filePointer--;

            // we are at start of file so this is the first line in the file.
            if (filePointer < 0) {  
                break; 
            }

            in.seek(filePointer);
            int readByte = in.readByte();

            // We ignore last LF in file. search back to find the previous LF.
            if (readByte == 0xA && filePointer != lastPosInFile ) {   
                break;
            }
         }
         // we want to start at pointer +1 so we are after the LF we found or at 0 the start of the file.   
         currentLineStart = filePointer + 1;
         currentPos = currentLineStart;
    }

    public int read() throws IOException {

        if (currentPos < currentLineEnd ) {
            in.seek(currentPos++);
            int readByte = in.readByte();
            return readByte;

        }
        else if (currentPos < 0) {
            return -1;
        }
        else {
            findPrevLine();
            return read();
        }
    }
}
forsvarir
  • 10,749
  • 6
  • 46
  • 77
Mark O'Donohue
  • 681
  • 5
  • 4
  • 3
    While it is admirable that you coded this yourself, you want *some* form of buffering; otherwise, the performance of reading bytes one at a time will be poor. http://stackoverflow.com/a/31961274/14731 is probably a better way to go. I also hand-coded a solution which I am now forced to throw out :) – Gili Mar 21 '16 at 13:21
  • @Gili where is that solution? – steveen zoleko Sep 21 '17 at 14:30
  • @steveenzoleko Gone. I deleted the code because its performance was too poor. You can use the above solution as an alternative if performance is not an issue. – Gili Sep 22 '17 at 21:18
30

Apache Commons IO has the ReversedLinesFileReader class for this now (well, since version 2.2).

So your code could be:

String strpath="/var/nagios.log";
ReversedLinesFileReader fr = new ReversedLinesFileReader(new File(strpath));
String ch;
int time=0;
String Conversion="";
do {
    ch = fr.readLine();
    out.print(ch+"<br/>"); 
} while (ch != null);
fr.close();
Thomas Decaux
  • 21,738
  • 2
  • 113
  • 124
nessa.gp
  • 1,804
  • 21
  • 20
12

The ReverseLineInputStream posted above is exactly what I was looking for. The files I am reading are large and cannot be buffered.

There are a couple of bugs:

  • File is not closed
  • if the last line is not terminated the last 2 lines are returned on the first read.

Here is the corrected code:

package www.kosoft.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;

public class ReverseLineInputStream extends InputStream {

    RandomAccessFile in;

    long currentLineStart = -1;
    long currentLineEnd = -1;
    long currentPos = -1;
    long lastPosInFile = -1;
    int lastChar = -1;


    public ReverseLineInputStream(File file) throws FileNotFoundException {
        in = new RandomAccessFile(file, "r");
        currentLineStart = file.length();
        currentLineEnd = file.length();
        lastPosInFile = file.length() -1;
        currentPos = currentLineEnd; 

    }

    private void findPrevLine() throws IOException {
        if (lastChar == -1) {
            in.seek(lastPosInFile);
            lastChar = in.readByte();
        }

        currentLineEnd = currentLineStart; 

        // There are no more lines, since we are at the beginning of the file and no lines.
        if (currentLineEnd == 0) {
            currentLineEnd = -1;
            currentLineStart = -1;
            currentPos = -1;
            return; 
        }

        long filePointer = currentLineStart -1;

        while ( true) {
            filePointer--;

            // we are at start of file so this is the first line in the file.
            if (filePointer < 0) {  
                break; 
            }

            in.seek(filePointer);
            int readByte = in.readByte();

            // We ignore last LF in file. search back to find the previous LF.
            if (readByte == 0xA && filePointer != lastPosInFile ) {   
                break;
            }
        }
        // we want to start at pointer +1 so we are after the LF we found or at 0 the start of the file.   
        currentLineStart = filePointer + 1;
        currentPos = currentLineStart;
    }

    public int read() throws IOException {

        if (currentPos < currentLineEnd ) {
            in.seek(currentPos++);
            int readByte = in.readByte();            
            return readByte;
        } else if (currentPos > lastPosInFile && currentLineStart < currentLineEnd) {
            // last line in file (first returned)
            findPrevLine();
            if (lastChar != '\n' && lastChar != '\r') {
                // last line is not terminated
                return '\n';
            } else {
                return read();
            }
        } else if (currentPos < 0) {
            return -1;
        } else {
            findPrevLine();
            return read();
        }
    }

    @Override
    public void close() throws IOException {
        if (in != null) {
            in.close();
            in = null;
        }
    }
}
Tim Lavallee
  • 121
  • 1
  • 4
12

The proposed ReverseLineInputStream works really slow when you try to read thousands of lines. At my PC Intel Core i7 on SSD drive it was about 60k lines in 80 seconds. Here is the inspired optimized version with buffered reading (opposed to one-byte-at-a-time reading in ReverseLineInputStream). 60k lines log file is read in 400 milliseconds:

public class FastReverseLineInputStream extends InputStream {

private static final int MAX_LINE_BYTES = 1024 * 1024;

private static final int DEFAULT_BUFFER_SIZE = 1024 * 1024;

private RandomAccessFile in;

private long currentFilePos;

private int bufferSize;
private byte[] buffer;
private int currentBufferPos;

private int maxLineBytes;
private byte[] currentLine;
private int currentLineWritePos = 0;
private int currentLineReadPos = 0;
private boolean lineBuffered = false;

public ReverseLineInputStream(File file) throws IOException {
    this(file, DEFAULT_BUFFER_SIZE, MAX_LINE_BYTES);
}

public ReverseLineInputStream(File file, int bufferSize, int maxLineBytes) throws IOException {
    this.maxLineBytes = maxLineBytes;
    in = new RandomAccessFile(file, "r");
    currentFilePos = file.length() - 1;
    in.seek(currentFilePos);
    if (in.readByte() == 0xA) {
        currentFilePos--;
    }
    currentLine = new byte[maxLineBytes];
    currentLine[0] = 0xA;

    this.bufferSize = bufferSize;
    buffer = new byte[bufferSize];
    fillBuffer();
    fillLineBuffer();
}

@Override
public int read() throws IOException {
    if (currentFilePos <= 0 && currentBufferPos < 0 && currentLineReadPos < 0) {
        return -1;
    }

    if (!lineBuffered) {
        fillLineBuffer();
    }


    if (lineBuffered) {
        if (currentLineReadPos == 0) {
            lineBuffered = false;
        }
        return currentLine[currentLineReadPos--];
    }
    return 0;
}

private void fillBuffer() throws IOException {
    if (currentFilePos < 0) {
        return;
    }

    if (currentFilePos < bufferSize) {
        in.seek(0);
        in.read(buffer);
        currentBufferPos = (int) currentFilePos;
        currentFilePos = -1;
    } else {
        in.seek(currentFilePos);
        in.read(buffer);
        currentBufferPos = bufferSize - 1;
        currentFilePos = currentFilePos - bufferSize;
    }
}

private void fillLineBuffer() throws IOException {
    currentLineWritePos = 1;
    while (true) {

        // we've read all the buffer - need to fill it again
        if (currentBufferPos < 0) {
            fillBuffer();

            // nothing was buffered - we reached the beginning of a file
            if (currentBufferPos < 0) {
                currentLineReadPos = currentLineWritePos - 1;
                lineBuffered = true;
                return;
            }
        }

        byte b = buffer[currentBufferPos--];

        // \n is found - line fully buffered
        if (b == 0xA) {
            currentLineReadPos = currentLineWritePos - 1;
            lineBuffered = true;
            break;

            // just ignore \r for now
        } else if (b == 0xD) {
            continue;
        } else {
            if (currentLineWritePos == maxLineBytes) {
                throw new IOException("file has a line exceeding " + maxLineBytes
                        + " bytes; use constructor to pickup bigger line buffer");
            }

            // write the current line bytes in reverse order - reading from
            // the end will produce the correct line
            currentLine[currentLineWritePos++] = b;
        }
    }
}}
dpetruha
  • 1,214
  • 12
  • 12
3
@Test
public void readAndPrintInReverseOrder() throws IOException {

    String path = "src/misctests/test.txt";

    BufferedReader br = null;

    try {
        br = new BufferedReader(new FileReader(path));
        Stack<String> lines = new Stack<String>();
        String line = br.readLine();
        while(line != null) {
            lines.push(line);
            line = br.readLine();
        }

        while(! lines.empty()) {
            System.out.println(lines.pop());
        }

    } finally {
        if(br != null) {
            try {
                br.close();   
            } catch(IOException e) {
                // can't help it
            }
        }
    }
}

Note that this code reads the hole file into memory and then starts printing it. This is the only way you can do it with a buffered reader or anry other reader that does not support seeking. You have to keep this in mind, in your case you want to read a log file, log files can be very big!

If you want to read line by line and print on the fly then you have no other alternative than using a reader that support seeking such as java.io.RandomAccessFile and this anything but trivial.

A4L
  • 17,353
  • 6
  • 49
  • 70
2

As far as I understand, you try to read backwards line by line. Suppose this is the file you try to read:

line1
line2
line3

And you want to write it to the output stream of the servlet as follows:

line3
line2
line1

Following code might be helpful in this case:

    List<String> tmp = new ArrayList<String>();

    do {
        ch = br.readLine();
        tmp.add(ch);
        out.print(ch+"<br/>"); 
    } while (ch != null);

    for(int i=tmp.size()-1;i>=0;i--) {
        out.print(tmp.get(i)+"<br/>");
    }
anilsinaci
  • 386
  • 1
  • 4
1

I had a problem with your solution @dpetruha because of this:

Does RandomAccessFile.read() from local file guarantee that exact number of bytes will be read?

Here is my solution: (changed only fillBuffer)

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

public class ReverseLineInputStream extends InputStream {

    private static final int MAX_LINE_BYTES = 1024 * 1024;
    private static final int DEFAULT_BUFFER_SIZE = 1024 * 1024;

    private RandomAccessFile in;
    private long currentFilePos;
    private int bufferSize;
    private byte[] buffer;
    private int currentBufferPos;
    private int maxLineBytes;
    private byte[] currentLine;
    private int currentLineWritePos = 0;
    private int currentLineReadPos = 0;
    private boolean lineBuffered = false;

    public ReverseLineInputStream(File file) throws IOException {
        this(file, DEFAULT_BUFFER_SIZE, MAX_LINE_BYTES);
    }

    public ReverseLineInputStream(File file, int bufferSize, int maxLineBytes) throws IOException {
        this.maxLineBytes = maxLineBytes;
        in = new RandomAccessFile(file, "r");
        currentFilePos = file.length() - 1;
        in.seek(currentFilePos);
        if (in.readByte() == 0xA) {
            currentFilePos--;
        }
        currentLine = new byte[maxLineBytes];
        currentLine[0] = 0xA;

        this.bufferSize = bufferSize;
        buffer = new byte[bufferSize];
        fillBuffer();
        fillLineBuffer();
    }

    @Override
    public int read() throws IOException {
        if (currentFilePos <= 0 && currentBufferPos < 0 && currentLineReadPos < 0) {
            return -1;
        }

        if (!lineBuffered) {
            fillLineBuffer();
        }

        if (lineBuffered) {
            if (currentLineReadPos == 0) {
                lineBuffered = false;
            }
            return currentLine[currentLineReadPos--];
        }
        return 0;
    }

    private void fillBuffer() throws IOException {
        if (currentFilePos < 0) {
            return;
        }

        if (currentFilePos < bufferSize) {
            in.seek(0);
            buffer = new byte[(int) currentFilePos + 1];
            in.readFully(buffer);
            currentBufferPos = (int) currentFilePos;
            currentFilePos = -1;
        } else {
            in.seek(currentFilePos - buffer.length);
            in.readFully(buffer);
            currentBufferPos = bufferSize - 1;
            currentFilePos = currentFilePos - bufferSize;
        }
    }

    private void fillLineBuffer() throws IOException {
        currentLineWritePos = 1;
        while (true) {

            // we've read all the buffer - need to fill it again
            if (currentBufferPos < 0) {
                fillBuffer();

                // nothing was buffered - we reached the beginning of a file
                if (currentBufferPos < 0) {
                    currentLineReadPos = currentLineWritePos - 1;
                    lineBuffered = true;
                    return;
                }
            }

            byte b = buffer[currentBufferPos--];

            // \n is found - line fully buffered
            if (b == 0xA) {
                currentLineReadPos = currentLineWritePos - 1;
                lineBuffered = true;
                break;

                // just ignore \r for now
            } else if (b == 0xD) {
                continue;
            } else {
                if (currentLineWritePos == maxLineBytes) {
                    throw new IOException("file has a line exceeding " + maxLineBytes
                            + " bytes; use constructor to pickup bigger line buffer");
                }

                // write the current line bytes in reverse order - reading from
                // the end will produce the correct line
                currentLine[currentLineWritePos++] = b;
            }
        }
    }

}