Java HashSet vs HashMap

时间:2011-04-16 20:48:42

标签: java collections

我了解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;
 }
}

7 个答案:

答案 0 :(得分:129)

地图包含唯一键。使用映射中存在的键调用put时,该键下的对象将替换为新对象。因此大小为1。

两者之间的区别应该是显而易见的:

  • Map存储键值对
  • Set中仅存储密钥

事实上,HashSet有一个HashMap字段,每当调用add(obj)时,都会在基础地图put上调用map.put(obj, DUMMY)方法 - 虚拟对象是private static final Object DUMMY = new Object()的位置。因此,地图将填充您的对象作为键,以及一个不感兴趣的值。

答案 1 :(得分:7)

Map中的密钥只能映射到单个值。因此,当您put第二次使用相同的密钥进入地图时,它会覆盖第一个条目。

答案 2 :(得分:6)

对于HashSet,添加same对象或多或少都是无操作。对于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());

答案 3 :(得分:1)

我只想补充这些伟大的答案,解决你最后的两难困境。您想知道这两个集合之间的区别是什么,如果它们在插入后返回相同的大小。好吧,你不能在这里真正看到差异,因为你在地图中用相同的键插入两个值,因此用第二个值改变第一个值。如果您在地图中插入相同值,但是使用不同的键,您会看到真正的差异(在其他之中)。然后,您会在地图中看到可以 重复值,但无法 重复键< / strong>,并且在无法重复值的集合中。这是主要区别。

答案 4 :(得分:1)

答案很简单,因为它是HashSets的本质。 HashSet使用内部HashMap和名为PRESENT的虚拟对象作为值,此hashmap的 KEY 将是您的对象。

hash(simpleObject1)和hash(simplObject2)将返回相同的int。所以?

当你将simpleObject1添加到hashset时,它将把它放到它的内部hashmap中,并将simpleObject1作为键。然后,当您添加(simplObject2)时,您将得到错误,因为它在内部hashmap中已经可用作键。

作为一个额外的信息,HashSet使用有效的散列函数通过使用object的equals()和hashCode()契约来提供O(1)性能。这就是为什么hashset不允许“null”无法将equals()和hashCode()实现为非对象的原因。

答案 5 :(得分:0)

我认为主要的区别是, HashSet在某种意义上是稳定的,它不会替换重复值(如果在插入第一个唯一键后发现,只丢弃所有未来的重复项),HashMap将努力用新的重复值替换旧的。因此,在插入新的重复项时,HashMap必须有开销。

答案 6 :(得分:0)

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable
此类实现Set接口,由哈希表(实际上是HashMap实例)支持。它不能保证集合的迭代顺序;特别是,它不保证订单会随着时间的推移保持不变。该类允许null元素。

该类为基本操作(添加,删除,包含和大小)提供恒定的时间性能,假设散列函数在桶之间正确地分散元素。迭代此集合需要的时间与HashSet实例的大小(元素数量)加上&#34;容量&#34;的总和成正比。支持HashMap实例(桶的数量)。因此,如果迭代性能很重要,不要将初始容量设置得太高(或负载因子太低)非常重要。

请注意,此实现未同步。如果多个线程同时访问哈希集,并且至少有一个线程修改了该集,则必须在外部进行同步。这通常通过在自然封装集合的某个对象上进行同步来实现。如果不存在这样的对象,那么该集合应该是&#34; wrap&#34;使用Collections.synchronizedSet方法。这最好在创建时完成,以防止对集合的意外不同步访问 More Details