如何获取BufferedReader已读取的字节数(偏移量)?

5

我想逐行读取文件。 BufferedReader比RandomAccessFile或BufferedInputStream快得多。 但问题是我不知道读取了多少字节。 如何知道已读取的字节数(偏移量)? 我尝试过。

String buffer;
int offset = 0;

while ((buffer = br.readLine()) != null)
    offset += buffer.getBytes().length + 1; // 1 is for line separator

如果文件很小,我可以正常工作。 但是,当文件变得很大时,偏移量会变得比实际值小。 我该如何获取偏移量?


1
你试图完成什么更大的任务?由于内部缓冲区(以及编码和不同的行结尾符),这基本上是棘手的。 - Jon Skeet
我想获取每行开头的偏移量。因此,稍后我将使用这些偏移量使用RandomAccessFile读取文件的某些部分。 - user1301568
1
你假设只有一个换行符字节,例如 \n。你能这样假设吗? - user207421
实际上,我使用了 line.separator 而不是 1。 - user1301568
5个回答

9
由于两个因素:字符编码和行结束符,使用 BufferedReader 并没有一个简单的方法来完成这项任务。在 Windows 中,行结束符是由两个字节组成的 \r\n,而在 Unix 中,行分隔符只有一个字节。 BufferedReader 能够处理这两种情况并且不会让您注意到,在调用 readLine() 之后,您将无法知道跳过了多少个字节。
另外,当默认编码和文件中数据的编码恰好相同时,buffer.getBytes() 才会返回正确的结果。在任何情况下,当使用任何类型的 byte[] <-> String 转换时,您应该始终明确指定所需的编码。
您也不能使用计数的 InputStream,因为缓冲读取器会一次性读取大块数据。因此,在读取第一行(比如5个字节)后,内部 InputStream 中的计数器会返回 4096,因为读取器总是将那么多字节读入其内部缓冲区。
您可以查看 NIO。您可以使用底层的 ByteBuffer 来跟踪偏移量,并将其包装在 CharBuffer 中以将输入转换为行。

使用BufferedReader没有简单的方法来完成这个任务,因为它既进行缓冲又进行新行检测。顺便说一下,感谢您提供有关ByteBuffer和CharBuffer的提示。 - Kirill Gamazkov

1
这里有一个应该可以工作的东西。它假定为UTF-8,但你可以很容易地更改它。
import java.io.*;

class main {
    public static void main(final String[] args) throws Exception {
        ByteCountingLineReader r = new ByteCountingLineReader(new ByteArrayInputStream(toUtf8("Hello\r\nWorld\n")));

        String line = null;
        do {
            long count = r.byteCount();
            line = r.readLine();
            System.out.println("Line at byte " + count + ": " + line);
        } while (line != null);

        r.close();
    }

    static class ByteCountingLineReader implements Closeable {
        InputStream in;
        long _byteCount;
        int bufferedByte = -1;
        boolean ended;

        // in should be a buffered stream!
        ByteCountingLineReader(InputStream in) {
            this.in = in;
        }

        ByteCountingLineReader(File f) throws IOException {
            in = new BufferedInputStream(new FileInputStream(f), 65536);
        }

        String readLine() throws IOException {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            if (ended) return null;
            while (true) {
                int c = read();
                if (ended && baos.size() == 0) return null;
                if (ended || c == '\n') break;
                if (c == '\r') {
                    c = read();
                    if (c != '\n' && !ended)
                        bufferedByte = c;
                    break;
                }
                baos.write(c);
            }
            return fromUtf8(baos.toByteArray());
        }

        int read() throws IOException {
            if (bufferedByte >= 0) {
                int b = bufferedByte;
                bufferedByte = -1;
                return b;
            }
            int c = in.read();
            if (c < 0) ended = true; else ++_byteCount;
            return c;
        }

        long byteCount() {
            return bufferedByte >= 0 ? _byteCount - 1 : _byteCount;
        }

        public void close() throws IOException {
            if (in != null) try {
                in.close();
            } finally {
                in = null;
            }
        }

        boolean ended() {
            return ended;
        }
    }

    static byte[] toUtf8(String s) {
        try {
            return s.getBytes("UTF-8");
        } catch (Exception __e) {
            throw rethrow(__e);
        }
    }

    static String fromUtf8(byte[] bytes) {
        try {
            return new String(bytes, "UTF-8");
        } catch (Exception __e) {
            throw rethrow(__e);
        }
    }

    static RuntimeException rethrow(Throwable t) {

        throw t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t);
    }
}

0

尝试使用RandomAccessFile

     RandomAccessFile raf = new RandomAccessFile(filePath, "r");
     while ((cur_line = raf.readLine()) != null){
        System.out.println(curr_line);
        // get offset
        long rowIndex = raf.getFilePointer();
     }

按偏移量查找:

raf.seek(offset);


-2

我很想知道你的最终解决方案,不过我认为在你上面的代码中使用long类型而不是int类型可以满足大多数情况。


这样做解决不了任何问题? - David

-3
如果你想逐行读取文件,我建议使用以下代码:
import java.io.*;
class FileRead 
{
 public static void main(String args[])
  {
  try{
  // Open the file that is the first 
  // command line parameter
  FileInputStream fstream = new FileInputStream("textfile.txt");
  // Use DataInputStream to read binary NOT text.
  BufferedReader br = new BufferedReader(new InputStreamReader(fstream));
  String strLine;
  //Read File Line By Line
  while ((strLine = br.readLine()) != null)   {
  // Print the content on the console
  System.out.println (strLine);
  }
  //Close the input stream
  in.close();
    }catch (Exception e){//Catch exception if any
  System.err.println("Error: " + e.getMessage());
  }
  }
}

我过去经常使用那种方法,效果非常好!

来源:这里


2
你的回答有点不对,因为你应该在 finally 块中关闭外部资源,而且你没有回答问题。此外,他使用的是类似的东西,但代码示例更紧凑。 - comanitza
如果是来自Rose India的内容,你应该假设它只是大致正确的。最好阅读其他任何网站。 - Peter Lawrey

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