Java HashSet vs HashMap
我明白, HashSet
是基于HashMap
实现,但是当你需要独特的元素集使用。 那么为什么在接下来的代码中,将相同的对象放入地图并设置两个集合的大小等于1? 不应该映射大小为2? 因为如果这两个集合的大小相等,我不会看到使用这两个集合的任何区别。
Set testSet = new HashSet<SimpleObject>(); Map testMap = new HashMap<Integer, SimpleObject>(); SimpleObject simpleObject1 = new SimpleObject("Igor", 1); SimpleObject simplObject2 = new SimpleObject("Igor", 1); testSet.add(simpleObject1); testSet.add(simplObject2); Integer key = new Integer(10); testMap.put(key, simpleObject1); testMap.put(key, simplObject2); System.out.println(testSet.size()); System.out.println(testMap.size());
输出是1和1。
SimpleObject code public class SimpleObject { private String dataField1; private int dataField2; public SimpleObject(){} public SimpleObject(String data1, int data2){ this.dataField1 = data1; this.dataField2 = data2; } public String getDataField1() { return dataField1; } public int getDataField2() { return dataField2; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((dataField1 == null) ? 0 : dataField1.hashCode()); result = prime * result + dataField2; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SimpleObject other = (SimpleObject) obj; if (dataField1 == null) { if (other.dataField1 != null) return false; } else if (!dataField1.equals(other.dataField1)) return false; if (dataField2 != other.dataField2) return false; return true; } }
地图拥有唯一的密钥。 当您使用映射中存在的键调用put
,该键下的对象将replace为新对象。 因此,大小1。
两者的区别应该是显而易见的:
- 在
Map
存储键值对 - 在一
Set
,只存储密钥
事实上,一个HashSet
有一个HashMap
字段,每当add(obj)
被调用时, put
方法在底层映射上被调用map.put(obj, DUMMY)
– 其中dummy对象是一个private static final Object DUMMY = new Object()
。 所以地图是用你的对象作为关键字填充的,并且是一个不感兴趣的值。
Map
的键只能映射到单个值。 所以第二次用相同的键put
地图时,它会覆盖第一个条目。
在HashSet的情况下,添加相同的对象将或多或less是一个空操作。 在HashMap的情况下,把一个新的键值对与一个已经存在的键值一起覆盖现有的值,为该键值设置一个新的值。 下面我添加了equals()检查你的代码:
SimpleObject simpleObject1 = new SimpleObject("Igor", 1); SimpleObject simplObject2 = new SimpleObject("Igor", 1); //If the below prints true, the 2nd add will not add anything System.out.println("Are the objects equal? " , (simpleObject1.equals(simpleObject2)); testSet.add(simpleObject1); testSet.add(simplObject2); Integer key = new Integer(10); //This is a no-brainer as you've the exact same key, but lets keep it consistent //If this returns true, the 2nd put will overwrite the 1st key-value pair. testMap.put(key, simpleObject1); testMap.put(key, simplObject2); System.out.println("Are the keys equal? ", (key.equals(key)); System.out.println(testSet.size()); System.out.println(testMap.size());
我只是想增加这些伟大的答案,回答你最后的困境。 你想知道这两个集合之间有什么区别,如果他们插入后返回相同的大小。 那么,你不能真正看到这里的区别,因为你用相同的键在地图中插入了两个值,因此用第二个值改变了第一个值。 如果您在地图中插入了相同的值 ,但是使用了不同的键 ,则会看到真正的差异(其他)。 然后,你会看到你可以在地图中有重复的值 ,但是你不能有重复的键 ,并且在这个集合中你不能有重复的值 。 这是这里的主要区别。
答案很简单,因为它是HashSets的本质。 HashSet使用名为PRESENT的虚拟对象作为值在内部使用HashMap,此HashMap的KEY将成为您的对象。
散列(simpleObject1)和散列(simplObject2)将返回相同的int。 所以?
当您将simpleObject1添加到哈希集时,它将把simpleObject1作为关键字放到它的内部哈希映射中。 然后当你添加(simplObject2)时,你将会得到false,因为它已经在内部hashmap中作为key可用了。
作为一些额外的信息,HashSet使用有效的散列函数来通过使用对象的equals()和hashCode()契约来提供O(1)的性能。 这就是为什么hashset不允许“无效”,无法实现equals()和hashCode()非对象。
我认为主要的区别在于,HashSet在某种意义上是稳定的,它并不代替重复的值(如果在插入第一个唯一的键之后发现,只是放弃所有未来的重复),并且HashMap将努力用新的重复值replace旧的。 所以在插入新的重复项目的HashMap中必然会有开销。
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable
这个类实现了Set接口,由一个哈希表(实际上是一个HashMap实例)支持。 对集合的迭代次序没有保证; 特别是不能保证订单会随着时间的推移而保持不变。 这个类允许null元素。
这个类为基本操作(添加,删除,包含和大小)提供了恒定的时间性能,假设散列函数在桶之间正确地分散元素。 遍历这个集合需要的时间与HashSet实例的大小(元素数量)加上支持HashMap实例的“容量”(桶的数量)的总和成正比。 因此,如果迭代性能很重要,不要将初始容量设置得太高(或者负载因子太低)。
请注意,此实现不同步。 如果多个线程同时访问哈希集合,并且至less有一个线程修改了集合,则必须在外部进行同步。 这通常是通过同步一些自然封装集合的对象来完成的。 如果不存在这样的对象,则应该使用Collections.synchronizedSet方法来“包装”该集合。 这最好在创build时完成,以防止意外的不同步访问设置更多细节
Map
包含唯一的密钥。 当您尝试使用现有key
新logging时,这意味着您要用此特定key
覆盖您的logging。