java эффективный способ обработки больших текстовых файлов
Я делаю частотный словарь, в котором я читаю 1000 файлов, каждый из которых содержит около 1000 строк. Подход, которому я следую, таков:
- BufferedReader для чтения fileByFile
- прочитайте первый файл, получите первое предложение, разделите предложение на строку массива, а затем заполните хэш-карту значениями из массива строк.
- сделайте это для всех отправленных сообщений в этом файле
- сделайте это для всех 1000 файлов
Моя проблема в том, что это не очень эффективный способ сделайте это, я беру около 4 минут, чтобы сделать все это. Я увеличил размер кучи, переработал код, чтобы убедиться, что я не делаю что-то не так. При таком подходе я абсолютно уверен, что в коде нет ничего, что я мог бы улучшить.
Моя ставка заключается в том, что каждый раз, когда sentece читается, применяется расщепление, которое, умноженное на 1000 предложений в файле и на 1000 файлов, является огромным количеством расщеплений для обработки.
Моя идея заключается в том, что вместо чтения и обработки файла за файлом, я мог бы прочитать каждый файл в массив символов, а затем сделайте разделение только один раз на файл. Это облегчило бы количество времени обработки, потребляемое при разделении. Мы будем признательны за любые предложения по осуществлению.
6 ответов:
Хорошо, я только что реализовал POC вашего словаря. Быстро и грязно. Мои файлы содержали 868 строк каждая, но я создал 1024 копии одного и того же файла. (Это оглавление документации Spring Framework.)
Я провел свой тест, и он занял 14020 МС (14 секунд!). Кстати, я запустил его из eclipse, что может немного снизить скорость. Итак, я не знаю, в чем заключается ваша проблема. Пожалуйста, попробуйте мой код на вашем компьютере, и если он работает быстрее, попробуйте сравнить его с вашим кодом и понять, где корень проблемы.В любом случае мой код не самый быстрый, который я могу написать. Я могу создать шаблон перед циклом и использовать его вместо строки.расщеплять(). Строка.split() вызывает шаблон.compile() каждый раз. Создание шаблона очень дорого.
Вот код:
public static void main(String[] args) throws IOException { Map<String, Integer> words = new HashMap<String, Integer>(); long before = System.currentTimeMillis(); File dir = new File("c:/temp/files"); for (File file : dir.listFiles()) { BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); for (String line = reader.readLine(); line != null; line = reader.readLine()) { String[] lineWords = line.split("\\s+"); for (String word : lineWords) { int count = 1; Integer currentCount = words.get(word); if (currentCount != null) { count = currentCount + 1; } words.put(word, count); } } } long after = System.currentTimeMillis(); System.out.println("run took " + (after - before) + " ms"); System.out.println(words); }
Если вы не заботитесь о содержимом в разных файлах, я бы сделал подход, который вы рекомендуете. Считайте все файлы и все строки в память (string, или char array, что угодно), а затем выполните разделение 1 и хэш-заполнение на основе одной строки/набора данных.
Если я понимаю, что вы делаете, я не думаю, что вы хотите использовать строки, кроме как при доступе к вашей карте.
Вы хотите:
Цикл по файлам считывание каждого файла в буфер размером примерно 1024 обработайте буфер, ища символы конца слова создайте строку из массива символов проверьте свою карту если найдено, обновите счетчик, если нет, создайте новую запись когда вы достигнете конца буфера, получите следующий буфер из файла в конце, петля к следующему файл
Split, вероятно, довольно дорого, так как он должен интерпретировать выражение каждый раз.
Чтение файла как одной большой строки, а затем разделение, что звучит как хорошая идея. Разбиение/изменение строк может быть удивительно "тяжелым", когда речь заходит о сборке мусора. Несколько строк / предложений означает несколько строк, и со всеми разделениями это означает огромное количество строк (строки неизменяемы, поэтому любое изменение в них фактически создаст новую строку или несколько строк)... это создает много мусора, который нужно собрать, и сбор мусора может стать узким местом (при меньшем объеме кучи, максимальный объем памяти достигается все время, начиная сборку мусора, которая потенциально должна очистить сотни тысяч или миллионы отдельных строковых объектов).
Конечно, не зная вашего кода, это просто дикое предположение, но в свое время я получил старую командную строку Java-программ (это был алгоритм-график, производящий огромный SVG-файл), время выполнения которого упало примерно с 18 секунд до менее чем 0,5 секунды, просто изменив код. обработки строк использовать StringBuffers/Строителей.
Еще одна вещь, которая приходит на ум, - это использование нескольких потоков (или threadpool) для обработки различных файлов одновременно, а затем объединить результаты в конце. Как только вы заставите программу работать "как можно быстрее", оставшимся узким местом будет доступ к дискам, и единственный способ (afaik) пройти через это-более быстрые диски (SSDs и т. д.).
Поскольку вы используете bufferedReader, почему вам нужно явно читать весь файл? Я определенно не буду использовать split, если вы ищете скорость, помните, что он должен оценивать регулярное выражение каждый раз, когда вы его запускаете.
Попробуйте что-то вроде этого для вашего внутреннего цикла (обратите внимание, я не компилировал это или не пытался запустить его):
StringBuilder sb = null; String delimiters = " .,\t"; //Build out all your word delimiters in a string here for(int nextChar = br.read(); nextChar >= 0; nextChar = br.read()) { if(delimiters.indexOf(nextChar) < 0) { if(sb == null) sb = new StringBuilder(); sb.append((char)(nextChar)); } else { if(sb != null) { //Add sb.toString() to your map or increment it sb = null; } } }Вы можете попробовать использовать буферы разного размера явно, но вы, вероятно, не получите улучшения производительности по сравнению с этим.
Один очень простой подход, который использует минимальное пространство кучи и должен быть (почти) таким же быстрым, как и все остальное, как
int c; final String SEPARATORS = " \t,.\n"; // extend as needed final StringBuilder word = new StringBuilder(); while( ( c = fileInputStream.read() ) >= 0 ) { final char letter = (char) c; if ( SEPARATORS.indexOf(letter) < 0 ) { word.append(letter); } else { processWord( word.toString() ); word.setLength( 0 ); } }Расширить для большего количества символов разделителя по мере необходимости, возможно, использовать многопоточность для обработки нескольких файлов одновременно, пока disc IO не станет бутылочным горлышком...
Comments