【问题标题】:How to compare two maps by their values如何按值比较两张地图
【发布时间】:2011-02-10 01:38:35
【问题描述】:

如何通过值比较两张地图?我有两个包含相等值的地图,并希望通过它们的值来比较它们。这是一个例子:

    Map a = new HashMap();
    a.put("foo", "bar"+"bar");
    a.put("zoo", "bar"+"bar");

    Map b = new HashMap();
    b.put(new String("foo"), "bar"+"bar");
    b.put(new String("zoo"), "bar"+"bar");

    System.out.println("equals: " + a.equals(b));            // obviously false

我应该如何更改代码以获得真实的?

【问题讨论】:

  • 现在应该很清醒,因为这两张地图至少在一个意义上是相同的......
  • @lewap:你在寻找什么样的相似性? “此映射具有与该映射相同的一组(键,值)映射”的相似性?
  • -1 - 这个问题完全模棱两可。

标签: java dictionary collections compare equals


【解决方案1】:

如果有人希望在 Java 8 流中执行此操作,则示例如下。

import java.util.HashMap;
import java.util.Map;

public class CompareTwoMaps {

    public static void main(String[] args) {

        Map<String, String> a = new HashMap<>();
        a.put("foo", "bar" + "bar");
        a.put("zoo", "bar" + "bar");

        Map<String, String> b = new HashMap<>();
        b.put(new String("foo"), "bar" + "bar");
        b.put(new String("zoo"), "bar" + "bar");

        System.out.println("result = " + areEqual(a, b));
    }

    private static boolean areEqual(Map<String, String> first, Map<String, String> second) {
        return first.entrySet().stream()
                .allMatch(e -> e.getValue().equals(second.get(e.getKey())));
    }
}

【讨论】:

    【解决方案2】:

    既然您询问了现成的 Api 的...以及 Apache 的公地。 collections 库有一个 CollectionUtils 类,该类为 Collection 操作/检查提供易于使用的方法,例如交集、差异和联合。

    【讨论】:

      【解决方案3】:

      您尝试使用连接构造不同的字符串将失败,因为它是在编译时执行的。这两张地图都有一对;每对都有“foo”和“barbar”作为键/值,都使用相同的字符串引用。

      假设您真的想在不引用键的情况下比较值集,这只是以下情况:

      Set<String> values1 = new HashSet<>(map1.values());
      Set<String> values2 = new HashSet<>(map2.values());
      boolean equal = values1.equals(values2);
      

      可能将 map1.values() 与 map2.values() 进行比较会起作用 - 但它们返回的顺序也有可能用于相等比较,这不是你想要的。

      请注意,使用集合有其自身的问题 - 因为上面的代码会将 {"a":"0", "b":"0"} 和 {"c":"0"} 的映射视为相等...毕竟值集是相等的。

      如果您可以对您想要的内容提供更严格的定义,那么确保我们为您提供正确的答案会更容易。

      【讨论】:

      • 为什么要使用额外的空间?
      • @Manu:实现我相信 OP 想要的结果。您的答案比较键和值。例如,我相信 OP 希望 {"a"=1, "b"=2 } 的地图等于 {"c"=2, "d"=1} 的地图。
      • OP 在这方面模棱两可。如果是纯粹的值比较,则不确定 [1] 他想如何处理重复项 [2] 他是否想通过它的键来比较值。现在,由于您的答案是“接受”,看起来他想复制忽略的值比较。
      • @Manu:确实。这就是为什么我在可能的情况下要求进一步澄清,并明确给出了一个使用重复的例子 - 但鉴于 OP 接受了我的回答,我假设 他们正在寻找什么。
      【解决方案4】:

      这个问题很老,但仍然相关。

      如果你想通过它们的值匹配它们的键来比较两个地图,你可以这样做:

      public static <K, V> boolean mapEquals(Map<K, V> leftMap, Map<K, V> rightMap) {
          if (leftMap == rightMap) return true;
          if (leftMap == null || rightMap == null || leftMap.size() != rightMap.size()) return false;
          for (K key : leftMap.keySet()) {
              V value1 = leftMap.get(key);
              V value2 = rightMap.get(key);
              if (value1 == null && value2 == null)
                  continue;
              else if (value1 == null || value2 == null)
                  return false;
              if (!value1.equals(value2))
                  return false;
          }
          return true;
      }
      

      【讨论】:

      • @uı6ʎɹnɯlǝıuɐp 是的,仅此而已。
      【解决方案5】:

      比较地图是否相等的正确方法是:

      1. 检查地图大小是否相同(!)
      2. 从一个地图中获取一组键
      3. 对于您检索的该集合中的每个键,检查从每个映射中检索到的该键的值是否相同(如果该键在一个映射中不存在,则完全不相等)

      换句话说(减去错误处理):

      boolean equalMaps(Map<K,V>m1, Map<K,V>m2) {
         if (m1.size() != m2.size())
            return false;
         for (K key: m1.keySet())
            if (!m1.get(key).equals(m2.get(key)))
               return false;
         return true;
      }
      

      【讨论】:

      • 这和Map.equals有什么不同?
      • 对于价值平等,这是一个过于严格的测试。它不仅测试值相等,还测试键值映射相等。
      • @CPerkins:我想我只是没有看到仅比较值的意义。房间里至少缺少一整群大象;不同的地图只是不相同。 @poly:嗯,没有什么不同。我想这就是为什么定义相等操作的原因。 :-) 我认为最初的问题是由提问者更新错误的地图或同样基本的东西引起的。
      • 我同意你的观点,但 OP 似乎在询问值平等,而不是键值映射平等。否则这个问题没有意义。
      • @polygenelubricants(很久很久以后……)真正的区别在于Map.equals 对null 的键和值很谨慎(而且效率也更高一些)。
      【解决方案6】:
      public boolean equalMaps(Map<?, ?> map1, Map<?, ?>map2) {
      
          if (map1==null || map2==null || map1.size() != map2.size()) {
              return false;
          }
      
          for (Object key: map1.keySet()) {
              if (!map1.get(key).equals(map2.get(key))) {
                  return false;
              }
          }
          return true;
      }
      

      【讨论】:

        【解决方案7】:

        @paweloque 为了比较 java 中的两个 Map 对象,您可以将映射的键添加到列表中,并且对于这两个列表,您可以使用方法 retainAll() 和 removeAll() 并将它们添加到另一个公共键列表和不同的键列表。使用 common list 和 different list 的键可以遍历 map,使用 equals 可以比较 map。

        以下代码将给出如下输出: {zoo=barbar, foo=barbar} 之前 {zoo=barbar, foo=barbar} 之后 相等:前- barbar 后- barbar 相等:前- barbar 后- barbar

        package com.demo.compareExample
        
        import java.util.ArrayList;
        import java.util.HashMap;
        import java.util.Iterator;
        import java.util.List;
        import java.util.Map;
        
        import org.apache.commons.collections.CollectionUtils;
        
        public class Demo 
        {
            public static void main(String[] args) 
            {
                Map<String, String> beforeMap = new HashMap<String, String>();
                beforeMap.put("foo", "bar"+"bar");
                beforeMap.put("zoo", "bar"+"bar");
        
                Map<String, String> afterMap = new HashMap<String, String>();
                afterMap.put(new String("foo"), "bar"+"bar");
                afterMap.put(new String("zoo"), "bar"+"bar");
        
                System.out.println("Before "+beforeMap);
                System.out.println("After "+afterMap);
        
                List<String> beforeList = getAllKeys(beforeMap);
        
                List<String> afterList = getAllKeys(afterMap);
        
                List<String> commonList1 = beforeList;
                List<String> commonList2 = afterList;
                List<String> diffList1 = getAllKeys(beforeMap);
                List<String> diffList2 = getAllKeys(afterMap);
        
                commonList1.retainAll(afterList);
                commonList2.retainAll(beforeList);
        
                diffList1.removeAll(commonList1);
                diffList2.removeAll(commonList2);
        
                if(commonList1!=null & commonList2!=null) // athough both the size are same
                {
                    for (int i = 0; i < commonList1.size(); i++) 
                    {
                        if ((beforeMap.get(commonList1.get(i))).equals(afterMap.get(commonList1.get(i)))) 
                        {
                            System.out.println("Equal: Before- "+ beforeMap.get(commonList1.get(i))+" After- "+afterMap.get(commonList1.get(i)));
                        }
                        else
                        {
                            System.out.println("Unequal: Before- "+ beforeMap.get(commonList1.get(i))+" After- "+afterMap.get(commonList1.get(i)));
                        }
                    }
                }
                if (CollectionUtils.isNotEmpty(diffList1)) 
                {
                    for (int i = 0; i < diffList1.size(); i++) 
                    {
                        System.out.println("Values present only in before map: "+beforeMap.get(diffList1.get(i)));
                    }
                }
                if (CollectionUtils.isNotEmpty(diffList2)) 
                {
                    for (int i = 0; i < diffList2.size(); i++) 
                    {
                        System.out.println("Values present only in after map: "+afterMap.get(diffList2.get(i)));
                    }
                }
            }
        
            /**getAllKeys API adds the keys of the map to a list */
            private static List<String> getAllKeys(Map<String, String> map1)
            {
                List<String> key = new ArrayList<String>();
                if (map1 != null) 
                {
                    Iterator<String> mapIterator = map1.keySet().iterator();
                    while (mapIterator.hasNext()) 
                    {
                        key.add(mapIterator.next());
                    }
                }
                return key;
            }
        }
        

        【讨论】:

          【解决方案8】:

          如果你想比较两个地图,下面的代码可能会对你有所帮助

          (new TreeMap<String, Object>(map1).toString().hashCode()) == new TreeMap<String, Object>(map2).toString().hashCode()
          

          【讨论】:

          • 这行不通。不同的字符串可以具有相同的哈希码,== 运算符比较对象引用。因此,两个 new 实例永远不会成立。正确答案是使用equals。
          • String.hashCode() 将返回 int,因此 == 可以正常工作。实际上,在这个例子中,两个不同的 Map 会出现相同的哈希码是非常罕见的。如果不是hashCode,我们可以使用MD5。
          • 如果多个字符串可以返回相同的哈希码,则不会 - 这是可能的。最好推荐一些可以正常工作的东西,而不仅仅是“有时”。
          【解决方案9】:

          所有这些都返回等于。他们实际上并没有进行比较,这对排序很有用。这将更像一个比较器:

          private static final Comparator stringFallbackComparator = new Comparator() {
              public int compare(Object o1, Object o2) {
                  if (!(o1 instanceof Comparable))
                      o1 = o1.toString();
                  if (!(o2 instanceof Comparable))
                      o2 = o2.toString();
                  return ((Comparable)o1).compareTo(o2);
              }
          };
          
          public int compare(Map m1, Map m2) {
              TreeSet s1 = new TreeSet(stringFallbackComparator); s1.addAll(m1.keySet());
              TreeSet s2 = new TreeSet(stringFallbackComparator); s2.addAll(m2.keySet());
              Iterator i1 = s1.iterator();
              Iterator i2 = s2.iterator();
              int i;
              while (i1.hasNext() && i2.hasNext())
              {
                  Object k1 = i1.next();
                  Object k2 = i2.next();
                  if (0!=(i=stringFallbackComparator.compare(k1, k2)))
                      return i;
                  if (0!=(i=stringFallbackComparator.compare(m1.get(k1), m2.get(k2))))
                      return i;
              }
              if (i1.hasNext())
                  return 1;
              if (i2.hasNext())
                  return -1;
              return 0;
          }
          

          【讨论】:

            【解决方案10】:

            如果您假设可能存在重复值,则唯一的方法是将值放入列表中,对它们进行排序并比较列表,即:

            List<String> values1 = new ArrayList<String>(map1.values());
            List<String> values2 = new ArrayList<String>(map2.values());
            Collections.sort(values1);
            Collections.sort(values2);
            boolean mapsHaveEqualValues = values1.equals(values2);
            

            如果值不能包含重复值,那么您可以在不使用集合的情况下执行上述操作。

            【讨论】:

            • 我们都希望Collections.sort 是“流动的”(我认为这是术语),但不,它返回void。
            • 呃,是的,我的错。对其进行了编辑以解决该问题。
            【解决方案11】:

            我认为没有“类 apache-common”工具来比较地图,因为 2 个地图的相等性非常模糊,并且取决于开发人员的需求和地图的实现......

            例如,如果您在 java 中比较两个哈希图: - 您可能只想比较键/值是否相同 - 您可能还想比较键是否以相同的方式排序 - 您可能还想比较剩余容量是否相同 ...你可以比较很多东西!

            当比较 2 个不同的地图实现时,这样的工具会做什么: - 一张地图允许空键 - 另一个在 map2.get(null) 上抛出运行时异常

            你最好根据你真正需要做的事情来实现你自己的解决方案,我想你已经在上面得到了一些答案:)

            【讨论】:

              【解决方案12】:

              要查看两张地图是否具有相同的值,您可以执行以下操作:

              • 获取他们的Collection&lt;V&gt; values() 视图
              • 换成List&lt;V&gt;
              • Collections.sort那些名单
              • 测试两个列表是否为equals

              类似这样的工作(尽管它的类型界限可以改进):

              static <V extends Comparable<V>>
              boolean valuesEquals(Map<?,V> map1, Map<?,V> map2) {
                  List<V> values1 = new ArrayList<V>(map1.values());
                  List<V> values2 = new ArrayList<V>(map2.values());
                  Collections.sort(values1);
                  Collections.sort(values2);
                  return values1.equals(values2);
              }
              

              测试工具:

              Map<String, String> map1 = new HashMap<String,String>();
              map1.put("A", "B");
              map1.put("C", "D");
              
              Map<String, String> map2 = new HashMap<String,String>();
              map2.put("A", "D");
              map2.put("C", "B");
              
              System.out.println(valuesEquals(map1, map2)); // prints "true"
              

              由于Collections.sort,这是O(N log N)。

              另见:


              测试 keys 是否相等更容易,因为它们是 Set&lt;K&gt;:

              map1.keySet().equals(map2.keySet())
              

              另见:

              【讨论】:

              • 那无济于事。如果map1 有 (A=>B,C=>D) 和 map2 有 (A=>D,C=>B) 那么它们具有相同的值和键(甚至可能以相同的方式枚举它们)顺序;不能保证!)但仍然明显不同。
              • @Donald:添加了排序列表版本和您的测试工具;它现在应该可以工作了。
              【解决方案13】:

              在您的示例中,equals 的结果显然是错误的,因为您正在将地图 a 与其中的一些值与空地图 b 进行比较(可能是复制和粘贴错误)。我建议使用正确的变量名(这样可以避免此类错误)并使用泛型。

                  Map<String, String> first = new HashMap<String, String>();
                  first.put("f"+"oo", "bar"+"bar");
                  first.put("fo"+"o", "bar"+"bar");
              
                  Map second = new HashMap();
                  second.put("f"+"oo", "bar"+"bar");
                  second.put("fo"+"o", "bar"+"bar");
              
                  System.out.println("equals: " + first.equals(second));
              

              字符串的连接没有任何影响,因为它将在编译时完成。

              【讨论】:

                猜你喜欢
                • 2014-09-08
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多