检查List <String>是否包含唯一String的最快方法


69

基本上我有大约1,000,000个字符串,对于每个请求,我都必须检查一个String是否属于列表。

我担心性能,最好的方法是什么?ArrayList?哈希?


5
一个好的练习是尝试两个不同的列表/集合/地图,然后查看是否可以通过阅读集合的Java文档来弄清楚为什么得到不同的时间:)
willcodejavaforfood 2010年

3
为了确定您正在执行此操作,请学习如何良好地使用探查器。挂在最底层的是JDK中的jvisualvm。
托尔比约恩Ravn的安徒生

Answers:


101

最好的选择是使用,HashSet并通过contains()方法检查集合中是否存在字符串。建立HashSet可以通过使用Object方法hashCode()和进行快速访问equals()HashSet状态的Javadoc :

此类为基本操作(添加,删除,包含和调整大小)提供了恒定的时间性能,

HashSet将对象存储在哈希存储桶中,也就是说,该hashCode方法返回的值将确定对象存储在哪个存储桶中。通过这种方式,HashSet通过该equals()方法必须执行的相等检查数量减少为仅其他对象相同的哈希桶。

为了有效地使用HashSets和HashMaps,必须符合javadoc中概述的equalshashCode合同。在这些方法的情况下已经实现了这一目的。java.lang.String


1
还有什么?它具有O(1)用于添加和包含。
安德烈亚斯·多克

谢谢@Andreas_D,我在Javadoc中添加了引号,指出它具有恒定的时间性能。
krock

13
当百万个字符串不再适合主存时,便是有趣的部分。
托尔比约恩Ravn的安徒生

11

通常,HashSet可以为您提供更好的性能,因为它不必像ArrayList一样仔细检查每个元素并进行比较,但是通常最多比较几个哈希码相等的元素。

但是,对于1M字符串,hashSet的性能可能仍然不是最佳的。许多缓存未命中会降低搜索集的速度。如果所有字符串都具有相同的可能性,那么这是不可避免的。但是,如果某些字符串比其他字符串更经常被请求,则可以将公共字符串放入一个小的hashSet中,然后在检查较大的set之前先进行检查。小哈希集的大小应适合缓存(例如,最多几百个K)。然后,对较小的哈希集的命中将非常快,而对较大的哈希集的命中将以受内存带宽限制的速度进行。


+1:尽管在我看来,由于字符串是单独分配的,因此与特定哈希图中的总数(总数)可能并不特别相关,因为搜索只会找到其中很小的一部分。更相关的可能是字符串本身中char数组的实际分配模式,Java程序员无论如何都对它们进行零控制(这是一件好事)。
劳伦斯·多尔

@Software Monkey-目的是通过将最常搜索的字符串放入其自己的地图中,该地图的点击率很高。较小的具有频繁使用的字符串的哈希图将比较大的映射具有更高的缓存命中率,因为每个高速缓存行在映射支持数组中将对应于几个频繁使用的字符串。当然,正如您所说,这对字符串本身的分配没有帮助。如果这是一个问题,则首先分配最常见的字符串可能会更好地使用缓存,因为VM可能会从堆的同一区域进行分配。
mdma

8

在继续之前,请考虑以下事项:为什么要担心性能?这张支票多久发出一次?

至于可能的解决方案:

  • 如果列表已经排序,则可以使用java.util.Collections.binarySearch提供与相同的性能特征的列表java.util.TreeSet

  • 否则,可以将java.util.HashSetthat用作O(1)的性能特征。请注意,为尚未计算的字符串计算哈希码是使用m =的O(m)运算string.length()。还请记住,哈希表只有在达到给定的负载因子后才能正常工作,即哈希表将比纯列表使用更多的内存。HashSet使用的默认加载因子为.75,这意味着内部用于1e6对象的HashSet将使用具有1.3e6条目的数组。

  • 如果HashSet对您不起作用(例如,由于存在很多哈希冲突,由于内存紧张或有很多插入),则可以考虑使用Trie。在Trie中查找具有最坏情况的复杂度O(m),其中m = string.length()。Trie也有一些额外的好处,可能对您有用:例如,它可以为您提供最适合搜索字符串的条件。但是请记住,最好的代码就是没有代码,因此只有在收益超过成本的情况下,才推出自己的Trie实现。

  • 如果您要进行更复杂的查询,例如考虑匹配子字符串或正则表达式,请考虑使用数据库。


9
-1:他担心性能,因为(a)具有庞大的数据集,并且(b)任何精打细算的1/2方式像样的程序员都应始终考虑算法或数据结构的性能特征是否适合该任务。
劳伦斯·多尔

5

我会使用Set,在大多数情况下HashSet都可以。


1
krock的答案在将OP推到最佳解决方案方面稍微好一点:TreeSet具有O(log2(N))性能,而HashSet理想地具有O(1)。
卡尔·斯莫特里奇

@Carl,假设equals和hashCode()均为O(1),即不考虑字符串长度。
托尔比约恩Ravn的安徒生

2

有了如此众多的Strings,我立即想到了Trie。它适用于字符集(例如字母)较少和/或许多字符串的开头重叠的情况。


2

在这里进行练习是我的结果。

private static final int TEST_CYCLES = 4000;
private static final long RAND_ELEMENT_COUNT = 1000000l;
private static final int RAND_STR_LEN = 20;
//Mean time
/*
Array list:18.55425
Array list not contains:17.113
Hash set:5.0E-4
Hash set not contains:7.5E-4
*/

我相信这些数字说明了一切。哈希集的查找时间很短,wayyyy更快。


1

如果您有大量的字符串,那么最好的机会就是使用数据库。寻找MySQL。


1
总的来说,我会同意您的意见,但是他担心查询性能-这样会增加很多开销吗?
Rup

1
添加了网络延迟,但是您可以使用SQL的全部功能。另一个考虑因素是内存-100万个字符串(每个字符32个字符)意味着约64MB的RAM。这是经典的CPU与内存的权衡。我对它进行了基准测试,然后看看。
duffymo

1
@Rup:绝对。还有很多出错的机会。如果数据适合内存(并且必须存储,因为它们已经塞满了数据),则应在内存中进行查找。
卡尔·斯莫特里兹

2
@duffymo:要对存在进行直接测试,您在数据库服务器上无法做的任何事情都无法达到contains()散列中a的性能。
卡尔·斯莫特里奇

@Carl Smotricz&Rup:我不知道。因此,谢谢您的评论。
oopbase


0

不仅对于String,还可以在需要唯一项的任何情况下使用Set

如果项目的类型是原始的或包装的,则您可能不在乎。但是,如果它是一个类,则必须重写两个方法:

  1. hashCode()
  2. 等于()

0

有时,您想检查某个对象是否在列表/集中,同时又希望对列表/集进行排序。如果您希望不使用枚举或迭代器也轻松检索对象,则可以考虑同时使用ArrayList<String>HashMap<String, Integer>。该列表由地图支持。

我最近所做的一些工作的示例:

public class NodeKey<K> implements Serializable, Cloneable{
private static final long serialVersionUID = -634779076519943311L;

private NodeKey<K> parent;
private List<K> children = new ArrayList<K>();
private Map<K, Integer> childrenToListMap = new HashMap<K, Integer>();

public NodeKey() {}

public NodeKey(Collection<? extends K> c){
    List<K> childHierarchy = new ArrayList<K>(c);
    K childLevel0 = childHierarchy.remove(0);

    if(!childrenToListMap.containsKey(childLevel0)){
        children.add(childLevel0);
        childrenToListMap.put(childLevel0, children.size()-1);
    }

    ...

在这种情况下,参数KString适合您。映射(childrenToMapList)作为键存储Strings在列表(children)中,映射值是列表中的索引位置。

使用列表和映射的原因是,您可以检索列表的索引值,而不必在上进行迭代HashSet<String>

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.