为什么Sun Java中的HashSet实现使用HashMap作为后台?

查看Java 6的源代码, HashSet<E>实际上是使用HashMap<E,Object> ,在Set的每个条目上都使用了虚拟对象实例。

我认为这是浪费4个字节(在32位机器上)的大小的条目本身。

但是,为什么仍然使用? 除了维护代码更容易之外,是否有任何理由使用它?

实际上,这不仅仅是HashSet 。 Java 6中Set接口的所有实现都基于基础Map 。 这不是要求; 这只是实施的方式。 您可以查看Set的各种实现的文档。

你的主要问题是

但是,为什么仍然使用? 除了维护代码更容易之外,是否有任何理由使用它?

我认为代码维护是一个很大的激励因素。 那么是防止重复和膨胀。

SetMap是类似的接口,因为重复的元素是不允许的。 (我认为唯一支持Map SetCopyOnWriteArraySet ,这是一个不寻常的集合,因为它是不可变的。)

特别:

Set的文档 :

不包含重复元素的集合。 更正式地,集合不包含e1和e2这样的元素,使得e1.equals(e2)和至多一个null元素。 正如其名称所暗示的,这个接口模拟了math集抽象。

Set接口将所有构造函数的合约以及add,equals和hashCode方法的契约之外的其他约束放在超出Collection接口inheritance的约束之外。 其他的inheritance方法的声明也包括在内。 (这些声明附带的规范已经根据Set界面进行了调整,但不包含任何其他规定。)

对构造函数的额外规定并不奇怪,所有构造函数都必须创build一个不包含重复元素(如上所定义)的集合。

Map

将键映射到值的对象。 地图不能包含重复的键; 每个键可以映射到最多一个值。

如果你可以使用现有的代码来实现你的Set ,那么你可以从现有的代码中获得的任何好处(例如速度)都会累积到你的Set中。

如果您select实现无Map支持的Set ,则必须复制旨在防止重复元素的代码。 啊,美味的讽刺

也就是说,没有什么能阻止你以不同的方式实现你的Set

我猜测,它从来没有成为真正的应用程序或重要基准的重大问题。 为什么复杂的代码没有真正的好处?

还要注意的是,在很多JVM实现中,对象大小都是四舍五入的,所以实际上可能不会增加大小(我不知道这个例子)。 此外, HashMap的代码很可能被编译和caching。 其他的事情是相同的,更多的代码=>更多的caching未命中=>性能较低。

我的猜测是,HashSet最初是以HashMap的方式实现的,以便快速,轻松地完成。 就代码行而言,HashSet是HashMap的一小部分。

我猜想,它还没有被优化的原因是害怕变化。

但是,浪费比你想象的要糟得多。 在32位和64位上,HashSet比所需要的大4倍,而HashMap比所需要的大2倍。 HashMap可以通过一个数组来实现,其中包含键和值(加上碰撞链)。 这意味着每个条目有两个指针,或者64位虚拟机上有16个字节。 实际上,HashMap在每个条目中都包含一个Entry对象,它为Entry指针添加8个字节,为Entry对象头添加8个字节。 HashSet也使用每个元素32字节,但浪费是4倍而不是2倍,因为它只需要每个元素8字节。

是的你是对的,那里有less量的浪费。 小,因为,对于每个条目,它使用相同的对象PRESENT (这是宣布的最终)。 因此唯一的浪费就是HashMap中每个条目的值。

大多数情况下,我认为,他们采取这种方法的可维护性和可重用性。 (JCF开发者会认为,我们已经testing了HashMap,为什么不重用呢。)

但是,如果你有巨大的collections,而你是一个记忆怪胎,那么你可以select更好的替代品,如Trove或谷歌collections 。

我看了你的问题,花了一段时间思考你的话。 所以这里是我对HashSet实现的看法。

有必要让虚拟实例知道该值是否存在于集合中。

看看add方法

 public boolean add(E e) { return map.put(e, PRESENT)==null; } 

现在让我们来看看放回价值

@返回与键关联的前一个值,如果没有键的映射,则返回null。 (空返回也可以指示映射先前与键关联的是null。)

所以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 )而不是其他一些使用类似Entry的只有4的数据结构,那么是的,我会说他们这样做是因为你提及。

在search这样的页面之后,想知道为什么轻度低效的标准实现,发现com.carrotsearch.hppc.IntOpenHashSet

你的问题:我认为这是浪费4个字节(在32位机器上)的大小的条目本身。

只需要为整个哈希集的数据结构创build一个对象variables,这样可以避免重新编写整个哈希映射types的代码。

private static final Object PRESENT = new Object();

所有的键都有一个值,即PRESENT对象。