基本上,我有大约1,000,000个字符串,对于每个请求,我都需要检查一个字符串是否属于列表。
我担心性能问题,那么什么是最好的方法?ArrayList
?哈希表?
基本上,我有大约1,000,000个字符串,对于每个请求,我都需要检查一个字符串是否属于列表。
我担心性能问题,那么什么是最好的方法?ArrayList
?哈希表?
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中,并在检查大的集合之前先检查该小的集合。小型哈希集的大小应该适合缓存(例如最多几百K)。对小型哈希集的命中将非常快,而对大型哈希集的命中则受到内存带宽限制。
在进一步深入之前,请考虑这个问题:你为什么担心性能?这个检查调用的频率有多高?
至于可能的解决方案:
如果列表已经排序,则可以使用java.util.Collections.binarySearch
,其提供与java.util.TreeSet
相同的性能特征。否则,您可以使用具有O(1)性能特征的java.util.HashSet
。请注意,对于尚未计算哈希值的字符串计算哈希码是一项O(m)操作,其中m = string.length()
。还要记住,散列表只在达到给定负载因子时才有效,即散列表将使用比普通列表更多的内存。 HashSet使用的默认负载因子为0.75,这意味着内部具有1.3e6个条目的数组的HashSet将用于1e6个对象。如果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
*/
我相信数据说话。哈希集合的查找时间快得多。
如果你有大量的字符串需要处理,最好的选择是使用数据库。可以考虑使用MySQL。
contains()
函数的性能。 - Carl SmotriczArrayList<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
)将插入到列表(children
)中的字符串Strings
作为键存储,而映射值则是列表中的索引位置。不仅适用于字符串,您可以在任何需要唯一项的情况下使用Set。
如果项目的类型是基本类型或包装器类型,则可能不需要关心。但如果它是一个类,则必须重写两个方法: