看看Java 6的源代码,HashSet<E>实际上是使用HashMap<E,Object>实现的,在Set的每个条目上使用伪对象示例。我认为这浪费了4个字节(在32位机器上)作为条目本身的大小。但是,为什么它还在使用呢?除了使代码更容易维护之外,还有什么理由使用它呢?
HashSet<E>
HashMap<E,Object>
dy2hfwbg1#
实际上,不仅仅是HashSet。* 所有 * Java6中Set接口的实现都基于底层Map。这只是实现的方式,您可以查看Set的各种实现的文档,自己来查看。你的主要问题是但是,为什么它还在使用呢?除了使代码更容易维护之外,还有什么理由使用它呢?我认为代码维护是一个很大的激励因素,防止重复和膨胀也是如此。Set和Map是相似的接口,因为不允许重复的元素(我认为Map支持的唯一Set是CopyOnWriteArraySet,这是一个不寻常的集合,因为它是不可变的)。具体而言:从documentation of Set:一种不包含重复元素的集合。更正式地说,set不包含e1和e2这样的元素对e1.equals(e2),并且最多包含一个空元素。正如其名称所暗示的,此接口模拟数学集合抽象。除了从Collection接口继承的约定之外,Set接口还在所有构造函数的约定以及add、equals和hashCode方法的约定上添加了额外的约定。为了方便起见,这里还包括了其他继承方法的声明。(这些声明附带的规范已经针对Set接口进行了定制,但它们不包含任何额外的约定。)构造函数的附加规定是,所有构造函数必须创建一个不包含重复元素的集合(如上所述)。从Map开始:将键Map到值的对象。Map不能包含重复的键;每个键可以Map到至多一个值。如果您可以使用现有代码实现Set,那么您可以从现有代码中获得的任何好处(例如,速度)也会增加到您的Set中。如果你选择实现一个没有Map支持的Set,你就必须复制那些旨在防止重复元素的代码。也就是说,没有什么可以阻止您以不同的方式实现Set。
HashSet
Set
Map
CopyOnWriteArraySet
41zrol4v2#
我的猜测是HashSet最初是根据HashMap实现的,目的是快速和容易地完成它。就代码行而言,HashSet只是HashMap的一小部分。我猜它还没有优化的原因是害怕改变。然而,浪费比您想象的要严重得多。在32位和64位上,HashSet都比所需的大4倍,HashMap比所需的大2倍。HashMap可以用包含键和值的数组来实现(加上冲突链)。这意味着每个条目两个指针,或者在64位VM上为16字节。实际上,HashMap每个条目包含一个Entry对象,这增加了8个字节用于指向条目的指针和8个字节用于条目对象头。HashSet也使用每个元素32个字节,但是浪费是4倍而不是2倍,因为它只需要每个元素8个字节。
8iwquhpp3#
是的,你是对的,这里确实有少量的浪费。浪费很小是因为每个条目都使用相同的对象PRESENT(声明为final)。因此,唯一的浪费是HashMap中每个条目的值。我认为,大多数情况下,他们采用这种方法是为了可维护性和可重用性(JCF开发人员可能会想,反正我们已经测试了HashMap,为什么不重用它呢?)但是如果你有大量的收藏,而且你是一个记忆狂,那么你可以选择更好的替代品,如Trove或Google Collections。
PRESENT
hvvq6cgz4#
我看了你的问题,花了一点时间来思考你说的话,所以这是我对HashSet实现的看法。必须有伪示例才能知道该值是否存在于集合中。看一下add方法
public boolean add(E e) { return map.put(e, PRESENT)==null; }
现在让我们来看看put返回值@返回与key关联的前一个值,如果没有key的Map,则返回null。(null返回也可以指示Map以前将null与key关联。)所以PRESENT对象只是用来表示集合包含e值,我想你问过为什么不用null代替PRESENT,但是,你不能区分这个条目之前是否在map上,因为map.put(key,value)总是返回null,你没有办法知道这个键是否存在。也就是说,您可能会认为他们本可以使用这样的实现
null
map.put(key,value)
public boolean add(E e) { if( map.containsKey(e) ) { return false; } map.put(e, null); return true; }
我猜他们浪费了4个字节来避免两次计算密钥的hashCode,因为这可能是昂贵的(如果要添加密钥的话)。如果你问他们为什么使用HashMap,这将浪费8个字节(因为Map.Entry),而不是一些其他的数据结构使用类似的条目只有4,那么是的,我会说他们这样做的原因你提到的。
HashMap
Map.Entry
dsekswqp5#
我猜它从来没有作为一个真实的的应用程序或重要的基准测试的重大问题出现过。为什么要复杂化代码而没有真正的好处呢?还要注意的是,在很多JVM实现中对象的大小都是四舍五入的,所以实际上可能并没有增加(在这个例子中我不知道)。另外,HashMap的代码可能已经编译并在缓存中。在其他条件相同的情况下,更多的代码=〉更多的缓存未命中=〉更低的性能。
flvlnr446#
在搜索了类似这样的页面之后,我想知道为什么标准实现的效率有点低,我找到了com.carrotsearch.hppc.IntOpenHashSet
x4shl7ld7#
您的问题:我认为这浪费了4个字节(在32位机器上)作为条目本身的大小。只为hashset的整个数据结构创建一个Object变量,这样做可以避免重新编写整个hashMap类型的代码。第一个月所有键都有一个值,即PRESENT对象。
7条答案
按热度按时间dy2hfwbg1#
实际上,不仅仅是
HashSet
。* 所有 * Java6中Set
接口的实现都基于底层Map
。这只是实现的方式,您可以查看Set
的各种实现的文档,自己来查看。你的主要问题是
但是,为什么它还在使用呢?除了使代码更容易维护之外,还有什么理由使用它呢?
我认为代码维护是一个很大的激励因素,防止重复和膨胀也是如此。
Set
和Map
是相似的接口,因为不允许重复的元素(我认为Map
支持的唯一Set
是CopyOnWriteArraySet
,这是一个不寻常的集合,因为它是不可变的)。具体而言:
从documentation of
Set
:一种不包含重复元素的集合。更正式地说,set不包含e1和e2这样的元素对e1.equals(e2),并且最多包含一个空元素。正如其名称所暗示的,此接口模拟数学集合抽象。
除了从Collection接口继承的约定之外,Set接口还在所有构造函数的约定以及add、equals和hashCode方法的约定上添加了额外的约定。为了方便起见,这里还包括了其他继承方法的声明。(这些声明附带的规范已经针对Set接口进行了定制,但它们不包含任何额外的约定。)
构造函数的附加规定是,所有构造函数必须创建一个不包含重复元素的集合(如上所述)。
从
Map
开始:将键Map到值的对象。Map不能包含重复的键;每个键可以Map到至多一个值。
如果您可以使用现有代码实现
Set
,那么您可以从现有代码中获得的任何好处(例如,速度)也会增加到您的Set
中。如果你选择实现一个没有
Map
支持的Set
,你就必须复制那些旨在防止重复元素的代码。也就是说,没有什么可以阻止您以不同的方式实现
Set
。41zrol4v2#
我的猜测是HashSet最初是根据HashMap实现的,目的是快速和容易地完成它。就代码行而言,HashSet只是HashMap的一小部分。
我猜它还没有优化的原因是害怕改变。
然而,浪费比您想象的要严重得多。在32位和64位上,HashSet都比所需的大4倍,HashMap比所需的大2倍。HashMap可以用包含键和值的数组来实现(加上冲突链)。这意味着每个条目两个指针,或者在64位VM上为16字节。实际上,HashMap每个条目包含一个Entry对象,这增加了8个字节用于指向条目的指针和8个字节用于条目对象头。HashSet也使用每个元素32个字节,但是浪费是4倍而不是2倍,因为它只需要每个元素8个字节。
8iwquhpp3#
是的,你是对的,这里确实有少量的浪费。浪费很小是因为每个条目都使用相同的对象
PRESENT
(声明为final)。因此,唯一的浪费是HashMap中每个条目的值。我认为,大多数情况下,他们采用这种方法是为了可维护性和可重用性(JCF开发人员可能会想,反正我们已经测试了HashMap,为什么不重用它呢?)
但是如果你有大量的收藏,而且你是一个记忆狂,那么你可以选择更好的替代品,如Trove或Google Collections。
hvvq6cgz4#
我看了你的问题,花了一点时间来思考你说的话,所以这是我对
HashSet
实现的看法。必须有伪示例才能知道该值是否存在于集合中。
看一下add方法
现在让我们来看看put返回值
@返回与key关联的前一个值,如果没有key的Map,则返回null。(null返回也可以指示Map以前将null与key关联。)
所以
PRESENT
对象只是用来表示集合包含e值,我想你问过为什么不用null
代替PRESENT
,但是,你不能区分这个条目之前是否在map上,因为map.put(key,value)
总是返回null
,你没有办法知道这个键是否存在。也就是说,您可能会认为他们本可以使用这样的实现
我猜他们浪费了4个字节来避免两次计算密钥的hashCode,因为这可能是昂贵的(如果要添加密钥的话)。
如果你问他们为什么使用
HashMap
,这将浪费8个字节(因为Map.Entry
),而不是一些其他的数据结构使用类似的条目只有4,那么是的,我会说他们这样做的原因你提到的。dsekswqp5#
我猜它从来没有作为一个真实的的应用程序或重要的基准测试的重大问题出现过。为什么要复杂化代码而没有真正的好处呢?
还要注意的是,在很多JVM实现中对象的大小都是四舍五入的,所以实际上可能并没有增加(在这个例子中我不知道)。另外,
HashMap
的代码可能已经编译并在缓存中。在其他条件相同的情况下,更多的代码=〉更多的缓存未命中=〉更低的性能。flvlnr446#
在搜索了类似这样的页面之后,我想知道为什么标准实现的效率有点低,我找到了com.carrotsearch.hppc.IntOpenHashSet
x4shl7ld7#
您的问题:我认为这浪费了4个字节(在32位机器上)作为条目本身的大小。
只为hashset的整个数据结构创建一个Object变量,这样做可以避免重新编写整个hashMap类型的代码。
第一个月
所有键都有一个值,即PRESENT对象。