Подтвердить что ты не робот

Прочитать файл по строкам в обратном порядке

У меня есть приложение 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 и загрузку всего файла перед печатью, нет ли способа поиска кода в конце файла и чтения содержимого до начала файла

4b9b3361

Ответ 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, но без исключений