为什么Sun Java中的HashSet实现使用HashMap作为支持?

为什么Sun Java中的HashSet实现使用HashMap作为支持?,java,hashmap,hashset,Java,Hashmap,Hashset,查看Java6的源代码,HashSet实际上是使用HashMap实现的,在集合的每个条目上使用虚拟对象实例 我认为条目本身的大小浪费了4字节(在32位机器上) 但是,为什么它仍然被使用?除了使代码维护更容易之外,还有什么理由使用它吗?我猜想,对于实际应用程序或重要的基准测试来说,它从来没有出现过重大问题。为什么要把代码复杂化而没有真正的好处 还要注意的是,在许多JVM实现中,对象大小都是四舍五入的,因此实际上可能不会增加大小(本例中我不知道)。另外,HashMap的代码可能会被编译并存储在缓存中

查看Java6的源代码,
HashSet
实际上是使用
HashMap
实现的,在集合的每个条目上使用虚拟对象实例

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


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

我猜想,对于实际应用程序或重要的基准测试来说,它从来没有出现过重大问题。为什么要把代码复杂化而没有真正的好处


还要注意的是,在许多JVM实现中,对象大小都是四舍五入的,因此实际上可能不会增加大小(本例中我不知道)。另外,
HashMap
的代码可能会被编译并存储在缓存中。在其他条件相同的情况下,更多的代码=>更多的缓存未命中=>更低的性能。

是的,你是对的,确实存在少量的浪费。小,因为对于每个条目,它使用相同的对象
PRESENT
(声明为final)。因此,惟一的浪费是HashMap中每个条目的值

我认为,大多数情况下,他们采用这种方法是为了可维护性和可重用性。(JCF开发人员会想,反正我们已经测试了HashMap,为什么不重用它呢。)


但是,如果你有大量的收藏,而且你是一个记忆怪胎,那么你可能会选择更好的替代品,如或。

我看了你的问题,花了一段时间思考你说的话。下面是我对
HashSet
实现的看法

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

看看add方法

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

Abd,现在让我们来看看PUT返回值

@返回与键关联的上一个值,如果键没有映射,则返回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个字节来避免计算哈希代码,因为这可能会很昂贵,因为要将密钥计算两次(如果要添加密钥的话)



如果你问他们为什么使用浪费8字节的
HashMap
(因为
Map.Entry
),而不是使用类似的只有4个条目的其他数据结构,那么是的,我会说他们这样做是因为你提到的原因。

事实上,这不仅仅是
HashSet
。Java 6中
Set
接口的所有实现都基于底层
Map
。这不是一项要求;这就是实现的方式。您可以查看的各种实现的文档

你的主要问题是

但是,为什么它仍然被使用?有 除了制作它,还有什么理由使用它吗 更容易维护代码

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

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

具体而言:

从:

不包含任何内容的集合 重复元素。更正式地说, 集合不包含元素对e1 和e2,使得e1等于(e2),并且 大多数情况下只有一个空元素。正如 它的名字,这个接口模型 数学集合抽象

Set接口会放置额外的 规定,超越继承的规定 从集合界面,在 所有施工人员的合同,以及 加法的契约,等于和 hashCode方法。声明 其他继承的方法也很有用 包括在这里是为了方便。(修订) 随附的规范 声明是根据实际情况量身定制的 设置接口,但它们不包含 任何附加规定。)

附加规定 构造器是,毫不奇怪, 所有构造函数都必须创建一个 不包含重复项的集合 元素(如上所述)

和来自:

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

如果您可以使用现有代码实现
集合
,那么您可以从现有代码中获得的任何好处(例如,速度)也会累积到
集合

如果选择在没有
映射
支持的情况下实现
,则必须复制为防止重复元素而设计的代码。啊,有趣的讽刺


也就是说,没有什么可以阻止您以不同的方式实现
集。

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

我猜它仍然没有被优化的原因是害怕改变

然而,浪费比你想象的要严重得多。在32位和64位上,HashSet比需要的大4倍,HashMap比需要的大2倍。HashMap可以用一个包含键和值的数组(加上冲突链)来实现。这意味着每个条目有两个指针,或者64位虚拟机上有16个字节。事实上,HashMap每个条目都包含一个条目对象,而