Как работать с очень большим текстовым файлом?

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

  • CSV-based, после RFC 4180, за исключением встроенных разрывов строк
  • случайный доступ для чтения к линиям, хотя в основном по строкам и ближе к концу
  • добавляющие строки в конце
  • (изменение строк). Очевидно, что призывы к тому, чтобы остальная часть файла была переписана, это также редко, поэтому не особенно важно на данный момент.

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

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

Есть ли способ сопоставить смещения в CharBuffer с смещениями в совпадающем ByteBuffer вообще? Это явно тривиально с ASCII или ISO-8859- *, тем более, что с UTF-8 и с ISO 2022 или BOCU-1 все становится совершенно безобразным (не то, что я действительно ожидаю, что последние два, но UTF-8 должен быть по умолчанию здесь - и все еще создает проблемы).

Я думаю, я мог бы просто преобразовать часть CharBuffer в байты снова и использовать длину. Либо он работает, либо у меня возникают проблемы с диакритикой, и в этом случае я мог бы, вероятно, поручить использование NFC или NFD, чтобы гарантировать, что текст всегда однозначно кодируется.

Тем не менее, мне интересно, это даже путь сюда. Есть ли лучшие варианты?

ETA: Некоторые ответы на общие вопросы и предложения здесь:

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

Я также поддерживаю только подмножество CSV (без встроенных разрывов строк), но это нормально. Проблемные моменты здесь в значительной степени таковы, что я не могу предсказать, сколько строк и, следовательно, необходимо создать грубую карту файла.

Что касается того, что я изложил выше: проблема, о которой я размышлял, заключалась в том, что я могу легко определить конец строки на уровне символов (U + 000D + U + 000A), но я не хотел предполагать, что это выглядит как 0A 0D на байтовом уровне (который уже не работает для UTF-16, например, где он либо 0D 00 0A 00, либо 00 0D 00 0A). Мои мысли состояли в том, что я мог бы сделать кодировку символов меняющейся, а не жестко-кодирующую информацию о кодировке, которую я сейчас использую. Но я думаю, я мог бы просто придерживаться UTF-8 и все остальное. Похоже, что-то не так.

Ответы

Ответ 1

Очень сложно поддерживать сопоставление 1:1 между последовательностью Java-символов (что эффективно UTF-16) и байтами, которые могут быть любыми в зависимости от кодировки вашего файла. Даже с UTF-8 "очевидное" отображение 1 байт в 1 char работает только для ASCII. Ни UTF-16, ни UTF-8 не гарантируют, что символ Юникода может быть сохранен в одной машине char или byte.

Я бы сохранил свое окно в файле в виде байтового буфера, а не буфера char. Затем, чтобы найти окончания строки в байтовом буфере, я бы кодировал строку Java "\r\n" (или, возможно, просто "\n") в качестве последовательности байтов, используя ту же кодировку, что и файл. Я бы тогда использовал эту последовательность байтов для поиска окончаний строк в байтовом буфере. Положение строки, заканчивающейся в буфере + смещение буфера от начала файла, точно соответствует позиции байта в файле окончания строки.

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

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

Ответ 2

Можно ли разбить файл в "подфайлах" (разумеется, вы не должны разбить его в пределах одного Utf-8 char)? Затем вам понадобятся метаданные для каждого из подфайлов (общее количество символов и общее количество строк).

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

Даже редактирование становится легким, потому что вам нужно только обновить "подфайл" и его метаданные.

Если вы поместите его в край: тогда вы можете использовать базу данных и хранить одну строку на каждой строке базы данных. - Если это хорошая идея, сильно зависит от вашего использования.

Ответ 3

CharBuffer предполагает, что все символы UTF-16 или UCS-2 (возможно, кто-то знает разницу)

Проблема с использованием правильного текстового формата заключается в том, что вам нужно прочитать каждый байт, чтобы узнать, где находится n-й символ или где находится n-я строка. Я использую текстовые файлы с несколькими GB, но предполагаю данные ASCII-7, и я только читаю/записываю последовательно.

Если вам нужен произвольный доступ к текстовому файлу без объявления, вы не можете ожидать его выполнения.

Если вы хотите купить новый сервер, вы можете получить его с 24 ГБ за 1800 и 64 ГБ за около 4200 фунтов стерлингов. Это позволит вам загружать в память файлы с несколькими ГБ.

Ответ 4

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

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

Ответ 5

  • Поиск начала строки:

Stick с UTF-8 и \n, обозначающий конец строки, не должен быть проблемой. В качестве альтернативы вы можете разрешить UTF-16 и распознавать данные: он должен быть указан (например), имеет N commans (точки с запятой) и другой конец строки. Может прочитать заголовок, чтобы узнать, сколько столбцов структуры.

  • Вставка в середину файла

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

  • добавляющие строки в конце

Это тривиально, пока файл заблокирован (как и любые другие модификации)

Ответ 6

В случае фиксированного количества столбцов я разделил файл логически и/или физически на столбцы и реализовал некоторые оболочки/адаптеры для задач ввода-вывода и управления файлом в целом.

Ответ 7

Как насчет таблицы смещений через несколько регулярных интервалов в файле, чтобы вы могли перезапустить парсинг где-то рядом с местом, которое вы ищете?

Идея заключалась бы в том, что это будут байтовые смещения, где кодирование будет в его исходном состоянии (т.е. если данные были закодированы по ISO-2022, то это место будет в режиме, совместимом с ASCII). Любой индекс в данные будет состоять из указателя в эту таблицу плюс все, что требуется для поиска фактической строки. Если вы поместите точки перезапуска так, чтобы каждая из них находилась между двумя точками, вписывающимися в окно mmap, тогда вы можете опустить код проверки/переназначения/перезапуска из уровня синтаксического анализа и использовать парсер, предполагающий, что данные будут последовательно отображаться.