【问题标题】:Improve counting algorithm performance提高计数算法性能
【发布时间】:2019-08-03 09:45:18
【问题描述】:

我需要编写需要 2 个集合的算法,List<String>MyClass[]

public class MyClass {
   String key;
   String value;
}

然后它将遍历MyClass[] 并检查它的key 是否也在List<String> 中。这里不能简单containskey有2部分,文本和数字(例如“Lorem ipsum 1990”,我们只需要检查文本部分所以这里有trimNumber方法。

 public String trimNumber(String key) {           
       String[] splitKey = key.split(" ");
       return splitKey [splitKey .length-1].matches(("\\d+(\\.\\d+)?")) ?
       key.replace(splitKey [splitKey .length-1], "").trim() : key;
    }

现在,我需要计算这场比赛的每一次出现。 最后一步,是从整个集合中找到最高出现率。

最后,我的实现

public long calculate(final List<String> list, final MyClass[] data) {
        return Arrays.stream(data)
                .map(MyClass::getKey)
                .map(Main::trimNumber)
                .filter(list::contains)
                .collect(Collectors.groupingBy(v -> v, Collectors.counting()))
                .values()
                .stream()
                .mapToLong(i -> i)
                .max()
                .orElse(1);
    }

现在的问题是,我可以简化它吗?或者改变一些东西让性能更好?那部分只是更大算法的一部分,它将对大量数据进行操作。假设每个 HTTP 请求 15 000 x MyClass[150]。所以每一秒都很重要。

【问题讨论】:

  • List.contains() 是 O(N)。首先将其转换为 HashSet。 HashSet.contains() 是 O(1)。
  • return key.replaceFirst(" ?+\\d+(\\.\\d+)?$", "");替换你的trimNumber实现,它已经在使用正则表达式,当没有匹配时它已经返回原始字符串,并且通过前置" ?+"和附加$,它将如果有空格,则限制为最后一个以空格分隔的术语。不需要split

标签: java performance java-stream


【解决方案1】:

我们可以使用Set&lt;String&gt; 特别是HashSet&lt;String&gt; 实现来查找键,而不是使用List&lt;String&gt; 来保存key。在内部,HashSet 将使用支持 HashMap 来存储数据作为 HashMap 的键。

现在通常键的查找时间是 O(1) 在最好的情况下假设没有哈希冲突,但是如果从 JDK-8 开始查找时间最多是 O(log(n)),这是因为在内部来自哈希冲突的条目链接链被转换为 LinkedListRed-Black 树,其中查找时间通过 treeify 过程是对数的。

这是上述更改的 JEP:

http://openjdk.java.net/jeps/180

本文做了一个基准测试来展示ArrayListHashSetcontains方法之间的比较:

https://www.baeldung.com/java-hashset-arraylist-contains-performance

如果可能的话,我们可以将修剪后的密钥文本存储在 MyClass 类的另一个字段中,这样Main::trimNumber 调用不会针对所有请求进行,但这也会转化为额外的内存开销:

public class MyClass {
   String key;
   String value;
   String trimmedKey; //caching the trimmed key value
}

【讨论】:

    【解决方案2】:

    我可以在您的 sn-p 中看到几个问题。


    首先。您使用Regular Expression。这通常很慢。因此,您必须将Matcher 与预编译正则表达式一起使用,甚至避免使用它。例如。喜欢这个:

    private static final Function<String, String> trimNumber = key -> {
        for (int i = key.length() - 1; i >= 0; i--) {
            char ch = key.charAt(i);
    
            if (ch == ' ')
                return key.substring(0, i);
            if (ch < '0' || ch > '9')
                return key.substring(0, i + 1);
        }
    
        return "";
    };
    

    对于简单的字符串部分,使用for loop 更容易。它比正则表达式效果更好。

    此外,Java 中的String 是不可变的,因此当您想要对字符串的一部分进行底层处理时,最好使用str.substract() 而不是str.replace("")


    第二。您使用List 进行密钥缓存。最好使用Set 来获得O(1) 性能。


    我认为您的解决方案可能如下所示:

    public static long calculate(Set<String> keys, MyClass[] data) {
        Map<String, Long> map = Arrays.stream(data)
                                      .map(MyClass::getKey)
                                      .map(trimNumber)
                                      .filter(keys::contains)
                                      .collect(Collectors.groupingBy(v -> v, Collectors.counting()));
    
        return map.values().stream().mapToLong(i -> i).max().orElse(1L);
    }
    

    【讨论】:

      猜你喜欢
      • 2011-08-06
      • 1970-01-01
      • 2022-12-11
      • 1970-01-01
      • 1970-01-01
      • 2018-08-19
      • 2021-12-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多