好的,这可能是一个超级小问题,但是 我有点困惑,渴望听到你能告诉我的事情。
我有一个添加了大约500万个长片的ArrayList。这些long是从一个大的csv文件中计算主键(连接字符串)的哈希值。
现在我想检查唯一性并在列表中循环:
for(int i=0;i<hashArrayList.size();i++)
{
long refValue = hashArrayList.get(i)
for(int j=i+1;j<hashArrayList.size();j++)
{
if(refValue == hashArrayList.get(j))
--> UNIQUENESS VIOLATION, now EXPLODE!!
}
}
这种方式需要HOURS。
现在关于Hashset,它本身不允许重复。 hashset.addAll(hashArrayList)需要4秒!同时消除/不为此列表添加5个mio元素的重复项。
它是如何做到的? 并且:我的ArrayList循环是如此愚蠢吗?
答案 0 :(得分:3)
你正在做一个完全不同的比较。
使用ArrayList,您有一个嵌套的 for 循环,使其成为O(n^2)
。
但是使用HashSet,您不会进行任何循环,只需向n
添加O(n)
个元素即可。在内部,HashSet使用HashMap
,其键是列表的各个元素,值是静态 对象 。
HashSet
(Java 8)的源代码
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
addAll
来电add
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
因此,最终将所有对象(此处为 long )插入到HashMap中,该HashMap提供恒定的时间性能 1
1 来自HashMap的javadoc(强调我的)
此实现为基本操作(get和put)提供了恒定时间性能,假设散列函数在桶之间正确地分散元素
答案 1 :(得分:1)
基于散列的集合不需要循环来检查是否存在具有相同键的元素。
想象一下,你有1000个对象X.在你的情况下,你每次添加东西时都会遍历列表。
基于散列的集合计算对象的散列,查看是否存在具有相同散列的其他元素,然后只需检查其中一个元素是否等于新元素。如果你有一个好的哈希函数,它返回唯一元素的唯一哈希值,你只需要计算一个数字。
当然,如果您只是说“我很懒,而且我使用返回1覆盖我的hashCode方法”,那么除了哈希集合开销之外,您将获得相同数量的比较。
示例:想象一下,您有以下HashSet:
HashSet: [[obj1], [null], [null], [null], [obj2, obj3, obj4]]
如您所见,基本结构(可以)如下所示:包含具有实际条目的其他数据结构的数组。现在,如果你将一个obj5放入HashSet,它将调用obj5.hashCode()。基于此,它将计算该obj的外部索引。让我们说它是4:
HashSet: [[obj1], [null], [null], [null], [obj2, obj3, obj4]] ^ obj5
现在我们有三个具有相同索引的其他对象。是的,我们需要一个循环来检查,它们中的一些是否等于新的obj5,但如果你有一个包含数百万个条目的更大的HashSet,与一些元素的比较比与所有元素相比要快得多。这是基于散列的集合的优势。
答案 2 :(得分:0)
此外,你在一个循环中使用循环,这使得复杂度为O(n ^ 2),这比hashmap使用的效率低。