Прочитать файл по строкам в обратном порядке
У меня есть приложение java ee, где я использую сервлет для печати файла журнала, созданного с помощью log4j. При чтении файлов журналов вы обычно ищете последнюю строку журнала, и поэтому сервлет будет намного полезнее, если он напечатает файл журнала в обратном порядке. Мой фактический код:
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 и загрузку всего файла перед печатью, нет ли способа поиска кода в конце файла и чтения содержимого до начала файла
Ответы
Ответ 1
[EDIT]
По запросу, я предваряю этот ответ с чувством более позднего комментария: если вам это часто требуется, более подходящим решением является перемещение ваших журналов из текстовых файлов в таблицы базы данных с помощью DBAppender (часть log4j 2). Затем вы можете просто запросить последние записи.
[/EDIT]
Я бы, вероятно, приблизился к этому немного иначе, чем перечисленные ответы.
(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
, предоставив ему файл журнала "revere".
Ответ 2
Это старый вопрос. Я также хотел сделать то же самое, и после некоторого поиска найденного класса в apache commons-io для этого:
org.apache.commons.io.input.ReversedLinesFileReader
Ответ 3
Я думаю, что хорошим выбором для этого будет использование RandomFileAccess класса. Существует пример кода для повторного чтения с использованием этого класса на этой странице. Чтение байтов так легко, но чтение строк может быть немного сложнее.
Ответ 4
Более простая альтернатива, поскольку вы говорите, что вы создаете сервлет для этого, заключается в использовании LinkedList
для хранения последних N строк (где N может быть параметром сервлета). Когда размер списка превышает N, вы вызываете removeFirst()
.
С точки зрения пользовательского опыта это, вероятно, лучшее решение. Как вы заметили, самые последние строки являются самыми важными. Не быть перегруженным информацией, также очень важно.
Ответ 5
Если вы спешите и хотите простейшее решение, не слишком беспокоясь о производительности, я бы попытался использовать внешний процесс для выполнения грязной работы (учитывая, что вы используете свое приложение на сервере Un * x, так как любой порядочный человек будет делать XD)
new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("tail yourlogfile.txt -n 50 | rev").getProcess().getInputStream()))
Ответ 6
Хороший вопрос. Я не знаю об общих реализациях этого. Это не так уж и сложно, так что будьте осторожны, что вы выбираете. Он должен иметь дело с кодировкой набора символов и обнаружением различных методов разрыва строки. Здесь реализована реализация, которая работает с кодированными файлами 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);
}
}
}
Ответ 7
вы можете использовать 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
.......
});
Ответ 8
Самое простое решение - прочитать файл в прямом порядке, используя 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
. Существует несколько трюков: чтобы правильно декодировать байты (при условии многобайтовой кодировки), вам нужно будет прочитать байты, соответствующие записи, а затем применить к нему декодирование. Список, однако, даст вам начальную и конечную позиции байтов.
Одно большое преимущество этого подхода, а не просто печать строк в обратном порядке, заключается в том, что вы не повредите многострочные лог-сообщения (например, исключения).
Ответ 9
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 [email protected]
*
*/
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. Он хороший программист-программист, доверяй мне
tokens [vaquar, khan, is, working, in, Citi, He, is, good, good, programmer, programmer, trust, me]
uniquWordList [trust, vaquar, programmer, is, good, into, khan, me, working, Citi, He]
uniquWordList [программист, рабочий, vaquar, trust, good, into, khan, Citi, is, me, He]
Если вы хотите сортировать от A до Z, тогда напишите еще один компаратор
Ответ 10
Краткое решение с использованием Java 7 Autoclosables и потоков Java 8:
try (Stream<String> logStream = Files.lines(Paths.get("C:\\logfile.log"))) {
logStream
.sorted(Comparator.reverseOrder())
.limit(10) // last 10 lines
.forEach(System.out::println);
}
Большой недостаток: работает только тогда, когда строки строго в натуральном порядке, такие как файлы журналов с префиксом timestamps, но без исключений