在java中收集符号出现的最快方法是什么

时间:2016-08-07 11:38:16

标签: java arrays performance arraylist hashmap

我的目标是创建一个函数来计算某些符号(字符)的出现。 一个int ID赋予我需要计算的每个字符。 这组字符是有限的,我从一开始就知道它。 所有线条仅包含来自给定集合的字符。 该功能处理线条的增加。 我的探查器总是显示收集统计数据的功能是最慢的(97%)尽管程序做了很多其他事情。 首先我使用了HashMap和这样的代码:

    occurances = new HashMap<>();
    for (int symbol : line) {
        Integer amount = 1;
        if (occurances.containsKey(symbol)) {
            amount += occurances.get(symbol);
        }
        occurances.put(symbol, amount);
    }

探查器显示hashMap.put占用97%的处理器

然后我尝试用创建的一次ArrayList替换它: 并且优化它是一个小点(线总是长于1个字符),但它仍然非常慢。

    int symbol = line[0];
    occurances.set(symbol, 1);

    for (int i = 1; i < length; i++) {
        symbol = line[i];
        occurances.set(symbol, 1 + occurances.get(symbol));
    }

如果有人有更好的想法如何以更好的表现来解决这个任务,那么你的帮助就会非常有用。

5 个答案:

答案 0 :(得分:2)

根据建议here,您可以尝试做类似

的事情
List<Integer> line = //get line as a list;
Map<Integer, Long> intCount = line.parallelStream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

答案 1 :(得分:1)

您可以将char直接转换为int并将其用作索引

for (i=0; ; i++){
    occurences[(int)line[i]]++;
}

答案 2 :(得分:1)

很可能没有参数化HashMap会导致很多性能问题。

我要做的是创建一个名为IntegerCounter的类。查看AtomicIntegerhttp://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/atomic/AtomicInteger.java)代码并从那里复制所有内容,除了使其成为Atomic的代码。使用IntegerCounter并递增它的单个实例应该可以节省大量的垃圾回收。

使用new Integer(x)进行密钥查找应允许转义分析自动对其进行垃圾收集。

HashMap<Integer, IntegerCounter> occurances;

// since the set of characters are already known, add all of them here with an initial count of 0

for (int i = 0; i < length; i++) {
    occurances.get(new Integer(line[i])).incrementAndGet();
}

答案 3 :(得分:1)

在大多数循环迭代的代码中,您将在Map中查找条目3次:

1

occurances.containsKey(symbol)

2

occurances.get(symbol);

3

occurances.put(symbol, amount);

这不仅仅是需要,您可以简单地使用get返回null的事实来将此改进为2次查找:

Integer currentCount = occurances.get(symbol);
Integer amount = currentCount == null ? 1 : currentCount + 1;
occurances.put(symbol, amount);

此外,通过使用Integer,需要经常创建新的Integer个对象(一旦超过127或用于缓存值的上限),这会减少性能

此外,由于您在分析数据之前就知道了字符集,因此您可以插入0 s(或等效的)作为所有字符的值,如果映射已经在映射中,则无需检查。

以下代码使用包含int count字段的辅助类来代替存储数据,这允许在不进行装箱/取消装箱转换的情况下递增值。

class Container {
    public int count = 0;
}

int[] symbolSet = ...
Map<Integer, Container> occurances = new HashMap<>();
for (int s : symbolSet) {
    occurances.put(s, new Container());
}

for (int symbol : line) {
    occurances.get(symbol).count++;
}

同样使用不同的数据结构也可以提供帮助。我想到的是Perfect Hashing或将数据存储在与Map不同的数据结构中。但是,我建议使用ArrayList数组,而不是使用int[],因为这不需要任何方法调用,也不需要对Integer进行装箱/取消装箱转换。在计算频率后,数据仍然可以转换为更合适的数据结构。

答案 4 :(得分:1)

您可以尝试这样的事情:

public class CharCounter {

    final int max;
    final int[] counts;

    public CharCounter(char max) {
        this.max = (int) max;
        counts = new int[this.max + 1];
    }

    public void addCounts(char[] line) {
        for (int symbol : line) {
            counts[symbol]++;
        }
    }

    public Map<Integer, Integer> getCounts() {
        Map<Integer, Integer> countsMap = new HashMap<>();
        for (int symbol = 0; symbol < counts.length; symbol++) {
            int count = counts[symbol];
            if (count > 0) {
                countsMap.put(symbol, count);
            }
        }
        return countsMap;
    }
}

这使用数组来保持计数并使用char本身作为数组的索引 这消除了检查地图是否包含给定密钥等的需要。它还消除了对字符进行自动装箱的需要。

性能比较显示大约20倍的加速:

public static final char MIN = 'a';
public static final char MAX = 'f';

private static void count1(Map<Integer, Integer> occurrences, char[] line) {
    for (int symbol : line) {
        Integer amount = 1;
        if (occurrences.containsKey(symbol)) {
            amount += occurrences.get(symbol);
        }
        occurrences.put(symbol, amount);
    }
}

private static void count2(CharCounter counter, char[] line) {
    counter.addCounts(line);
}

public static void main(String[] args) {
    char[] line = new char[1000];
    for (int i = 0; i < line.length; i++) {
        line[i] = (char) ThreadLocalRandom.current().nextInt(MIN, MAX + 1);
    }

    Map<Integer, Integer> occurrences;
    CharCounter counter;

    // warmup
    occurrences = new HashMap<>();
    counter = new CharCounter(MAX);
    System.out.println("Start warmup ...");
    for (int i = 0; i < 500_000; i++) {
        count1(occurrences, line);
        count2(counter, line);
    }
    System.out.println(occurrences);
    System.out.println(counter.getCounts());
    System.out.println("Warmup done.");


    // original method
    occurrences = new HashMap<>();
    System.out.println("Start timing of original method ...");
    long start = System.nanoTime();
    for (int i = 0; i < 500_000; i++) {
        count1(occurrences, line);
    }
    System.out.println(occurrences);
    long duration1 = System.nanoTime() - start;
    System.out.println("End timing of original method.");
    System.out.println("time: " + duration1);


    // alternative method
    counter = new CharCounter(MAX);
    System.out.println("Start timing of alternative method ...");
    start = System.nanoTime();
    for (int i = 0; i < 500_000; i++) {
        count2(counter, line);
    }
    System.out.println(counter.getCounts());
    long duration2 = System.nanoTime() - start;
    System.out.println("End timing of alternative method.");
    System.out.println("time: " + duration2);

    System.out.println("Speedup: " + (double) duration1 / duration2);
}

<强>输出:

Start warmup ...
{97=77000000, 98=82000000, 99=86500000, 100=86000000, 101=80000000, 102=88500000}
{97=77000000, 98=82000000, 99=86500000, 100=86000000, 101=80000000, 102=88500000}
Warmup done.
Start timing of original method ...
{97=77000000, 98=82000000, 99=86500000, 100=86000000, 101=80000000, 102=88500000}
End timing of original method.
time: 7110894999
Start timing of alternative method ...
{97=77000000, 98=82000000, 99=86500000, 100=86000000, 101=80000000, 102=88500000}
End timing of alternative method.
time: 388308432
Speedup: 18.31249185698857

此外,如果添加-verbose:gc JVM标志,您可以看到原始方法需要进行相当多的垃圾收集,而替代方法并不需要任何方法。