【问题标题】:Problem with Maps in javajava中的地图问题
【发布时间】:2011-08-05 20:50:03
【问题描述】:

我有一个包含 X 个元素的 Hashmap
我需要将此地图移动到另一张地图中
这就是我的代码的样子

Map originMap = initialize();
Map destMap = new Hashmap ();  

int originMapSize = originMap.size(); 
Set<Map.Entry<K, V>> entries = originMap.entrySet();
for (Map.Entry<K, Y> mapEntry : entries) {
 K key = mapEntry.getKey();
 V value = mapEntry.getValue();
 destMap.put (key,value);
}  

// Shouldnt this be equal to originMapSize ????
int destMapSize = destMap.size();

我观察到的是 - originMapSize 不等于 destMapSize

似乎当我们将元素放入 destMap 时,某些元素被覆盖了

我们已经覆盖了 hashCode 和 equals 方法——这是一个可疑的实现。
但是,如果 originMap 允许添加元素,为什么destinationMap 不添加新元素并覆盖现有元素呢?

【问题讨论】:

  • 初始化方法是做什么的,为什么i++在里面?
  • 你如何初始化在你的 for 循环中迭代的“条目”变量?
  • 如果映射中已经存在一个键,那么它将覆盖之前的值。
  • i++ 仅用于调试目的。现在删除它。
  • @Varun:我的问题是 - 为什么第一个映射允许两个键同时存在而第二个映射覆盖它

标签: java collections hashmap


【解决方案1】:

如果equals 方法不对称,就会发生这种情况。假设有两个键 a 和 b 使得:

  • a.hashCode() == b.hashCode()
  • a.equals(b) 返回错误
  • b.equals(a) 返回真

然后假设HashMap 实现通过调用existingKey.equals(newKey) 来搜索现有密钥,每个现有密钥具有与新密钥相同的哈希码。

现在假设我们最初按 { a, b } 的顺序添加它们。

第一个键(a)显然没有问题。第二个键 (b) 插入最终调用 a.equals(b) - 这是错误的,所以我们得到两个键。

现在构建第二个HashMap,我们最终可能会以 { b, a } 的顺序获取条目。

这次我们首先添加b,这很好......但是当我们插入第二个键(a)时,我们最终会调用b.equals(a),它返回true,所以我们覆盖 em> 条目。

这可能不是正在发生的事情,但它可以解释事情 - 并显示不对称 equals 方法的危险。

编辑:这是一个简短但完整的程序来演示这种情况。 (a 和b 的确切细节可能不一样,但不对称性是。)

import java.util.*;

public class Test {

    private final String name;

    public Test(String name)
    {
        this.name = name;
    }

    public static void main(String[] args)
    {
        Map<Test, String> firstMap = new HashMap<Test, String>();

        Test a = new Test("a");
        Test b = new Test("b");

        firstMap.put(b, "b");
        firstMap.put(a, "a");

        Map<Test, String> secondMap = new HashMap<Test, String>();
        for (Map.Entry<Test, String> entry : firstMap.entrySet())
        {
            System.out.println("Adding " + entry.getKey().name);
            secondMap.put(entry.getKey(), entry.getValue());
        }
        System.out.println(secondMap.size());
    }

    @Override public int hashCode()
    {
        return 0;
    }

    @Override public boolean equals(Object other)
    {
        return this.name.equals("b");
    }
}

我的机器上的输出:

Adding a
Adding b
1

您可能无法以这种方式获得输出 - 这取决于:

  • equals 的调用方式(candidateKey.equals(newKey) 或反之亦然)
  • 从集合中返回条目的顺序

它甚至可能在不同的运行中以不同的方式工作。

【讨论】:

  • 如何创建一个不对称的 equals 函数?
  • 嗯。我明白你的意思了。将使用来自 Guava 库的多图 help (如果我现在不想更改 equals\hashcode)
  • @RN:不确定。但是如果你有一个糟糕的 equals/hashCode 实现,那么修复它应该是你的第一要务 - 直到你完成它,你会看到奇怪的东西。
  • 刚刚验证 - equals 方法是不对称的。谢谢,这很有帮助。
【解决方案2】:

这些值应该相等,但问题是您正在迭代不同的 Map 对象。

for (Map.Entry mapEntry : entries)

和

不一样
for (Map.Entry mapEntry : originMap)

【讨论】:

  • 我很确定他的意思是 originMap.entrySet()。
【解决方案3】:

我怀疑元素添加到第一个 hashmap 的顺序与添加到第二个的顺序不同。这与粗略的 hashCode 方法相结合导致将重复项添加到第一个。

尝试更改 hashCode 以始终返回相同的值,看看您的问题是否消失。

【讨论】:

  • 我不认为一个糟糕的hashCode 实现会导致这种情况,但一个糟糕的equals 实现肯定可以 - 请参阅我的答案作为示例。
  • 你可能是对的,但由于 hashCode 只是一个性能调整,我会在调试这样的问题时将其删除。
  • “删除它”是指删除覆盖吗?这将完全改变行为,因为您最终会得到可能从 equals 返回 true 但具有不同哈希码的对象。您可以让它返回 0...但我认为这不会是这里的问题。
  • 是的,我只会返回 0。事实上,我会一直这样做,直到发现性能问题。
  • 哇,真的吗?在大多数情况下很容易获得正确的哈希码(只需遵循 Effective Java) - 为什么要将每个 HashMap 查找转换为 O(n) 操作?我完全是为了避免微优化,但这是一个 O(1) 到 O(n) 的变化,所有这些都是为了不编写通常非常简单的方法。
【解决方案4】:

你为什么不使用 destMap.putAll(originMap) ?

【讨论】:

    【解决方案5】:

    Map 有一个 putAll 方法。试试这样的:

        Map<String, String> destination = new HashMap<String, String>();
        Map<String, String> original = new HashMap<String, String>();
    
        destination.putAll(original);
    

    【讨论】:

    • 同样的结果。第二张地图的大小不同。
    【解决方案6】:

    这取决于第一个 HashMap 是如何初始化的。此外,每次将对象添加到 HashMap 时,一旦它通过 75% 的负载因子,它就会分配两倍的默认大小来容纳新值。地图通常具有默认大小 = 16:当您通过 75% 的负载系数时,它会放大到 32。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-12-04
      • 2021-07-17
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-03
      相关资源
      最近更新 更多