基本上我有大约1,000,000个字符串,对于每个请求,我都必须检查一个String是否属于列表。
我担心性能,最好的方法是什么?ArrayList
?哈希?
基本上我有大约1,000,000个字符串,对于每个请求,我都必须检查一个String是否属于列表。
我担心性能,最好的方法是什么?ArrayList
?哈希?
Answers:
最好的选择是使用,HashSet
并通过contains()
方法检查集合中是否存在字符串。建立HashSet可以通过使用Object方法hashCode()
和进行快速访问equals()
。HashSet
状态的Javadoc :
此类为基本操作(添加,删除,包含和调整大小)提供了恒定的时间性能,
HashSet将对象存储在哈希存储桶中,也就是说,该hashCode
方法返回的值将确定对象存储在哪个存储桶中。通过这种方式,HashSet
通过该equals()
方法必须执行的相等检查数量减少为仅其他对象相同的哈希桶。
为了有效地使用HashSets和HashMaps,必须符合javadoc中概述的equals
和hashCode
合同。在这些方法的情况下已经实现了这一目的。java.lang.String
通常,HashSet可以为您提供更好的性能,因为它不必像ArrayList一样仔细检查每个元素并进行比较,但是通常最多比较几个哈希码相等的元素。
但是,对于1M字符串,hashSet的性能可能仍然不是最佳的。许多缓存未命中会降低搜索集的速度。如果所有字符串都具有相同的可能性,那么这是不可避免的。但是,如果某些字符串比其他字符串更经常被请求,则可以将公共字符串放入一个小的hashSet中,然后在检查较大的set之前先进行检查。小哈希集的大小应适合缓存(例如,最多几百个K)。然后,对较小的哈希集的命中将非常快,而对较大的哈希集的命中将以受内存带宽限制的速度进行。
在继续之前,请考虑以下事项:为什么要担心性能?这张支票多久发出一次?
至于可能的解决方案:
如果列表已经排序,则可以使用java.util.Collections.binarySearch
提供与相同的性能特征的列表java.util.TreeSet
。
否则,可以将java.util.HashSet
that用作O(1)的性能特征。请注意,为尚未计算的字符串计算哈希码是使用m =的O(m)运算string.length()
。还请记住,哈希表只有在达到给定的负载因子后才能正常工作,即哈希表将比纯列表使用更多的内存。HashSet使用的默认加载因子为.75,这意味着内部用于1e6对象的HashSet将使用具有1.3e6条目的数组。
如果HashSet对您不起作用(例如,由于存在很多哈希冲突,由于内存紧张或有很多插入),则可以考虑使用Trie。在Trie中查找具有最坏情况的复杂度O(m),其中m = string.length()
。Trie也有一些额外的好处,可能对您有用:例如,它可以为您提供最适合搜索字符串的条件。但是请记住,最好的代码就是没有代码,因此只有在收益超过成本的情况下,才推出自己的Trie实现。
如果您要进行更复杂的查询,例如考虑匹配子字符串或正则表达式,请考虑使用数据库。
我会使用Set
,在大多数情况下HashSet
都可以。
在这里进行练习是我的结果。
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更快。
如果您有大量的字符串,那么最好的机会就是使用数据库。寻找MySQL。
contains()
散列中a的性能。
也许这不是您的情况所必需的,但我认为了解某些节省空间的概率算法很有用。例如Bloom过滤器。
有时,您想检查某个对象是否在列表/集中,同时又希望对列表/集进行排序。如果您希望不使用枚举或迭代器也轻松检索对象,则可以考虑同时使用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>
。