java高效的方法来处理大文本文件

时间:2011-10-27 16:55:15

标签: java file optimization bufferedreader

我正在做一个频率字典,其中我读了1000个文件,每个文件大约有1000行。我关注的方法是:

  • BufferedReader读取fileByFile
  • 读取第一个文件,获取第一个句子,将句子拆分为数组字符串,然后用字符串数组中的值填充一个hashmap。
  • 为该文件中的所有句子执行此操作
  • 为所有1000个文件执行此操作

我的问题是,这不是一种非常有效的方法,我需要大约4分钟才能做到这一切。我增加了堆大小,重构代码以确保我没有做错的事情。对于这种方法,我完全确定在代码中没有任何改进。

我的赌注是,每次读取一个sentece时,都会应用一个分割,在文件和1000个文件中乘以1000个句子就是要处理的大量分割。 我的想法是,我可以将每个文件读取到一个char数组,而不是逐个读取和处理,然后每个文件只进行一次拆分。这样可以减轻拆分所需的处理时间。任何建议的实施将不胜感激。

6 个答案:

答案 0 :(得分:1)

好的,我刚刚实现了你字典的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);
}

答案 1 :(得分:0)

如果您不关心内容是否在不同的文件中,我会采用您推荐的方法。将所有文件和所有行读入内存(字符串或char数组,无论如何),然后根据一个字符串/数据集进行1分割和散列填充。

答案 2 :(得分:0)

如果我了解你在做什么,我认为你不想使用字符串,除非你访问你的地图。

你想要:

循环浏览文件     将每个文件读入1024个缓冲区     处理缓冲区寻找单词结束字符     从字符数组创建一个String     检查你的地图     如果找到,请更新您的计数,如果没有,请创建一个新条目     到达缓冲区末尾时,从文件中获取下一个缓冲区     最后,循环到下一个文件

Split可能非常昂贵,因为每次都必须解释表达式。

答案 3 :(得分:0)

将文件作为一个大字符串读取然后拆分听起来像个好主意。在垃圾收集方面,字符串拆分/修改可能会非常“重”。多行/句子意味着多个字符串和所有拆分它意味着大量的字符串(字符串是不可变的,因此对它们的任何更改实际上将创建一个新的字符串或多个字符串)...这会产生大量的垃圾收集,并且垃圾收集可能成为瓶颈(使用较小的堆,始终达到最大内存量,启动垃圾收集,这可能需要清理数十万或数百万个单独的String对象)

当然,在不知道你的代码的情况下,这只是一个疯狂的猜测,但在当天,我得到了一个旧的命令行Java程序(它是一个产生巨大SVG文件的图形算法)运行时间到只需修改字符串处理以使用StringBuffers / Builders,就可以从大约18秒减少到不到0.5秒。

另一件令人想到的事情是使用多个线程(或线程池)同时处理不同的文件,然后在最后组合结果。一旦你让程序“尽可能快地”运行,剩下的瓶颈就是磁盘访问,唯一的方法(afaik)就是更快的磁盘(SSD等)。

答案 4 :(得分:0)

一种使用最小堆空间的非常简单的方法,应该(几乎)和其他任何东西一样快

  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成为瓶颈......

答案 5 :(得分:0)

由于您使用的是bufferedReader,为什么需要显式读取整个文件?如果你追求速度,我绝对不会使用拆分,请记住,每次运行它时都必须评估正则表达式。

尝试这样的内循环(注意,我没有编译它或试图运行它):

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

您可以尝试明确使用不同大小的缓冲区,但是您可能无法获得相应的性能提升。