按相反顺序逐行读取文件

时间:2011-05-15 21:23:10

标签: java file text log4j

我有一个java ee应用程序,我使用servlet打印用log4j创建的日志文件。在读取日志文件时,通常会查找最后一个日志行,因此如果以相反的顺序打印日志文件,则servlet会更有用。我的实际代码是:

    response.setContentType("text");
    PrintWriter out = response.getWriter();
    try {
        FileReader logReader = new FileReader("logfile.log");
        try {
            BufferedReader buffer = new BufferedReader(logReader);
            for (String line = buffer.readLine(); line != null; line = buffer.readLine()) {
                out.println(line);
            }
        } finally {
            logReader.close();
        }
    } finally {
        out.close();
    }

我在互联网上发现的实现涉及使用StringBuffer并在打印之前加载所有文件,是不是有一种代码轻量级方式来寻找文件的末尾并读取内容直到文件的开头?

10 个答案:

答案 0 :(得分:11)

[编辑]

根据请求,我在前面的评论中提到了这个答案:如果您经常需要这种行为,“更合适”的解决方案可能是使用DBAppender将日志从文本文件移动到数据库表(log4j的一部分) 2)。然后你可以简单地查询最新的条目。

[/编辑]

我可能会比列出的答案略有不同。

(1)创建Writer的子类,以相反的顺序写入每个字符的编码字节:

public class ReverseOutputStreamWriter extends Writer {
    private OutputStream out;
    private Charset encoding;
    public ReverseOutputStreamWriter(OutputStream out, Charset encoding) {
        this.out = out;
        this.encoding = encoding;
    }
    public void write(int ch) throws IOException {
        byte[] buffer = this.encoding.encode(String.valueOf(ch)).array();
        // write the bytes in reverse order to this.out
    }
    // other overloaded methods
}

(2)创建log4j WriterAppender的子类,其createWriter方法将被覆盖,以创建ReverseOutputStreamWriter的实例。

(3)创建log4j Layout的子类,其format方法以反向字符顺序返回日志字符串:

public class ReversePatternLayout extends PatternLayout {
    // constructors
    public String format(LoggingEvent event) {
        return new StringBuilder(super.format(event)).reverse().toString();
    }
}

(4)修改我的日志配置文件,将日志消息发送到 “普通”日志文件和“反向”日志文件。 “反向”日志文件将包含与“普通”日志文件相同的日志消息,但每条消息都将向后写入。 (请注意,“反向”日志文件的编码不一定符合UTF-8,甚至不符合任何字符编码。)

(5)创建一个InputStream的子类,它包装RandomAccessFile的实例,以便以相反的顺序读取文件的字节:

public class ReverseFileInputStream extends InputStream {
    private RandomAccessFile in;
    private byte[] buffer;
    // The index of the next byte to read.
    private int bufferIndex;
    public ReverseFileInputStream(File file) {
        this.in = new RandomAccessFile(File, "r");
        this.buffer = new byte[4096];
        this.bufferIndex = this.buffer.length;
        this.in.seek(file.length());
    }
    public void populateBuffer() throws IOException {
        // record the old position
        // seek to a new, previous position
        // read from the new position to the old position into the buffer
        // reverse the buffer
    }
    public int read() throws IOException {
        if (this.bufferIndex == this.buffer.length) {
            populateBuffer();
            if (this.bufferIndex == this.buffer.length) {
                return -1;
            }
        }
        return this.buffer[this.bufferIndex++];
    }
    // other overridden methods
}

现在,如果我想以相反的顺序读取“普通”日志文件的条目,我只需创建一个ReverseFileInputStream的实例,为其提供“崇敬”日志文件。

答案 1 :(得分:8)

这是一个老问题。我也想做同样的事情,经过一些搜索后发现apache commons-io中有一个类来实现这个目的:

org.apache.commons.io.input.ReversedLinesFileReader

答案 2 :(得分:4)

我认为这是一个很好的选择,就是使用RandomFileAccess类。有一些示例代码可以使用此类on this page进行反向读取。以这种方式读取字节很容易,但读取字符串可能会更具挑战性。

答案 3 :(得分:2)

一个更简单的替代方案,因为你说你正在创建一个servlet来执行此操作,就是使用LinkedList来保存最后的 N 行(其中 N < / em>可能是一个servlet参数)。当列表大小超过 N 时,您调用removeFirst()

从用户体验的角度来看,这可能是最好的解决方案。如您所知,最新的行是最重要的。不被信息所淹没也非常重要。

答案 4 :(得分:2)

如果你急着想要最简单的解决方案而不用担心太多关于性能的问题,我会尝试使用外部进程来执行脏工作(假设你在Un * x服务器上运行你的应用程序) ,任何体面的人都会做XD)

new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("tail yourlogfile.txt -n 50 | rev").getProcess().getInputStream()))

答案 5 :(得分:1)

好问题。我不知道这个的任何常见实现。做正确的事也不是一件容易的事情,所以要小心你的选择。它应该处理字符集编码和不同换行方法的检测。这是我到目前为止实现的与ASCII和UTF-8编码文件一起使用的实现,包括UTF-8的测试用例。它不适用于UTF-16LE或UTF-16BE编码文件。

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import junit.framework.TestCase;

public class ReverseLineReader {
    private static final int BUFFER_SIZE = 8192;

    private final FileChannel channel;
    private final String encoding;
    private long filePos;
    private ByteBuffer buf;
    private int bufPos;
    private byte lastLineBreak = '\n';
    private ByteArrayOutputStream baos = new ByteArrayOutputStream();

    public ReverseLineReader(File file, String encoding) throws IOException {
        RandomAccessFile raf = new RandomAccessFile(file, "r");
        channel = raf.getChannel();
        filePos = raf.length();
        this.encoding = encoding;
    }

    public String readLine() throws IOException {
        while (true) {
            if (bufPos < 0) {
                if (filePos == 0) {
                    if (baos == null) {
                        return null;
                    }
                    String line = bufToString();
                    baos = null;
                    return line;
                }

                long start = Math.max(filePos - BUFFER_SIZE, 0);
                long end = filePos;
                long len = end - start;

                buf = channel.map(FileChannel.MapMode.READ_ONLY, start, len);
                bufPos = (int) len;
                filePos = start;
            }

            while (bufPos-- > 0) {
                byte c = buf.get(bufPos);
                if (c == '\r' || c == '\n') {
                    if (c != lastLineBreak) {
                        lastLineBreak = c;
                        continue;
                    }
                    lastLineBreak = c;
                    return bufToString();
                }
                baos.write(c);
            }
        }
    }

    private String bufToString() throws UnsupportedEncodingException {
        if (baos.size() == 0) {
            return "";
        }

        byte[] bytes = baos.toByteArray();
        for (int i = 0; i < bytes.length / 2; i++) {
            byte t = bytes[i];
            bytes[i] = bytes[bytes.length - i - 1];
            bytes[bytes.length - i - 1] = t;
        }

        baos.reset();

        return new String(bytes, encoding);
    }

    public static void main(String[] args) throws IOException {
        File file = new File("my.log");
        ReverseLineReader reader = new ReverseLineReader(file, "UTF-8");
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
    }

    public static class ReverseLineReaderTest extends TestCase {
        public void test() throws IOException {
            File file = new File("utf8test.log");
            String encoding = "UTF-8";

            FileInputStream fileIn = new FileInputStream(file);
            Reader fileReader = new InputStreamReader(fileIn, encoding);
            BufferedReader bufReader = new BufferedReader(fileReader);
            List<String> lines = new ArrayList<String>();
            String line;
            while ((line = bufReader.readLine()) != null) {
                lines.add(line);
            }
            Collections.reverse(lines);

            ReverseLineReader reader = new ReverseLineReader(file, encoding);
            int pos = 0;
            while ((line = reader.readLine()) != null) {
                assertEquals(lines.get(pos++), line);
            }

            assertEquals(lines.size(), pos);
        }
    }
}

答案 6 :(得分:1)

您可以使用RandomAccessFile实现此功能,例如:

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

import com.google.common.io.LineProcessor;
public class FileUtils {
/**
 * 反向读取文本文件(UTF8),文本文件分行是通过\r\n
 * 
 * @param <T>
 * @param file
 * @param step 反向寻找的步长
 * @param lineprocessor
 * @throws IOException
 */
public static <T> T backWardsRead(File file, int step,
        LineProcessor<T> lineprocessor) throws IOException {
    RandomAccessFile rf = new RandomAccessFile(file, "r");
    long fileLen = rf.length();
    long pos = fileLen - step;
    // 寻找倒序的第一行:\r
    while (true) {
        if (pos < 0) {
            // 处理第一行
            rf.seek(0);
            lineprocessor.processLine(rf.readLine());
            return lineprocessor.getResult();
        }
        rf.seek(pos);
        char c = (char) rf.readByte();
        while (c != '\r') {
            c = (char) rf.readByte();
        }
        rf.readByte();//read '\n'
        pos = rf.getFilePointer();
        if (!lineprocessor.processLine(rf.readLine())) {
            return lineprocessor.getResult();
        }
        pos -= step;
    }

  }

使用:

       FileUtils.backWardsRead(new File("H:/usersfavs.csv"), 40,
            new LineProcessor<Void>() {
                                   //TODO  implements method
                                   .......
            });

答案 7 :(得分:0)

最简单的解决方案是以正向顺序读取文件,使用ArrayList<Long>保存每个日志记录的字节偏移量。您需要使用类似Jakarta Commons CountingInputStream之类的东西来检索每条记录的位置,并且需要仔细组织缓冲区以确保它返回正确的值:

FileInputStream fis = // .. logfile
BufferedInputStream bis = new BufferedInputStream(fis);
CountingInputStream cis = new CountingInputSteam(bis);
InputStreamReader isr = new InputStreamReader(cis, "UTF-8");

你可能无法使用BufferedReader,因为它会尝试预读并甩掉计数(但是一次读取一个字符不会是性能问题,因为你在堆栈中缓慢下来。)

要编写文件,请向后迭代列表并使用RandomAccessFile。有一个技巧:要正确解码字节(假设是多字节编码),您需要读取与条目对应的字节,然后对其应用解码。但是,该列表将为您提供字节的开始和结束位置。

与简单地按相反顺序打印行相比,这种方法的一大好处是,您不会损坏多行日志消息(例如异常)。

答案 8 :(得分:0)

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
 * Inside of C:\\temp\\vaquar.txt we have following content
 * vaquar khan is working into Citi He is good good programmer programmer trust me
 * @author vaquar.khan@gmail.com
 *
 */

public class ReadFileAndDisplayResultsinReverse {
    public static void main(String[] args) {
        try {
            // read data from file
            Object[] wordList = ReadFile();
            System.out.println("File data=" + wordList);
            //
            Set<String> uniquWordList = null;
            for (Object text : wordList) {
                System.out.println((String) text);
                List<String> tokens = Arrays.asList(text.toString().split("\\s+"));
                System.out.println("tokens" + tokens);
                uniquWordList = new HashSet<String>(tokens);
                // If multiple line then code into same loop
            }
            System.out.println("uniquWordList" + uniquWordList);

            Comparator<String> wordComp= new Comparator<String>() {

                @Override
                public int compare(String o1, String o2) {
                    if(o1==null && o2 ==null) return 0;
                    if(o1==null ) return o2.length()-0;
                    if(o2 ==null) return o1.length()-0;
                    //
                    return o2.length()-o1.length();
                }
            };
            List<String> fs=new ArrayList<String>(uniquWordList);
            Collections.sort(fs,wordComp);

            System.out.println("uniquWordList" + fs);

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    static Object[] ReadFile() throws IOException {
        List<String> list = Files.readAllLines(new File("C:\\temp\\vaquar.txt").toPath(), Charset.defaultCharset());
        return list.toArray();
    }


}

<强>输出:

[Vaquar khan正在和Citi合作他是一名优秀的程序员,相信我 令牌[vaquar,khan,is,working,into,Citi,他,是,好,好,程序员,程序员,信任,我]

uniquWordList [trust,vaquar,programmer,is,good,into,khan,me,working,Citi,He]

uniquWordList [程序员,工作,vaquar,信任,好,成,汗,花旗,是,我,他]

如果要将A排序为Z,则再写一个比较器

答案 9 :(得分:0)

使用Java 7 Autoclosables和Java 8 Streams的简明解决方案:

try (Stream<String> logStream = Files.lines(Paths.get("C:\\logfile.log"))) {
   logStream
      .sorted(Comparator.reverseOrder())
      .limit(10) // last 10 lines
      .forEach(System.out::println);
}

大缺点:仅当行严格按自然顺序排列时才有效,例如前缀为时间戳但没有异常的日志文件