web-dev-qa-db-ja.com

ファイルを1行ずつ逆の順序で読み取る

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を使用し、印刷する前にすべてのファイルをロードすることが含まれます。ファイルの終わりまでシークし、ファイルの先頭までコンテンツを読み取る簡単なコード方法はありませんか?

28
eliocs

[編集]

リクエストに応じて、この回答の前にコメントを追加します。この動作が頻繁に必要な場合、「より適切な」解決策は、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)WriterAppenderのインスタンスを作成するためにcreateWriterメソッドがオーバーライドされるlog4j 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のインスタンスを作成して、「以前の」ログファイルを指定するだけです。

11
Nathan Ryan

これは古い質問です。私も同じことをしたいと思いました、そしていくつかの検索の後でこれを達成するために Apache commons-io にクラスがあることがわかりました:

org.Apache.commons.io.input.ReversedLinesFileReader

RandomFileAccess クラスを使用することをお勧めします。このクラスを使用した逆読みのサンプルコード このページ があります。この方法でバイトを読み取るのは簡単ですが、文字列を読み取るのは少し難しいかもしれません。

4
yms

急いでいて、パフォーマンスをあまり気にせずに最も簡単な解決策が必要な場合は、外部プロセスを使用してダーティジョブを実行してみてください(Un * xサーバーでアプリを実行している場合は、きちんとした人はXDをします)

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

これを行うサーブレットを作成していると言うので、より簡単な代替方法は、LinkedListを使用して最後の[〜#〜] n [〜#〜]を保持することです。行(ここで[〜#〜] n [〜#〜]はサーブレットパラメータの場合があります)。リストのサイズが[〜#〜] n [〜#〜]を超える場合、removeFirst()を呼び出します。

ユーザーエクスペリエンスの観点からは、これがおそらく最良のソリューションです。お気づきのように、最新の行が最も重要です。情報に圧倒されないことも非常に重要です。

2
Anon

良い質問。これの一般的な実装については知りません。適切に行うことも簡単ではないので、選択する内容に注意してください。文字セットエンコーディングとさまざまな改行方法の検出を処理する必要があります。 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);
        }
    }
}
1
WhiteFang34

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
user1536505
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に取り組んでいます彼は良いプログラマープログラマーを信頼しています私にトークンを信頼してください

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

uniquWordList [プログラマ、作業、vaquar、信頼、良い、に、カーン、シティ、is、私、彼]

AからZに並べ替える場合は、もう1つのコンパレータを記述します。

0
vaquar khan

Java 7 Autoclosables and 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
Journeycorner

最も簡単な解決策は、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を使用します。ちょっとしたトリックがあります。バイトを適切にデコードするには(マルチバイトエンコーディングを想定)、エントリに対応するバイトを読み取ってから、それにデコードを適用する必要があります。ただし、リストにはバイトの開始位置と終了位置が示されます。

このアプローチの大きな利点の1つは、単に行を逆順に印刷するのではなく、複数行のログメッセージ(例外など)に影響を与えないことです。

0
Anon