查看Java 6的源代码,HashSet<E>
实际上是使用HashMap<E,Object>
实现的,在Set的每个条目上使用虚拟对象实例。
我认为对于条目本身的大小,浪费了4个字节(在32位机器上)。
但是,为什么还在使用?有没有理由使用它,除了更容易维护代码?
答案 0 :(得分:19)
实际上,它不只是HashSet
。 Java 6中Set
接口的所有实现都基于底层Map
。这不是必要条件;这就是实施的方式。您可以通过查看Set
的各种实现的文档来了解自己。
您的主要问题是
但是,为什么还在使用?在那儿 任何使用它的理由除了制作它 更容易维护代码?
我认为代码维护是一个很大的激励因素。因此,防止重复和膨胀。
Set
和Map
是类似的接口,因为不允许重复的元素。 (我认为Set
支持的唯一Map
不是是CopyOnWriteArraySet
,这是一个不常见的集合,因为它是不可变的。)
具体做法是:
包含否的集合 重复元素。更正式的, 集合不包含元素对e1 和e2使得e1.equals(e2)和at 大多数一个null元素。正如所暗示的那样 它的名字,这个界面模型 数学集抽象。
Set界面增加了额外的功能 除了继承的规定之外的规定 来自Collection界面,关于 所有建设者的合同和 add,equals和。的合同 hashCode方法。声明 其他继承的方法也是 这里包括为方便起见。 (该 伴随这些的规格 声明已经适应了 设置界面,但它们不包含 任何其他规定。)
附加规定 毫不奇怪,构造函数是 所有构造函数都必须创建一个 设置不包含重复 元素(如上所定义)。
来自Map
:
将键映射到值的对象。 地图不能包含重复的键;每个键最多可以映射一个值。
如果您可以使用现有代码实施Set
,那么您可以通过现有代码实现的任何好处(例如速度)也会累积到Set
。
如果您选择在没有Set
支持的情况下实施Map
,则必须复制旨在防止重复元素的代码。啊,好吃的讽刺。
也就是说,没有什么能阻止您以不同的方式实施Set
。
答案 1 :(得分:4)
我猜它从未成为真正的应用程序或重要基准的重大问题。为什么复杂的代码没有真正的好处?
另请注意,对象大小在许多JVM实现中都是向上舍入的,因此实际上可能没有增加大小(我不知道这个例子)。此外,HashMap
的代码可能会在缓存中编译。其他条件相同,更多代码=&gt;更多缓存未命中=&gt;性能较低。
答案 2 :(得分:4)
我的猜测是HashSet最初是根据HashMap实现的,以便快速轻松地完成。就代码行而言,HashSet是HashMap的一小部分。
我猜它还没有被优化的原因是害怕改变。
然而,浪费比你想象的要糟糕得多。在32位和64位上,HashSet比必要的大4倍,而HashMap比必要的大2倍。可以使用包含键和值的数组(以及用于冲突的链)来实现HashMap。这意味着每个条目有两个指针,或64位VM上的16个字节。实际上,HashMap每个条目包含一个Entry对象,它为Entry的指针增加了8个字节,为Entry对象头添加了8个字节。 HashSet每个元素也使用32个字节,但浪费是4x而不是2x,因为每个元素只需要8个字节。
答案 3 :(得分:3)
是的,你是对的,那里有少量的浪费。小,因为,对于每个条目,它使用相同的对象PRESENT
(声明为final)。因此,唯一的浪费是HashMap中每个条目的值。
大多数情况下,我认为,他们采用这种方法来实现可维护性和可重用性。 (JCF开发人员会想到,无论如何我们已经测试了HashMap,为什么不重用它。)
但是如果你有大量的收藏,并且你是一个记忆狂,那么你可以选择更好的替代方案,如Trove或Google Collections。
答案 4 :(得分:3)
我看了你的问题,我花了一些时间思考你所说的话。所以这是我对HashSet
实施的看法。
必须让虚拟实例知道集合中是否存在该值。
看一下add方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
Abd现在让我们来看看put返回值
@returns与key关联的前一个值,如果没有key的映射,则返回null。 (null返回也可以指示映射先前将null与key关联。)
因此PRESENT
对象仅用于表示该集合包含e值。我想您问为什么不使用null
代替PRESENT
。但是,您无法区分该条目之前是否在地图上,因为map.put(key,value)
将始终返回null
,您无法知道该密钥是否存在。
话虽如此,你可以说他们可以使用像这样的实现
public boolean add(E e) {
if( map.containsKey(e) ) {
return false;
}
map.put(e, null);
return true;
}
我猜他们浪费了4个字节来避免计算hashCode,因为它可能很贵,密钥的两倍(如果要添加密钥)。
如果你提问为什么他们使用HashMap
会浪费8个字节(因为Map.Entry
)而不是使用类似条目只有4的其他数据结构,那么是的,我会说他们是出于你提到的原因而做的。
答案 5 :(得分:0)
在搜索了这样的页面后,想知道为什么标准实现效率低下,找到了com.carrotsearch.hppc.IntOpenHashSet
答案 6 :(得分:-3)
你的问题: 我认为这对于条目本身的大小来说浪费了4个字节(在32位机器上)。
只为hashset的整个数据结构创建一个Object变量,这样做可以避免再次重写整个hashMap类型的代码。
private static final Object PRESENT = new Object();
所有键都有一个值,即PRESENT对象。