简体   繁体   English

比较两张地图

[英]Comparing two maps

I have two maps declared as Map<String, Object> .我有两个地图声明为Map<String, Object> The Object here could be another Map<String, Object> (and so on).这里的Object可以是另一个Map<String, Object> (等等)。 I want to check if two maps are exactly the same without knowing their depth.我想在不知道深度的情况下检查两张地图是否完全相同。 Instead of using recursion can I compare the output of the toString() called on each map?我可以比较在每个地图上调用的toString()的输出而不是使用递归吗? Or is there a simpler way to compare the maps?或者有没有更简单的方法来比较地图?

Quick Answer快速回答

You should use the equals method since this is implemented to perform the comparison you want.您应该使用equals方法,因为这是为了执行您想要的比较而实现的。 toString() itself uses an iterator just like equals but it is a more inefficient approach. toString()本身像equals一样使用迭代器,但它是一种效率更低的方法。 Additionally, as @Teepeemm pointed out, toString is affected by order of elements (basically iterator return order) hence is not guaranteed to provide the same output for 2 different maps (especially if we compare two different maps).此外,正如@Teepeemm 指出的那样, toString受元素顺序(基本上是迭代器返回顺序)的影响,因此不能保证为 2 个不同的映射提供相同的输出(尤其是当我们比较两个不同的映射时)。

Note/Warning : Your question and my answer assume that classes implementing the map interface respect expected toString and equals behavior.注意/警告:您的问题和我的回答假定实现地图接口的类尊重预期的toStringequals行为。 The default java classes do so, but a custom map class needs to be examined to verify expected behavior.默认 java 类这样做,但需要检查自定义地图类以验证预期行为。

See: http://docs.oracle.com/javase/7/docs/api/java/util/Map.html请参阅: http : //docs.oracle.com/javase/7/docs/api/java/util/Map.html

boolean equals(Object o)

Compares the specified object with this map for equality.比较指定对象与此映射是否相等。 Returns true if the given object is also a map and the two maps represent the same mappings .如果给定对象也是一个映射并且这两个映射表示相同的映射,则返回 true。 More formally, two maps m1 and m2 represent the same mappings if m1.entrySet().equals(m2.entrySet()) .更正式地,如果 m1.entrySet().equals(m2.entrySet()) ,则两个映射 m1 和 m2 表示相同的映射 This ensures that the equals method works properly across different implementations of the Map interface.这确保了 equals 方法可以在 Map 接口的不同实现中正常工作。

Implementation in Java Source (java.util.AbstractMap) Java Source 中的实现(java.util.AbstractMap)

Additionally, java itself takes care of iterating through all elements and making the comparison so you don't have to.此外,java 本身负责遍历所有元素并进行比较,因此您不必这样做。 Have a look at the implementation of AbstractMap which is used by classes such as HashMap :看看AbstractMap的实现,它被诸如HashMap类的类使用:

 // Comparison and hashing

    /**
     * Compares the specified object with this map for equality.  Returns
     * <tt>true</tt> if the given object is also a map and the two maps
     * represent the same mappings.  More formally, two maps <tt>m1</tt> and
     * <tt>m2</tt> represent the same mappings if
     * <tt>m1.entrySet().equals(m2.entrySet())</tt>.  This ensures that the
     * <tt>equals</tt> method works properly across different implementations
     * of the <tt>Map</tt> interface.
     *
     * <p>This implementation first checks if the specified object is this map;
     * if so it returns <tt>true</tt>.  Then, it checks if the specified
     * object is a map whose size is identical to the size of this map; if
     * not, it returns <tt>false</tt>.  If so, it iterates over this map's
     * <tt>entrySet</tt> collection, and checks that the specified map
     * contains each mapping that this map contains.  If the specified map
     * fails to contain such a mapping, <tt>false</tt> is returned.  If the
     * iteration completes, <tt>true</tt> is returned.
     *
     * @param o object to be compared for equality with this map
     * @return <tt>true</tt> if the specified object is equal to this map
     */
    public boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Map))
            return false;
        Map<K,V> m = (Map<K,V>) o;
        if (m.size() != size())
            return false;

        try {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(m.get(key)))
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

Comparing two different types of Maps比较两种不同类型的地图

toString fails miserably when comparing a TreeMap and HashMap though equals does compare contents correctly. toString在比较TreeMapHashMap时失败得很惨,尽管equals确实比较了内容。

Code:代码:

public static void main(String args[]) {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("2", "whatever2");
map.put("1", "whatever1");
TreeMap<String, Object> map2 = new TreeMap<String, Object>();
map2.put("2", "whatever2");
map2.put("1", "whatever1");

System.out.println("Are maps equal (using equals):" + map.equals(map2));
System.out.println("Are maps equal (using toString().equals()):"
        + map.toString().equals(map2.toString()));

System.out.println("Map1:"+map.toString());
System.out.println("Map2:"+map2.toString());
}

Output:输出:

Are maps equal (using equals):true
Are maps equal (using toString().equals()):false
Map1:{2=whatever2, 1=whatever1}
Map2:{1=whatever1, 2=whatever2}

As long as you override equals() on each key and value contained in the map, then m1.equals(m2) should be reliable to check for maps equality.只要您在映射中包含的每个键和值上覆盖equals() ,那么m1.equals(m2)应该可靠地检查映射的相等性。

The same result can be obtained also by comparing toString() of each map as you suggested, but using equals() is a more intuitive approach.通过按照您的建议比较每个地图的toString()也可以获得相同的结果,但使用equals()是一种更直观的方法。

May not be your specific situation, but if you store arrays in the map, may be a little tricky, because they must be compared value by value, or using Arrays.equals() .可能不是您的具体情况,但如果您将数组存储在地图中,可能会有点棘手,因为它们必须逐个进行比较,或者使用Arrays.equals() More details about this see here .有关这方面的更多详细信息,请参阅此处

Comparing two Maps:比较两张地图:

declare:宣布:

enum Activity{
    ADDED,
    REMOVED,
    MODIFIED
}

@Data
@NoArgsConstructor
@AllArgsConstructor
static class FileStateRow {
    String key;
    String value;
    Activity activity;
}

BiFunction<Map<String, Object>, Map<String, Object>, Map<String, FileStateRow>> 
mapCompare = (newMap, oldMap) -> {
    Map<String, FileStateRow> resMap = new HashMap<>();
    newMap.forEach((k, v) -> {
        if (!oldMap.containsKey(k)) {
            System.out.println("newMap key:" + k + " is missing in oldMap - ADDED");
            resMap.put(k, new FileStateRow(k, (String) v, Activity.ADDED));
        } else {
            if (oldMap.get(k) != null && !oldMap.get(k).equals(v)) {
                System.out.println("newMap value change for key:" + k + ", old:" + oldMap.get(k) + ", new " + v);
                resMap.put(k, new FileStateRow(k, (String) v, Activity.MODIFIED));
            }
        }
    }); 

    oldMap.forEach((k, v) -> {
        if (!newMap.containsKey(k)) {
            System.out.println("newMap key:" + k + " is missing in oldMap");
            resMap.put(k, new FileStateRow(k, (String) v, Activity.REMOVED));
        }
    }); 
    return resMap;
};

use:用:

Map<String, Object> map1 = .. // initiate and put values in..
Map<String, Object> map2 = .. // initiate and put values in..

// compare...
Map<String, FileStateRow> res = mapCompare.apply(map1, map2);

// print results
res.forEach((k, v) -> {
     System.out.println("key:" + k + ",  value " + v);
});

by yl.伊尔。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM