1

ハードドライブからそれぞれ〜10,000行の〜1000ファイルを読み取る基本的な方法があります。また、ユーザーのすべての「説明語」を含む call の配列が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は、1 人のユーザーのすべてのツイートの単語頻度をカウントします。メソッドは次のとおりです。

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

wordCountメソッド addValuesは、巨大な 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

次の 2 つのことが思い浮かびます。

  • 正規表現を使用して分割を行うString.split() を使用しています。それは完全に特大です。代わりに、Apache StringUtils の多くの splitXYZ() メソッドの 1 つを使用してください。
  • おそらく、非常に巨大なハッシュ マップを作成しているでしょう。非常に大きなハッシュ マップがある場合、ハッシュの衝突により、ハッシュ マップの機能が大幅に遅くなります。これは、より広く分散されたハッシュ値を使用することで改善できます。ここで例を参照してください: Java HashMap パフォーマンスの最適化 / 代替
于 2012-05-22T18:33:46.053 に答える
1

1 つの提案 (それによってどれだけの改善が得られるかはわかりません) は、curTweet変更されていない観察に基づいています。コピーを作成する必要はありません。いえ

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

で置き換えることができます

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

または、配列を直接使用することもできます (これはわずかに高速になります)。いえ

String[] currTweet = removeUnwantedStrings(tweet);

また、

word.equals(null)

falseのコントラクトの定義により、は常にですequals。null チェックの正しい方法は次のとおりです。

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

さらに、これを行う場合、null-pointer-exception try-catch は必要ありません。例外処理は発生するとコストがかかるため、単語配列が多くの null を返す傾向がある場合、コードの速度が低下する可能性があります。

より一般的には、これはアドホックに最適化するものを探すのではなく、コードをプロファイリングし、実際のボトルネックがどこにあるか (ボトルネックがある場合) を把握する必要があるケースの 1 つです。

于 2012-05-22T18:24:45.673 に答える
1

さらにいくつかの最適化から得られます。

  • String.split は、入力正規表現 (文字列形式) を毎回パターンに再コンパイルします。単一のstatic final Pattern ptnTab = Pattern.compile( "\\t" ), ptnEquals = Pattern.compile( "=" );and 呼び出しが必要です (例: ptnTab.split( str ). 結果として得られるパフォーマンスは、StringTokenizer に近いはずです。
  • word.equals( "" ) || word.equals( null ). ここには多くの無駄なサイクルがあります。実際にヌル ワードが表示されている場合は、非常にコストのかかる NPE をキャッチしています。上記の @trutheality からの応答を参照してください。
  • 発生するすべてのサイズ変更を回避するために、非常に大きな初期容量で HashMap を割り当てる必要があります。
于 2012-05-22T21:19:15.613 に答える
0

Java の代わりに db を使用することを考えましたか。db ツールを使用すると、テーブル内の DB に付属するデータロード ツールを使用してデータをロードし、そこからセット処理を行うことができます。フィールドが「'」や「:」などの一般的な区切り記号で区切られていないため、テーブルにデータをロードすることが課題の 1 つです。

于 2012-05-22T18:36:36.570 に答える
0

このように書き直しaddValuesて高速化することもできます - いくつかのメモ:

  • 私はコードをテストしていませんが、あなたのものと同等だと思います。
  • 私はそれが速いことをテストしていません(しかし、そうでなければ驚くでしょう)
  • コードでそれらを交換しない場合、wordsCountはtempよりも大きいと想定しています
  • また、すべてのHashMaps をMaps に置き換えました。これにより、違いはありませんが、後でコードを簡単に変更できます

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 に答える
0

split() は「高速」ではない正規表現を使用します。代わりに StringTokenizer などを使用してみてください。

于 2012-05-22T18:26:40.123 に答える