Чтение огромного файла в Java

#java #bufferedreader

#java #bufferedreader

Вопрос:

Я прочитал огромный File (почти 5 миллионов строк). Каждая строка содержит дату и запрос, я должен анализировать запросы между конкретными ** датами **. Я использую BufferedReader для чтения File до начала Date , а затем для начала строк синтаксического анализа. Могу ли я использовать Thread s для синтаксического анализа строк, потому что это занимает много времени?

Комментарии:

1. вы пробовали читать файл последовательно, без синтаксического анализа, чтобы определить, не слишком ли он медленный для вашего варианта использования?

2. чтение файла без синтаксического анализа занимает всего несколько секунд, но для синтаксического анализа требуется почти 150 секунд

3. и 150 секунд — это слишком много? каков ваш вариант использования, вы должны сделать это встроенным в веб-приложение или это пакетная программа?

4. Вы могли бы поместить каждую строку в очередь и заставить потоки считывать из очереди для их обработки (BlockingQueue, потоки считывают из нее). Но если для анализа запроса не потребуется сколько-нибудь значимого времени (что, я полагаю, маловероятно, если это одна строка в файле), вы, вероятно, не увидите значительного прироста производительности (накладные расходы на блокировку потоков перевесят любые выгоды от параллельной обработки). Также вероятно, что простое чтение файла в любом случае будет самой медленной частью, поэтому потоковый синтаксический анализ на самом деле не поможет. Но лучший подход — просто протестировать и посмотреть.

5. Если анализ даты не медленный, и чтение файла не медленное … тогда что здесь медленного?

Ответ №1:

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

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

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

Ответ №2:

Во-первых, 5 миллионов строк по 1000 символов — это всего лишь 5 Гб, что не обязательно является непомерно большим для JVM. Если это действительно критический вариант использования с большим количеством обращений, то покупка большего объема памяти почти наверняка является правильным решением.

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

Что-то вроде

 HashMap<Date, ArrayList<String>> ()
  

было бы неплохо. Это должно иметь объем используемой памяти порядка 5 000 000 * 32/8 байт = 20 Мб, что должно быть нормально.

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

Смотрите http://docs.oracle.com/javase/7/docs/api/java/nio/channels/FileChannel.html

И http://en.wikipedia.org/wiki/Memory-mapped_file

Ответ №3:

Хороший способ распараллелить множество небольших задач — это обернуть обработку каждой задачи с помощью FutureTask, а затем передать каждую задачу ThreadPoolExecutor для их запуска. Исполнитель должен быть инициализирован с учетом количества доступных ядер процессора вашей системы.

При вызове executor.execute(future) future будет помещен в очередь для фоновой обработки. Чтобы избежать создания и уничтожения слишком большого количества потоков, ScheduledThreadPoolExecutor создаст только столько потоков, сколько вы указали, и выполнит фьючерсы один за другим.

Чтобы получить результат future, вызовите future.get() . Когда future еще не завершен (или даже еще не был запущен), этот метод будет зависать до его завершения. Но другие фьючерсы выполняются в фоновом режиме, пока вы ждете.

Не забудьте вызвать, executor.shutdown() когда он вам больше не понадобится, чтобы убедиться, что он завершает фоновые потоки, которые в противном случае сохраняются до истечения времени сохранения или сбора мусора.

псевдокод tl; dr:

  create executor
 for each line in file
     create new FutureTask which parses that line
     pass future task to executor
     add future task to a list
 for each entry in task list
     call entry.get() to retrieve result
 executor.shutdown()