我该如何优化此代码?

时间:2010-11-03 16:21:40

标签: java optimization build-time

我目前的项目让我们在Java中使用TreeSet和TreeMap,从文本文件中读入10514个Song元素的输入数组。每首歌曲包含艺术家,标题和抒情字段。该项目的目的是使用集合和地图对歌词进行快速搜索。

首先,我迭代输入Song数组,访问lyrics字段并创建一个Scanner对象,使用此代码迭代歌词:commonWords是一个不应该是键的单词TreeSet,{ {1}}是歌曲的整体地图。

lyricWords

为了构建songSet,我使用以下代码:

public void buildSongMap() {
    for (Song song:songs) {
        //method variables
        String currentLyrics= song.getLyrics().toLowerCase(); 
        TreeSet<Song> addToSet=null;
        Scanner readIn= new Scanner(currentLyrics);
        String word= readIn.next();

        while (readIn.hasNext()) {

            if (!commonWords.contains(word) && !word.equals("") && word.length()>1) {
                if (lyricWords.containsKey(word)) {
                    addToSet= lyricWords.get(word);
                    addToSet.add(song);
                    word=readIn.next();
                } else 
                    buildSongSet(word);

            } else 
                word= readIn.next();
        }

    }

现在,由于buildSongSet是从循环内部调用的,因此创建映射的时间为N ^ 2。当输入数组是4首歌曲时,搜索速度非常快,但是当使用完整的10514元素阵列时,在具有6 GiB RAM的2.4GHz机器上构建地图可能需要超过15分钟。我该怎么做才能使这段代码更有效率?不幸的是,减少输入数据不是一种选择。

4 个答案:

答案 0 :(得分:6)

看起来你的buildSongSet正在做多余的工作。你的块:

if (lyricWords.containsKey(word)) {
    addToSet= lyricWords.get(word);
    addToSet.add(song);
    word=readIn.next();
} 

将歌曲添加到现有集合中。因此,当您找到一个您不知道的单词时,只需添加一首歌即可。将buildSongSet更改为:

public void buildSongSet(String word, Song firstSongWithWord) {     
    TreeSet<Song> songSet= new TreeSet<Song>();
    songSet.add(firstSongWithWord);
    lyricWords.put(word, songSet);
    System.out.println("Word added "+word);
}

如果剩下的歌曲中包含该单词,那么剩下的剩余歌曲将从第一个代码块添加到该歌曲集中。我认为这应该有效。

编辑刚刚看到这是作业...所以删除了HashSet建议..

好的..所以假设你有这些歌曲的顺序与歌词:

  • Song 1 - foo
  • Song 2 - foo bar
  • Song 3 - foo bar baz

Song 1将看到foo不包含lyricWords,因此它将调用buildSongSet并为foo创建一个set。它会将自己添加到包含foo的集合中。

歌曲2将看到foo在lyricWords中,并将其自身添加到集合中。它会看到bar不在集合中,并创建一个集合并自行添加。自从第一次看到这个词出现在歌曲2中以来,它不需要遍历以前的歌曲。

Song 3遵循相同的逻辑。

您可以尝试优化代码的另一件事是找出一种不处理歌词中重复单词的方法。如果你的歌词是foo foo foo foo bar bar bar foo bar那么你将会做很多不必要的检查。

编辑也看到rsp's answer - 那里有额外的加速,但是大的加速正在摆脱内循环 - 很高兴现在已经下降到15秒。

答案 1 :(得分:4)

imho不需要整个buildSongSet()方法,因为你的主循环已经通过单词将歌曲添加到集合中。你唯一缺少的是为一个新单词添加一个集合,例如:

if (lyricWords.containsKey(word)) {
    addToSet= lyricWords.get(word);
} else {
    addToSet = new TreeSet();
    lyricWords.put(word, addToSet);
}
addToSet.add(song);

你没有解决的一个问题是歌曲最终被多次添加到集合中,因为歌曲中每个单词都会出现。

另一个问题是,如果一首歌只包含一个单词,则根本不添加它!最好先检查一下情况:

String word = null;
while (readIn.hasNext()) {
    word = readIn.next();

您的条件是进行一次检查太多(空字符串的长度为&lt; 1),并且交换支票也可以加快速度:

if (word.length() > 1 && !commonWords.contains(word)) {

答案 2 :(得分:3)

请尝试将TreeSet更改为HashSet。我无法看到你从哪里获得TreeSet的好处。

答案 3 :(得分:0)

如果你想要一种非常可扩展的,简单的方法来解决这个问题,那么性能就会达到几毫秒的顺序。考虑lucene http://lucene.apache.org/

请参阅我的答案,例如索引和搜索 How do I index and search text files in Lucene 3.0.2?