我正在做一个频率字典,其中我读了1000个文件,每个文件大约有1000行。我关注的方法是:
我的问题是,这不是一种非常有效的方法,我需要大约4分钟才能做到这一切。我增加了堆大小,重构代码以确保我没有做错的事情。对于这种方法,我完全确定在代码中没有任何改进。
我的赌注是,每次读取一个sentece时,都会应用一个分割,在文件和1000个文件中乘以1000个句子就是要处理的大量分割。 我的想法是,我可以将每个文件读取到一个char数组,而不是逐个读取和处理,然后每个文件只进行一次拆分。这样可以减轻拆分所需的处理时间。任何建议的实施将不胜感激。
答案 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;
}
}
}
您可以尝试明确使用不同大小的缓冲区,但是您可能无法获得相应的性能提升。