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

时间:2010-07-22 09:47:12

标签: java string performance list contains

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

我担心性能,所以最好的方法是什么? ArrayList?散列?

10 个答案:

答案 0 :(得分:94)

最好的办法是使用HashSet并通过contains()方法检查集合中是否存在字符串。 HashSet是通过使用Object方法hashCode()equals()来快速访问的。 HashSet状态的Javadoc:

  

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

HashSet stores objects in hash buckets也就是说hashCode方法返回的值将决定对象存储在哪个存储桶中。这样,相等的数量检查HashSet通过equals()方法执行的操作简化为同一个散列桶中的其他对象。

要有效地使用HashSets和HashMaps,您必须遵守equalshashCode合同概述in the javadoc。在java.lang.String的情况下,已经实现了这些方法。

答案 1 :(得分:11)

通常,HashSet会为您提供更好的性能,因为它不需要查看每个元素并进行比较,就像ArrayList那样,但通常最多只比较几个元素,其中哈希码是相等的。

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

答案 2 :(得分:8)

在进一步讨论之前,请考虑一下:你为什么担心表现?这张支票的频率是多少?

至于可能的解决方案:

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

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

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

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

答案 3 :(得分:5)

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

答案 4 :(得分:2)

如此庞大的字符串,我立即想到Trie。使用更有限的字符集(例如字母)和/或许多字符串重叠的开头时效果会更好。

答案 5 :(得分: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
*/

我相信这些数字不言自明。哈希集的查找时间更加快捷。

答案 6 :(得分:1)

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

答案 7 :(得分:1)

也许这不是你的情况所必需的,但我认为知道有一些节省空间的概率算法是有用的。例如Bloom filter

答案 8 :(得分:0)

不仅对于String,您还可以使用设置来处理任何需要独特项目的情况。

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

  1. hashCode()方法
  2. 等于()

答案 9 :(得分: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);
    }

    ...

在这种情况下,参数K将是String。地图(childrenToMapList)将Strings存储在列表中(children)作为键,地图值是列表中的索引位置。

列表和地图的原因是您可以检索列表的索引值,而无需对HashSet<String>进行迭代。