我编写了以下函数来解析一个大文本文件(大约2 GB) 逐行进入Map,有效地计算每个单词的出现次数。我只对单词感兴趣(小写以避免条目重复),没有标点符号或空格。然而,在大文件上执行以下代码大约需要3分钟。我想知道为什么以及是否有办法加快速度。
import java.util.*;
public class Stream {
Map<String, Integer> map = new HashMap();
public void getLines() {
try (BufferedReader fileReader = new BufferedReader(new FileReader("resources/hugeFile"))) {
String line ;
while ((line = fileReader.readLine()) != null) {
String[] words = line.toLowerCase().replaceAll("[^a-z ]", "").split("\\s+");
for (int i = 0; i < words.length; i++) {
if (map.get(words[i]) == null) {
map.put(words[i], 1);
}
else {
int newValue = Integer.valueOf(String.valueOf(map.get(words[i])));
newValue++;
map.put(words[i], newValue);
}
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
答案 0 :(得分:2)
首先,如果您认真对待优化,则必须衡量效果。因为很多&#34;改进&#34;这似乎是&#34;改进&#34;可能证明不会带来任何甚至恶化表现。在许多情况下,编译器比人类更好地优化代码。所以你必须做基准测试,请看下面的问题:
我在下面发布了两个代码草图。这些只是草图,给出一个粗略的想法。我既没有测试它们也没有基准测试。
一个提示是您访问地图太多了。您可以使用map.get
进行检查,然后使用map.put
有条件地设置值。您可以改用putIfAbsent
或computeIfAbsent
。此外,您可以改进增加现有值的方式。在这种情况下,我使用可变AtomicInteger
代替不可变Integer
。所以我建议如下:
Map<String, AtomicInteger> map = new HashMap<>();
Consumer<String> countWords = word -> map.computeIfAbsent(word, (w) -> new AtomicInteger(0)).incrementAndGet();
try (BufferedReader fileReader = new BufferedReader(new FileReader("resources/hugeFile"))) {
String line;
while ((line = fileReader.readLine()) != null) {
splitAndConsumeWords(line, countWords);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
接下来,您使用line.toLowerCase().replaceAll("[^a-z ]", "").split("\\s+")
将字符串转换为小写,仅保留字母和空格并将字符串拆分为单词。没有基准测试我肯定不知道它,但我怀疑这可能是代码中最耗时的操作。没有正则表达式重写它并不是什么大不了的事。您所需要的只是遍历字符串的字符,将它们转换为小写,附加到当前单词或扔掉。以下是我的表现。
我创建一个数组,将每个字符映射到替换字符。 a-z
或空格的相同字符,A-Z
的小写字母。所有其他字符都将映射到0
,这意味着它们应该被丢弃:
private static char[] ONLY_LETTERS_TO_LOWERCASE = new char[65535];
static {
ONLY_LETTERS_TO_LOWERCASE[' '] = ' ';
for (char c = 'a'; c <= 'z'; c++) {
ONLY_LETTERS_TO_LOWERCASE[c] = c;
}
for (char c = 'A'; c <= 'Z'; c++) {
ONLY_LETTERS_TO_LOWERCASE[c] = Character.toLowerCase(c);
}
}
然后,您只需查找每个角色的替换词并构建单词:
public static void splitAndConsumeWords(String line, Consumer<String> wordsConsumer) {
char[] characters = line.toCharArray();
StringBuilder sb = new StringBuilder(16);
for (int index = 0; index < characters.length; index++) {
char ch = characters[index];
char replacementCh = ONLY_LETTERS_TO_LOWERCASE[ch];
// If we encounter a space
if (replacementCh == ' ') {
// And there is a word in string builder
if (sb.length() > 0) {
// Send this word to the consumer
wordsConsumer.accept(sb.toString());
// Reset the string builder
sb.setLength(0);
}
} else if (replacementCh != 0) {
sb.append(replacementCh);
}
}
// Send the last word to the consumer
if (sb.length() > 0) {
wordsConsumer.accept(sb.toString());
}
}
ONLY_LETTERS_TO_LOWERCASE
映射表的替代方法是if
语句,如:
if (ch >= 'a' && ch <= 'z' || ch == ' ') {
replacementCh = ch;
} else if (ch >= 'A' && ch <= 'Z') {
replacementCh = Character.toLowerCase(ch);
}
else {
replacementCh = 0;
}
我不确定什么会更好用,我认为在数组中查找必须更快,但我不确定。这就是您最终需要进行基准测试的原因。