Java эффективный способ обработки больших текстовых файлов - PullRequest
2 голосов
/ 27 октября 2011

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

  • BufferedReader для чтения fileByFile
  • чтение первого файла, получение первого предложения, разбиение предложения на строку массива, а затем заполнение хэш-картысо значениями из строкового массива.
  • сделайте это для всех предложений в этом файле
  • сделайте это для всех 1000 файлов

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

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

Ответы [ 6 ]

1 голос
/ 27 октября 2011

ОК, я только что реализовал POC вашего словаря. Быстро и грязно. Мои файлы содержали 868 строк каждая, но я создал 1024 копии одного и того же файла. (Это оглавление документации Spring Framework.)

Я провел тест, и это заняло 14020 мс (14 секунд!). Кстати, я запустил его из затмения, которое может немного снизить скорость.

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

В любом случае мой код не самый быстрый, который я могу написать. Я могу создать Pattern перед циклом и использовать его вместо String.split (). String.split () вызывает Pattern.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);
}
0 голосов
/ 27 октября 2011

Поскольку вы используете 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;
        }
    }
}

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

0 голосов
/ 27 октября 2011

Один очень простой подход, который использует минимальное пространство кучи и должен (почти) быть таким же быстрым, как и все остальное, как

  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 );

    }

  }

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

0 голосов
/ 27 октября 2011

Чтение файла как одной большой строки, а затем разбиение, что звучит как хорошая идея. Разделение / модификация строк может быть на удивление «тяжелой», когда дело доходит до сбора мусора. Несколько строк / предложений означают несколько строк и со всеми разбиениями это означает огромное количество строк (строки неизменяемы, поэтому любое изменение в них фактически создаст новую строку или несколько строк) ... это создает много мусора для сборщик мусора может стать узким местом (при меньшей куче все время достигается максимальный объем памяти, запускается сборщик мусора, который может потребовать очистки сотен тысяч или миллионов отдельных объектов String) .

Конечно, не зная вашего кода, это всего лишь дикая догадка, но в свое время я получил старые Java-программы командной строки (это был алгоритм алгоритма, генерирующий огромный SVG-файл), чтобы уменьшите значение с 18 секунд до менее 0,5 секунд, просто изменив обработку строк, чтобы использовать StringBuffers / Builders.

Другая вещь, которая приходит на ум, - это использование нескольких потоков (или пула потоков) для одновременной обработки различных файлов, а затем объединения результатов в конце. Как только вы заставите программу работать «как можно быстрее», оставшимся узким местом будет доступ к диску, и единственный способ (afaik) преодолеть более быстрые диски (SSD и т. Д.).

0 голосов
/ 27 октября 2011

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

Вы хотите:

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

Разделение, вероятно, довольно дорого, поскольку оно должно интерпретировать выражение каждый раз.

0 голосов
/ 27 октября 2011

Если вас не волнует, что содержимое находится в разных файлах, я бы порекомендовал вам подход. Считайте все файлы и все строки в память (строку или массив символов и т. Д.), А затем выполните 1 разбиение и заполнение хеша на основе одной строки / набора данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...