Читать файл построчно в обратном порядке - PullRequest
28 голосов
/ 16 мая 2011

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

Ответы [ 10 ]

11 голосов
/ 16 мая 2011

[РЕДАКТИРОВАТЬ]

По запросу я добавляю этот ответ к комментарию более позднего комментария: если вам часто требуется такое поведение, «более подходящим» решением, вероятно, является перемещение ваших журналов из текстовых файлов.в таблицы базы данных с помощью 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, что дает ему файл журнала "admre".

8 голосов
/ 26 сентября 2014

Это старый вопрос.Я также хотел сделать то же самое, и после некоторого поиска был найден класс в apache commons-io для достижения этой цели:

org.apache.commons.io.input.ReversedLinesFileReader

4 голосов
/ 16 мая 2011

Я думаю, что хорошим выбором для этого будет использование RandomFileAccess класса.На этой странице приведен пример кода для повторного чтения с использованием этого класса .Чтение байтов таким способом легко, однако чтение строк может быть более сложным.

2 голосов
/ 16 мая 2011

Если вы спешите и хотите самое простое решение, не слишком заботясь о производительности, я бы попробовал использовать внешний процесс для грязной работы (учитывая, что вы запускаете свое приложение на сервере Un * x)., как любой порядочный человек сделал бы XD)

new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("tail yourlogfile.txt -n 50 | rev").getProcess().getInputStream()))
2 голосов
/ 16 мая 2011

Более простая альтернатива, поскольку вы говорите, что создаете сервлет для этого, состоит в использовании LinkedList для хранения последних N строк (где N можетбыть параметром сервлета).Когда размер списка превышает N , вы звоните removeFirst().

С точки зрения пользовательского опыта, это, вероятно, лучшее решение.Как вы заметили, самые последние строки являются наиболее важными.Очень важно не перегружаться информацией.

1 голос
/ 19 июля 2012

вы можете использовать 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
                                   .......
            });
1 голос
/ 16 мая 2011

Хороший вопрос.Я не знаю каких-либо распространенных реализаций этого.Это не тривиально, чтобы сделать правильно, так что будьте осторожны, что вы выбираете.Он должен иметь дело с кодировкой набора символов и обнаружением различных методов разрыва строки.Ниже приведена реализация, которая работает с файлами в кодировке 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);
        }
    }
}
0 голосов
/ 27 августа 2016

Краткое решение с использованием 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);
}

Большой недостаток : работает только тогда, когда строки строго в естественном порядке, как файлы журналов с префиксами времени, но без исключений

0 голосов
/ 02 марта 2016
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, работает, в Citi, он, хорошо, хорошо, программист, программист, доверься мне]

uniquWordList [доверие, vaquar, программист, хорошо, хан, я, работаю, Citi, He]

uniquWordList [программист, работает, vaquar, доверие, хорошо, в, хан, Citi, это я, он]

Если вы хотите отсортировать по Z, то напишите еще один компаратор

0 голосов
/ 16 мая 2011

Самое простое решение - прочитать файл в прямом порядке, используя 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.Здесь есть небольшая хитрость: чтобы правильно декодировать байты (при условии многобайтового кодирования), вам необходимо прочитать байты, соответствующие записи, а затем применить к ней декодирование.Список, однако, даст вам начальную и конечную позиции байтов.

Одно большое преимущество этого подхода, вместо простой печати строк в обратном порядке, состоит в том, что вы не повредите многострочный журнал.сообщения (например, исключения).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...