用hashmap改进字频的计数

时间:2010-12-03 18:41:18

标签: java algorithm performance count hashmap

对于我的一个应用程序,必须经常调用以下函数。这个功能占用了大量的CPU,因此我想知道你是否知道如何提高性能。

代码计算四个字符组合的出现次数。在测试期间,我发现地图中的条目数大约为100. 文本的长度在100到800的范围内。初始大小为200是猜测,代码似乎是比没有指定初始大小更快地运行。但它可能不是最佳值。

private Map<String, Integer> getTetagramCount(final String text) {
    final Map<String, Integer> cipherTetagrams = new HashMap<String, Integer>(200);

    for (int i = 0; i < text.length() - 4; i++) {
        final String tet = text.substring(i, i + 4);

        final Integer count = cipherTetagrams.get(tet);
        if (count != null) {
            cipherTetagrams.put(tet, count + 1);
        } else {
            cipherTetagrams.put(tet, 1);
        }
    }

    return cipherTetagrams;
}

6 个答案:

答案 0 :(得分:11)

答案 1 :(得分:7)

您可以尝试将prefix tree (trie)作为数据结构实现,特别是如果您知道字符的范围。最多可达4级,为您提供潜在的恒定(和更快的恒定)时间。与hashmap相比,它的执行方式实际上取决于您拥有的数据。

修改

或者,如果您知道字符的范围,您可以将它们填充到更快的数据类型中。

由于您知道所有字符都在A和Z或0和9之间,因此您可以将其压缩为6位:

 public int index(String str, int startPos) {
     return 
    ((str.charAt(startPos+3) - '0') << 18) + 
    ((str.charAt(startPos+2) - '0') << 12) + 
    ((str.charAt(startPos+1) - '0') << 6) + 
     (str.charAt(startPos) - '0');
 }

 //...    
 int[] counts = new int[42*42*42*42];
 final int max = text.length() - 4;
 for ( int i = 0; i < max; i++ ) {
     counts[index(text, i)]++;
 }    

修改:更新了上面的示例以涵盖A-Z, 0-9。现在注意两件事:首先,你必须创建一个大数组,但你不需要每次都这样做(你必须每次都清除它!)。其次,这提供了对某个单词出现次数的快速查找,但是如果要迭代所有单词(比如查找实际出现的所有单词),则需要O(42^4)次。

答案 2 :(得分:4)

嗯,一个可能的选择是从使用不可变包装器类型转换为可变类型:

public final class Counter
{
    private int value;

    public int getValue()
    {
        return value;
    }

    public void increment()
    {
        value++;
    }
}

然后将您的代码更改为:

private Map<String, Counter> getTetagramCount(final String text) {
    final Map<String, Counter> cipherTetagrams = new HashMap<String, Counter>(200);

    // Micro-optimization (may well not help) - only take the
    // length and subtract 4 once
    int lastStart = text.length() - 4;
    for (int i = 0; i < lastStart; i++) {
        final String tet = text.substring(i, i + 4);

        Counter counter = cipherTetagrams.get(tet);
        if (counter == null) {
            counter = new Counter();
            cipherTetagrams.put(tet, counter);
        }
        counter.increment();
    }

    return cipherTetagrams;
}

通过这种方式,您只需“放置”与单词关联的值一次......之后就可以将其增加到位。

(如果您想使用内置类型,则可以使用AtomicInteger代替Counter。)

答案 3 :(得分:1)

除了Big-O优化(如果有的话),有一种非常简单的方法可以大大加速你的应用程序:使用一些东西而不是默认的Java API,当它涉及到非常慢时处理批次的数据。

替换:

Map<String, Counter>

使用Trove(这意味着你必须将Trove jar添加到你的项目中):

TObjectIntHashMap<String>

final Integer count = cipherTetagrams.get(tet);

使用:

final int count = cipherTetagrams.get(tet);

因为当你使用 lot 数据时,使用像Integer这样的包装器而不是原语(比如int),并使用默认的Java API是最可靠的方式来拍摄自己。

答案 4 :(得分:-1)

我甚至没有开始分析你的代码,我注意到这个方法不能在任何成员字段上运行,并且可以是静态的。静态方法总是比非静态方法执行得更好。我会在一分钟内寻找更多问题......

答案 5 :(得分:-2)

我不确定这是否会更快,但我有一种感觉。

private Map<String, Integer> getTetagramCount( final String text) {

    final List<String> list = new ArrayList<String>();

    for( int i =0; i < text.length() - 4; i++) {
        list.add( text.substring( i, i+4);
    }

    Collections.sort( list);

    Map<String, Integer> map = new HashMap<String, Integer>( list.size());
    String last = null;
    int count = 0;
    for( String tetagram : list) {
        if( tetagram != last && last != null) {
            map.put( tetagram, count);
            count = 0;
        }
        count++;
        last = tetagram;
    }
    if( tetagram != null) {
        map.put( tetagram, count);
    }
    return map;
}

完成后,根据您对地图的操作,最后可能不需要转换为地图。