1

我有一个基本方法,它从硬盘驱动器读取 ~1000 个文件,每个文件有 ~10,000 行。另外,我有一个String调用数组,userDescription其中包含用户的所有“描述词”。我创建了一个 HashMap,其数据结构HashMap<String, HashMap<String, Integer>>对应于HashMap<eachUserDescriptionWords, HashMap<TweetWord, Tweet_Word_Frequency>>.

该文件组织为: <User=A>\t<Tweet="tweet...">\n <User=A>\t<Tweet="tweet2...">\n <User=B>\t<Tweet="tweet3...">\n ....

我的方法是:

for (File file : tweetList) {
        if (file.getName().endsWith(".txt")) {
            System.out.println(file.getName());
            BufferedReader in;
            try {
                in = new BufferedReader(new FileReader(file));
                String str;
                while ((str = in.readLine()) != null) {
                    // String split[] = str.split("\t");
                    String split[] = ptnTab.split(str);
                    String user = ptnEquals.split(split[1])[1];
                    String tweet = ptnEquals.split(split[2])[1];
                    // String user = split[1].split("=")[1];
                    // String tweet = split[2].split("=")[1];

                    if (tweet.length() == 0)
                        continue;

                    if (!prevUser.equals(user)) {
                        description = userDescription.get(user);
                        if (description == null)
                            continue;
                        if (prevUser.length() > 0 && wordsCount.size() > 0) {
                            for (String profileWord : description) {
                                if (wordsCorr.containsKey(profileWord)) {
                                    HashMap<String, Integer> temp = wordsCorr
                                            .get(profileWord);
                                    wordsCorr.put(profileWord,
                                            addValues(wordsCount, temp));
                                } else {
                                    wordsCorr.put(profileWord, wordsCount);
                                }
                            }
                        }
                        // wordsCount = new HashMap<String, Integer>();
                        wordsCount.clear();
                    }
                    setTweetWordCount(wordsCount, tweet);
                    prevUser = user;
                }
            } catch (IOException e) {
                System.err.println("Something went wrong: "
                        + e.getMessage());
            }
        }
    }

在这里,该方法setTweetWord计算单个用户的所有推文的词频。方法是:

private void setTweetWordCount(HashMap<String, Integer> wordsCount,
            String tweet) {

        ArrayList<String> currTweet = new ArrayList<String>(
                Arrays.asList(removeUnwantedStrings(tweet)));

        if (currTweet.size() == 0)
            return;

        for (String word : currTweet) {
            try {
                if (word.equals("") || word.equals(null))
                    continue;
            } catch (NullPointerException e) {
                continue;
            }

            Integer countWord = wordsCount.get(word);
            wordsCount.put(word, (countWord == null) ? 1 : countWord + 1);
        }
    }

addValues 方法检查是否wordCount有已经在巨型 HashMap wordsCorr 中的单词。如果是这样,它会增加原始 HashMap 中单词的计数wordsCorr

现在,我的问题是无论我做什么程序都很慢。我在我的服务器上运行了这个版本,它有相当好的硬件,但它已经 28 小时了,扫描的文件数量只有 450 左右。我试着看看我是否在重复做任何可能不必要的事情,我纠正了一些。但是程序仍然很慢。

此外,我已将堆大小增加到 1500m,这是我可以达到的最大值。

有什么我可能做错了吗?

谢谢您的帮助!

编辑:分析结果首先我真的要感谢你们的评论。我已经更改了程序中的一些内容。我现在已经预编译了正则表达式,而不是直接String.split()和其他优化。但是,在分析之后,我的addValues方法花费的时间最长。所以,这是我的代码addValues。有什么我应该在这里优化的吗?哦,我也startProcess稍微改变了我的方法。

  private HashMap<String, Integer> addValues(
            HashMap<String, Integer> wordsCount, HashMap<String, Integer> temp) {

        HashMap<String, Integer> merged = new HashMap<String, Integer>();

        for (String x : wordsCount.keySet()) {
            Integer y = temp.get(x);
            if (y == null) {
                merged.put(x, wordsCount.get(x));
            } else {
                merged.put(x, wordsCount.get(x) + y);
            }
        }

        for (String x : temp.keySet()) {
            if (merged.get(x) == null) {
                merged.put(x, temp.get(x));
            }
        }
        return merged;
    }

EDIT2:即使经过如此努力,程序也没有按预期运行。我做了所有“慢方法”的优化,addValues但没有奏效。所以我去了不同的路径,首先创建字典并为每个单词分配索引,然后再进行处理。让我们看看它的去向。谢谢您的帮助!

4

6 回答 6

2

想到两件事:

  • 您正在使用 String.split(),它使用正则表达式进行拆分。这完全是超大的。请改用 Apache StringUtils 中的许多 splitXYZ() 方法之一。
  • 您可能正在创建非常巨大的哈希映射。当散列图非常大时,散列冲突会使散列图函数变得更慢。这可以通过使用更广泛传播的哈希值来改善。在此处查看示例:Java HashMap 性能优化/替代
于 2012-05-22T18:33:46.053 回答
1

您将从更多优化中受益:

  • String.split 每次都将输入正则表达式(以字符串形式)重新编译为模式。你应该有一个static final Pattern ptnTab = Pattern.compile( "\\t" ), ptnEquals = Pattern.compile( "=" );和调用,例如,ptnTab.split( str )。产生的性能应该接近 StringTokenizer。
  • word.equals( "" ) || word.equals( null ). 这里有很多浪费的周期。如果您实际上看到的是空词,那么您正在捕获 NPE,这是非常昂贵的。请参阅上面@trutheality 的回复。
  • 您应该为 HashMap 分配一个非常大的初始容量,以避免所有必然发生的调整大小。
于 2012-05-22T21:19:15.613 回答
1

一个建议(我不知道你会从中得到多少改进)是基于curTweet从未修改过的观察结果。无需创建副本。IE

ArrayList<String> currTweet = new ArrayList<String>(
            Arrays.asList(removeUnwantedStrings(tweet)));

可以替换为

List<String> currTweet = Arrays.asList(removeUnwantedStrings(tweet));

或者您可以直接使用数组(这会稍微快一些)。IE

String[] currTweet = removeUnwantedStrings(tweet);

还,

word.equals(null)

总是false按照合同的定义equals。空检查的正确方法是:

if (null == word || word.equals(""))

此外,如果您这样做,您将不需要空指针异常 try-catch。异常处理在发生时代价高昂,因此如果您的单词数组倾向于返回大量空值,这可能会减慢您的代码速度。

不过,更一般地说,这是您应该分析代码并找出实际瓶颈所在(如果存在瓶颈)而不是寻找优化ad-hoc的东西的情况之一。

于 2012-05-22T18:24:45.673 回答
0

您是否考虑过使用 db 而不是 Java。使用 db 工具,您可以使用表中 DB 附带的数据加载工具加载数据,然后您可以从那里进行集合处理。我看到的一个挑战是在表中加载数据,因为字段没有用“'”或“:”等常用分隔符分隔

于 2012-05-22T18:36:36.570 回答
0

split() 使用不“快速”的正则表达式。尝试使用 StringTokenizer 或其他东西。

于 2012-05-22T18:26:40.123 回答
0

您可以像这样重写addValues以使其更快 - 一些注意事项:

  • 我没有测试过代码,但我认为它与您的代码相同。
  • 我没有测试过它是否更快(但如果不是,我会感到惊讶)
  • 我假设 wordsCount 大于 temp,如果不在代码中交换它们
  • 我还用 s 替换了所有的HashMaps,Map这对您没有任何影响,但使代码以后更容易更改

private Map<String, Integer> addValues(Map<String, Integer> wordsCount, Map<String, Integer> temp) {

    Map<String, Integer> merged = new HashMap<String, Integer>(wordsCount); //puts everyting in wordCounts

    for (Map.Entry<String, Integer> e : temp.entrySet()) {
        Integer countInWords = merged.get(e.getKey()); //the number in wordsCount
        Integer countInTemp = e.getValue();
        int newCount = countInTemp + (countInWords == null ? 0 : countInWords); //the sum
        merged.put(e.getKey(), newCount);
    }
    return merged;
}
于 2012-05-24T17:52:26.200 回答