【问题标题】:Java HashSet vs HashMapJava HashSet 与 HashMap
【发布时间】:2011-04-16 20:48:42
【问题描述】:

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

【问题讨论】:

  • 只是好奇,为什么第一个前缀是simple,第二个前缀是simpl? :)
  • 我明白为什么map size是1,但是不知道为什么set size也是1?我们把 2 个对象放在集合中,为什么它的大小是 1?

标签: java collections


【解决方案1】:

地图拥有唯一的键。当您使用映射中存在的键调用put 时,该键下的对象将替换为新对象。因此尺寸为 1。

两者的区别应该很明显:

  • Map 中存储键值对
  • Set 中,您只存储密钥

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

【讨论】:

  • 所以当键具有相同的哈希码时,我不能在 HashMap 中粘贴不同的对象?
  • 接受具有不同哈希码的键。但是彼此相等的键不相等。
  • @Bart Kiers - 相等 - 是的,但允许使用相同的哈希码。
  • @Bart 你可以这样:public int hashCode() { return 1; } 我不推荐它,但它是合法的,并且哈希图可以工作(尽管它的性能会降低。)
  • 完美答案!从字面上看,我花了2秒钟才理解它!我只是注意到“在一个集合中你只存储键”并且不需要在这句话之前或之后阅读其他任何内容。
【解决方案2】:

Map 中的键只能映射到单个值。所以第二次你put 使用相同的键进入地图时,它会覆盖第一个条目。

【讨论】:

    【解决方案3】:

    在 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());
    

    【讨论】:

    • 这并不完全正确。如果您的对象上的.equals() 方法是return false;,您可以根据需要多次添加相同的对象。不过我不推荐它。
    • 是的,我假设他没有覆盖 SimpleObject 中的 equals() 或 hashCode()。
    • 我在 SimpleObject 中覆盖了 equals() 或 hashCode()
    • @glowcoder:我们应该始终假设equalshashCode 的合理(即符合规范)实现。
    【解决方案4】:

    我只是想补充这些很棒的答案,即您最后一个难题的答案。您想知道这两个集合之间的区别是什么,如果它们在您插入后返回相同的大小。好吧,您在这里看不到真正的区别,因为您在地图中插入了具有相同键的两个值,因此用第二个值更改了第一个值。如果您在地图中插入了相同的值,但使用了不同的键,您会看到真正的区别(以及其他区别)。然后,您会看到您可以在地图中有重复值,但您不能重复键,并且在集合中您不能重复值。这是这里的主要区别。

    【讨论】:

      【解决方案5】:

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

      hash(simpleObject1) 和 hash(simplObject2) 将返回相同的 int。那么?

      当您将 simpleObject1 添加到 hashset 时,它会将其放入其内部 hashmap 中,并将 simpleObject1 作为键。然后当你 add(simplObject2) 你会得到 false 因为它在内部哈希图中已经作为键可用。

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

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

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-02-05
            • 1970-01-01
            • 2023-03-16
            • 2012-06-24
            • 1970-01-01
            相关资源
            最近更新 更多