基本上我有大约1,000,000个字符串,对于每个请求,我必须检查字符串是否属于列表。
我担心性能,所以最好的方法是什么? ArrayList
?散列?
答案 0 :(得分:94)
最好的办法是使用HashSet
并通过contains()
方法检查集合中是否存在字符串。 HashSet是通过使用Object方法hashCode()
和equals()
来快速访问的。 HashSet
状态的Javadoc:
此类为基本操作(添加,删除,包含和大小)提供恒定的时间性能,
HashSet stores objects in hash buckets也就是说hashCode
方法返回的值将决定对象存储在哪个存储桶中。这样,相等的数量检查HashSet
通过equals()
方法执行的操作简化为同一个散列桶中的其他对象。
要有效地使用HashSets和HashMaps,您必须遵守equals
和hashCode
合同概述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,您还可以使用设置来处理任何需要独特项目的情况。
如果项目类型是原始或包装,您可能不在乎。但如果它是一个类,则必须覆盖两个方法:
答案 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>
进行迭代。