Чтение строк символов и получение позиции файла

Я читаю последовательные строки символов из текстового файла. Кодирование символов в файле может быть не однобайтным.

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

Вопросы

Есть ли простой способ сделать это, желательно, используя стандартные библиотеки Java?

Если нет, то что разумное обходное решение?

Атрибуты идеального решения

Идеальное решение будет обрабатывать множественные кодировки символов. Это включает в себя UTF-8, в котором разные символы могут быть представлены различными байтами. Идеальное решение будет в основном полагаться на надежную, хорошо поддерживаемую библиотеку. Наиболее идеальным будет стандартная библиотека Java. Лучше всего было бы использовать Apache или Google. Решение должно быть масштабируемым. Чтение всего файла в память не является решением. Возврат в позицию не требует считывания всех предыдущих символов в линейном времени.

Подробнее

Для первого требования BufferedReader.readLine() является привлекательным. Но буферизация явно препятствует получению значимой позиции файла.

Менее очевидно, что InputStreamReader также может читать вперед, мешая получению позиции файла. Из Документация InputStreamReader:

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

Метод RandomAccessFile.readLine() читает один байт на символ.

Каждый байт преобразуется в символ, беря байтовое значение для младших восьми бит символа и устанавливая высокие восемь бит символа равным нулю. Поэтому этот метод не поддерживает полный набор символов Unicode.

Ответы

Ответ 1

Если вы построите BufferedReader из FileReader и сохраните экземпляр FileReader, доступный для вашего кода, вы сможете получить позицию следующей строки, вызвав:

fileReader.getChannel().position();

после вызова bufferedReader.readLine().

BufferedReader может быть сконструирован с входным буфером размера 1, если вы готовы торговать приростом производительности для позиционной точности.

Альтернативное решение Что было бы неправильно с отслеживанием самих байтов:

long startingPoint = 0; // or starting position if this file has been previously processed

while (readingLines) {
    String line = bufferedReader.readLine();
    startingPoint += line.getBytes().length;
}

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

Ответ 2

Случай, похоже, решается VTD-XML, библиотекой, способной быстро анализировать большие XML файлы:

Последняя реализация java VTD-XML ximpleware, в настоящее время 2.13 http://sourceforge.net/projects/vtd-xml/files/vtd-xml/ предоставляет некоторый код, поддерживающий смещение байта после каждого вызова getChar ( ) метода его реализации IReader.

Реализации IReader для различных кодировок caracter доступны внутри VTDGen.java и VTDGenHuge.java

Реализации IReader предусмотрены для следующих кодировок

ASCII; iso_8859_1 ISO_8859_10 ISO_8859_11 ISO_8859_12 ISO_8859_13 ISO_8859_14 ISO_8859_15 ISO_8859_16 ISO_8859_2 ISO_8859_3 ISO_8859_4 ISO_8859_5 ISO_8859_6 ISO_8859_7 ISO_8859_8 ISO_8859_9 UTF_16BE UTF_16LE UTF8;
WIN_1250 WIN_1251 WIN_1252 WIN_1253 WIN_1254 WIN_1255 WIN_1256 WIN_1257 WIN_1258

Ответ 3

Я бы предложил java.io.LineNumberReader. Вы можете установить и получить номер строки и, следовательно, продолжить с определенного индекса строки.

Так как это BufferedReader, он также способен обрабатывать UTF-8.

Ответ 4

Решение A

Проблема с чем-либо еще заключается в том, что вам придется абсолютно убедиться, что вы никогда не читали символ EOL.

readChar() возвращает char не байт. Поэтому вам не нужно беспокоиться о ширине символов.

Читает символ из этого файла. Этот метод считывает два байта из файла, начиная с текущего указателя файла.

[...]

Этот метод блокируется до тех пор, пока не будут прочитаны два байта, не будет обнаружен конец потока или исключено исключение.

Используя RandomAccessFile, а не Reader, вы отказываетесь от способности Java расшифровывать кодировку в файле для вас. BufferedReader сделает это автоматически.

Существует несколько способов преодоления этого. Один заключается в том, чтобы самостоятельно определить кодировку, а затем использовать правильный метод read *(). Другой способ - использовать поток BoundedInput.

В этом вопросе есть один Java: чтение строк из файла произвольного доступа с буферизованным вводом

например. fooobar.com/questions/333226/...

Ответ 5

RandomAccessFile имеет функцию: искать (long pos)         Устанавливает смещение указателя файла, измеренное от начала этого файла, при котором происходит следующее чтение или запись.

Ответ 6

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

В UTF-8:

  • Все однобайтовые символы можно отличить от всех байтов в многобайтовых символах. Все байты в многобайтовом символе имеют "1" в позиции высокого порядка. В частности, байты, представляющие LF и CR, не могут быть частью многобайтового символа.
  • Все однобайтовые символы находятся в 7-битном ASCII. Таким образом, мы можем декодировать файл, содержащий только 7-битные символы ASCII с декодером UTF-8.

Взятые вместе, эти две точки означают, что мы можем читать строку с чем-то, что считывает байты, а не символы, а затем декодирует строку.

Чтобы избежать проблем с буферизацией, мы можем использовать RandomAccessFile. Этот класс предоставляет методы для чтения строки и получает/устанавливает положение файла.

Здесь представлен эскиз кода для чтения следующей строки как UTF-8 с использованием RandomAccessFile.

protected static String 
readNextLineAsUTF8( RandomAccessFile in ) throws IOException {
    String rv = null;
    String lineBytes = in.readLine();
    if ( null != lineBytes ) {
        rv = new String( lineBytes.getBytes(),
            StandardCharsets.UTF_8 );
    }
    return rv;
 } 

Затем позиция файла может быть получена из RandomAccessFile непосредственно перед вызовом этого метода. С учетом RandomAccessFile, на который ссылается in:

    long startPos = in.getFilePointer();
    String line = readNextLineAsUTF8( in );