在Java中处理文件指针的高效方法?(使用带有文件指针的BufferedReader)

6
我有一个日志文件,每秒钟都会更新。我需要定期读取该日志文件,并且一旦读取完成后,我需要将文件指针位置存储在我读取的最后一行的末尾,并在下一次定期读取时从那个位置开始。
目前,我在Java中使用随机访问文件,并使用getFilePointer()方法获取偏移值和seek()方法转到偏移位置。
然而,我已经在大多数文章和甚至Java文档中阅读到推荐使用BufferedReader以实现有效地读取文件。如何使用BufferedReader来实现这一目标(获取文件指针并移动到最后一行),或者是否有其他有效的方式来完成此任务?
3个回答

4

以下是几个可行的方法:

  • 使用FileInputStream打开文件,跳过所需字节数后,通过InputStreamReader将BufferedReader包装在流周围;
  • 打开文件(使用FileInputStream或RandomAccessFile),在流/RandomAccessFile上调用getChannel()以获取底层FileChannel,在通道上调用position(),然后调用Channels.newInputStream()从通道中获取一个输入流,将其传递给InputStreamReader -> BufferedReader。

我并没有真正地对这些进行性能分析,但您应该看看哪种方法在您的情况下效果更好。

RandomAccessFile的问题在于它的readLine()方法非常低效。如果方便从RAF中读取数据并自己进行缓冲以拆分行,那么RAF本身就没有问题--只是其readLine()的实现很差。


1
Neil Coffey的解决方案适用于读取固定长度文件。但是对于具有可变长度(数据不断进入)的文件,直接在FileInputStream或FileChannel输入流上使用BufferedReader或通过InputStreamReader使用其readLine方法存在一些问题。例如,考虑以下情况:
1)您想要从某个偏移量读取数据到当前文件长度。因此,您在FileInputStream/FileChannel(通过InputStreamReader)上使用BR,并使用其readLine方法。但是,当您正在忙于读取数据时,假设添加了一些数据,这会导致BF的readLine读取比您预期的(之前的文件长度)更多的数据。
2)您完成了readLine操作,但是当您尝试读取当前文件长度/通道位置时,突然添加了一些数据,这会导致当前文件长度/通道位置增加,但是您已经读取的数据比这少。
在上述两种情况中,很难知道实际读取的数据(您不能仅使用使用readLine读取的数据长度,因为它跳过了一些字符,如回车符)。
因此,最好以缓冲字节方式读取数据,并在此周围使用BufferedReader包装器。我编写了一些类似于此的方法。
/** Read data from offset to length bytes in RandomAccessFile using BufferedReader
 * @param offset
 * @param length
 * @param accessFile
 * @throws IOException
 */
    public static void readBufferedLines(long offset, long length, RandomAccessFile accessFile) throws IOException{
    if(accessFile == null) return;
    int bufferSize = BYTE_BUFFER_SIZE;// constant say 4096

    if(offset < length && offset >= 0){ 
        int index = 1;
        long curPosition = offset;
        /*
         * iterate (length-from)/BYTE_BUFFER_SIZE times to read into buffer no matter where new line occurs
         */
        while((curPosition + (index * BYTE_BUFFER_SIZE)) <  length){        

            accessFile.seek(offset); // seek to last parsed data rather than last data read in to buffer

            byte[] buf = new byte[bufferSize];
            int read = accessFile.read(buf, 0, bufferSize);
            index++;// Increment whether or not read successful

            if(read > 0){

                int lastnewLine = getLastLine(read,buf);

                if(lastnewLine <= 0){ // no new line found in the buffer reset buffer size and continue
                    bufferSize = bufferSize+read;
                    continue;

                }
                else{
                    bufferSize = BYTE_BUFFER_SIZE;
                }

                readLine(buf, 0, lastnewLine); // read the lines from buffer and parse the line

                offset = offset+lastnewLine; // update the last data read

            }

        }



        // Read last chunk. The last chunk size in worst case is the total file when no newline occurs 
        if(offset < length){

            accessFile.seek(offset); 
            byte[] buf = new byte[(int) (length-offset)];
            int read = accessFile.read(buf, 0, buf.length);

            if(read > 0){

                readLine(buf, 0, read);

                offset = offset+read; // update the last data read


            }
        }


    }

}

private static void readLine(byte[] buf, int from , int lastnewLine) throws IOException{

    String readLine = "";
    BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf,from,lastnewLine) ));
    while( (readLine =  reader.readLine()) != null){
        //do something with readLine
        System.out.println(readLine);
    }
    reader.close();
}


private static int getLastLine(int read, byte[] buf) {
    if(buf == null ) return -1;
    if(read > buf.length) read = buf.length;
    while( read > 0 && !(buf[read-1] == '\n' || buf[read-1] == '\r')) read--;       
    return read;
}   
 public static void main(String[] args) throws IOException {
    RandomAccessFile accessFile = new RandomAccessFile("C:/sri/test.log",    "r");
    readBufferedLines(0, accessFile.length(), accessFile);
    accessFile.close();

}

0
我遇到了类似的问题,于是我创建了这个类来从 BufferedStream 中获取行,并通过使用 getBytes() 来计算到目前为止读取了多少字节。我们默认假设行分隔符只有一个字节,并且重新实例化 BufferedReader 以使 seek() 起作用。
public class FileCounterIterator {

    public Long position() {
        return _position;
    }

    public Long fileSize() {
        return _fileSize;
    }

    public FileCounterIterator newlineLength(Long newNewlineLength) {
        this._newlineLength = newNewlineLength;
        return this;
    }

    private Long _fileSize = 0L;
    private Long _position = 0L;
    private Long _newlineLength = 1L;
    private RandomAccessFile fp;
    private BufferedReader itr;

    public FileCounterIterator(String filename) throws IOException {
        fp = new RandomAccessFile(filename, "r");
        _fileSize = fp.length();
        this.seek(0L);
    }

    public FileCounterIterator seek(Long newPosition) throws IOException {
        this.fp.seek(newPosition);
        this._position = newPosition;
        itr = new BufferedReader(new InputStreamReader(new FileInputStream(fp.getFD())));
        return this;
    }

    public Boolean hasNext() throws IOException {
        return this._position < this._fileSize;
    }

    public String readLine() throws IOException {
        String nextLine = itr.readLine();
        this._position += nextLine.getBytes().length + _newlineLength;
        return nextLine;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接