按相反顺序逐行读取文件

我有一个java ee应用程序,我使用servlet来打印使用log4j创build的日志文件。 在读取日志文件时,通常会查找最后一个日志行,因此如果以相反的顺序打印日志文件,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(); } 

我在互联网上find的实现包括使用一个StringBuffer并在打印之前加载所有的文件,是不是有一种代码的方式来寻找文件的结尾,直到文件开始读取内容?

[编辑]

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

[/编辑]

我可能会略微不同于列出的答案。

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

 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)创build一个log4j WriterAppender的子类,其createWriter方法将被覆盖以创build一个ReverseOutputStreamWriter的实例。

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

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

(4)修改我的日志configuration文件,将日志消息发送到“正常”日志文件和“反向”日志文件。 “反向”日志文件将包含与“正常”日志文件相同的日志消息,但是每个消息将被反写。 (请注意,“反向”日志文件的编码不一定符合UTF-8甚至任何字符编码。)

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

 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 } 

现在,如果我想以相反的顺序读取“正常”日志文件的条目,我只需要创build一个ReverseFileInputStream的实例,给它“尊敬”日志文件。

这是一个老问题。 我也想做同样的事情,经过一番search发现,有一个类在Apache的公用事业,以实现这一点:

org.apache.commons.io.input.ReversedLinesFileReader

我认为这是一个很好的select使用RandomFileAccess类。 有一些示例代码用于在这个页面上使用这个类的背部阅读。 以这种方式读取字节很容易,但是读取string可能更具挑战性。

一个更简单的select,因为你说你正在创build一个servlet来完成这个任务,就是使用一个LinkedList来保存最后N行(其中N可能是一个servlet参数)。 当列表大小超过N时 ,可以调用removeFirst()

从用户体验的angular度来看,这可能是最好的解决scheme。 如您所见,最近的几行是最重要的。 不要被信息淹没也是非常重要的。

如果你急着想要最简单的解决scheme,而不用担心性能太差,我会尝试使用外部进程来完成这个肮脏的工作(假设你正在Un * x服务器上运行你的应用程序,体面的人会做XD)

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

好问题。 我不知道这个的任何常见的实现。 做正确的做法也不是微不足道的,所以要小心你select的东西。 它应该处理字符集编码和不同换行方法的检测。 以下是我目前使用的实现ASCII和UTF-8编码文件的实现,包括UTF-8的testing用例。 它不适用于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); } } } 

你可以使用RandomAccessFile实现这个function,比如:

 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 ....... }); 

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

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

而且你可能无法使用BufferedReader ,因为它会尝试读取并抛出计数(但是一次读取一个字符不会是性能问题,因为你在堆栈)。

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

这种方法的一大好处,而不是简单地以相反的顺序打印行,而不会损害多行日志消息(例如,例外)。

 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汗正在花旗工作他是一个很好的程序员程序员相信我的代币[vaquar,khan,is,working,into,花旗,他,是,good,good,programmer,programmer,trust,me]

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

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

如果你想sortingA到Z然后再写一个比较器

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

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

大缺点 :只有当行严格按照自然顺序时才起作用,比如带有时间戳前缀但没有例外的日志文件