繁体   English   中英

java8并行字符串统计信息/流/映射/减少

[英]java8 parallel string statistics / stream / map/reduce

我在Java中有一个很长的字符串,并且我正在尝试获取该字符串的统计信息。

例如String s =“ afafaf”

我想获取所有现有的长度为2的子字符串的所有计数。对于上面的这个小例子,这将是:

“ af”-3“ fa”-2

另一个示例:String s =“ hsdjs”

结果:“ hs”-1“ sd”-1“ dj”-1“ js”-1

我所做的工作是在for(int i = 0; i <s.length; i ++)遍历字符串,然后迭代Map条目。

问题是那该死的慢。 我当时在想,用于并行处理的新Java8函数可能会对我有所帮助。 但不幸的是,我无法运行某件事……也许有人可以帮助我。

当前代码:

    import com.google.common.collect.HashMultiset;


    String inputString = s;
    HashMultiset<String> multi = HashMultiset.create();

    for (int i=0;i <inputString.length()-1;i++) {             
        String aktuellerString = inputString.substring(i, i+2);
        multi.add(aktuellerString);       
    }   

这是当前的配置文件: http : //fs5.directupload.net/images/160909/naadsfxi.png

谷歌番石榴库的HashMultiset的add()方法实际上花费了大部分时间。 但这是我能找到的最快的收藏。 (尝试了其他几个优化的库,包括普通的HashMap,Tie,GS Collections,gnu.trove.map.hash.THashMap;导入org.apache.commons.collections.FastHashMap等)。

这就是为什么我认为并行处理可能是加快速度的唯一方法。

更新:正如Marko所指出的,创建子字符串的成本是巨大的,即使具有多个CPU,您也可以通过避免创建子字符串的结构来做得更好。 在这种情况下,我们只有两个字符,可以将这些字符编码为int值。 在这种情况下,我们可以假设使用ASCII字符。

public static void main(String[] args) throws IOException {
    char[] chars = new char[1000000000];
    Random rand = new Random();
    for (int i = 0; i < chars.length; i++)
        chars[i] = (char) (rand.nextInt(26) + 'a');
    String s = new String(chars);

    long start = System.currentTimeMillis();
    Map<String, Integer> freq = IntStream.range(0, s.length() - 1).parallel()
            .mapToObj(i -> s.substring(i, i + 2))
            .collect(Collectors.groupingBy(w -> w, Collectors.summingInt(e -> 1)));
    long time = System.currentTimeMillis() - start;
    System.out.println("Took " + time + " ms " + freq);
}

版画

Took 8401 ms {aa=1479201, ab=1478451, ac=1479055, ...

但是,如果我们直接使用collect ,则可以使用不创建任何对象的结构。

public static void main(String[] args) throws IOException {
    char[] chars = new char[1000000000];
    Random rand = new Random();
    for (int i = 0; i < chars.length; i++)
        chars[i] = (char) (rand.nextInt(26) + 'a');
    String s = new String(chars);

    long start = System.currentTimeMillis();
    int[] freqArr = IntStream.range(0, s.length() - 1).parallel()
            .collect(() -> new int[128 * 128],
                    (arr, i) -> arr[s.charAt(i) * 128 + s.charAt(i + 1)]++,
                    (a, b) -> sum(a, b));
    Map<String, Integer> freq = new TreeMap<>();
    for (int i = 0; i < freqArr.length; i++) {
        int c = freqArr[i];
        if (c == 0) continue;
        String key = "" + (char) (i >> 7) + (char) (i & 0x7f);
        freq.put(key, c);
    }
    long time = System.currentTimeMillis() - start;
    System.out.println("Took " + time + " ms " + freq);
}

static int[] sum(int[] a, int[] b) {
    for (int i = 0; i < a.length; i++)
        a[i] += b[i];
    return a;
}

打印以下内容,速度快约20倍。

Took 404 ms {aa=1479575, ab=1480511, ac=1476255, 

这有很大的不同,因为我们正在处理小的字符串


您可以更换

for (int i=0; i < s.length;i++) { something(i) }

IntStream.range(0, s.length()).parallel().forEach(i -> { something(i) })

但是更好的解决方案是使用映射...

String s = "afafaffafafafffafaaaf";

Map<String, Long> freq = IntStream.range(0, s.length()-1).parallel() // 1
        .mapToObj(i -> s.substring(i, i + 2)) // 2
        .collect(Collectors.groupingBy(w -> w, Collectors.counting())); //3

System.out.println(freq);
  1. 并行浏览所有包含两个字符串的索引。
  2. 获取这些索引的两个字符串中的每一个。
  3. 同时按名称分组所有出现次数的计数。

版画

{ff=3, aa=2, af=8, fa=7}

关于Holger关于groupByConcurrent可能会变慢的观点,我测试了四个案例。

    long start = System.currentTimeMillis();
    Map<Integer, Long> freq = IntStream.range(0,1000000000)/*.parralel()*/
            .mapToObj(i -> i % 10)
            .collect(Collectors.groupingBy/*Concurrent*/(w -> w, Collectors.counting()));
    long time = System.currentTimeMillis() - start;
    System.out.println("Took " + time+" ms " + freq);

without parallel(), with groupingBy : Took 14156 ms {0=100000000, 1=100000000, 2=100000000, 3=100000000, 4=100000000, 5=100000000, 6=100000000, 7=100000000, 8=100000000, 9=100000000}
with parallel(), with groupingBy : Took 5581 ms {0=100000000, 1=100000000, 2=100000000, 3=100000000, 4=100000000, 5=100000000, 6=100000000, 7=100000000, 8=100000000, 9=100000000}
without parallel(), with groupingByConcurrent : Took 38218 ms {0=100000000, 1=100000000, 2=100000000, 3=100000000, 4=100000000, 5=100000000, 6=100000000, 7=100000000, 8=100000000, 9=100000000}
with parallel(), with groupingByConcurrent : Took 27619 ms {0=100000000, 1=100000000, 2=100000000, 3=100000000, 4=100000000, 5=100000000, 6=100000000, 7=100000000, 8=100000000, 9=100000000}

使用groupingBy是最好的解决方案,无论是否并行。

通过使用summingInt进一步使用Holger的注释,这再次证明更快。

long start = System.currentTimeMillis();
Map<Integer, Integer> freq = IntStream.range(0, 1000000000).parallel()
        .mapToObj(i -> i % 10)
        .collect(Collectors.groupingBy(w -> w, Collectors.summingInt(e -> 1)));
long time = System.currentTimeMillis() - start;
System.out.println("Took " + time+" ms " + freq);

版画

Took 4131 ms {0=100000000, 1=100000000, 2=100000000, 3=100000000, 4=100000000, 5=100000000, 6=100000000, 7=100000000, 8=100000000, 9=100000000}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM