TreeSet显示错误的输出

当我在树上工作时,发现了非常奇怪的行为。 根据我的理解,这个程序应该打印两个相同的行:

public class TestSet { static void test(String... args) { Set<String> s = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); s.addAll(Arrays.asList("a", "b")); s.removeAll(Arrays.asList(args)); System.out.println(s); } public static void main(String[] args) { test("A"); test("A", "C"); } } 

但奇怪的是它打印:

 [b] [a, b] 

为什么树的行为是这样的?

发生这种情况是因为SortedSet的比较器用于sorting,但removeAll依赖于每个元素的equals方法。 从SortedSet文档 :

请注意,如果有序集合要正确实现Set接口,则由有序集合(不论是否提供显式比较器)维护的sorting必须与equals一致 。 (请参阅Comparable接口或Comparator接口以获得与equals一致的精确定义) 这是因为Set接口是根据equals操作定义的,但是有序集使用compareTo (或compare )方法执行所有元素比较,所以这个方法被认为是相等的两个元素从sorting集的angular度来看是相等的。 即使sorting与等号不一致,sorting集合的行为也是明确的。 它只是不服从Set接口的总体合同。

在“可比较”文档中定义了“与equals一致”的解释:

当且仅当e1.compareTo(e2) == 0e1.equals(e2)具有相同的布尔值e1.equals(e2)对于C类的每个e1e2 e1.equals(e2)时,才说C类的自然顺序与equals相一致 。 请注意, null不是任何类的实例,即使e.equals(null)返回falsee.compareTo(null)应抛出NullPointerException

强烈build议(尽pipe不要求)自然sorting与平等一致。 这是因为sorting集(和sorting映射)没有明确的比较器,当它们与自然sorting与equals不一致的元素(或键)一起使用时,performance“奇怪”。 特别是,这样一个有序的集合(或有序的映射)违反了集合(或映射)的一般契约,这是根据equals方法定义的。

总之,你的Set的比较器的行为与元素的equals方法不同,导致不寻常的(虽然是可预测的)行为。

那么,这让我感到惊讶,我不知道我是否正确,但看看AbstractSet中的这个实现:

 public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); boolean modified = false; if (size() > c.size()) { for (Iterator<?> i = c.iterator(); i.hasNext(); ) modified |= remove(i.next()); } else { for (Iterator<?> i = iterator(); i.hasNext(); ) { if (c.contains(i.next())) { i.remove(); modified = true; } } } return modified; } 

基本上在你的例子中,set的大小等于你想移除的参数的大小,所以else条件被调用。 在这种情况下,检查你的要移除的参数集合是否contains迭代器的当前元素,并且该检查是区分大小写的,因此它检查c.contains("a")是否返回false,因为c包含"A" ,而不是"a" ,所以元素不会被删除。 请注意,当你添加一个元素到你的设置s.addAll(Arrays.asList("a", "b", "d")); 它工作正常,因为size() > c.size()现在是true,因此没有contains检查了。

要添加一些关于为什么remove TreeSet实际上在您的示例中删除大小写的信息(并提供您按照if (size() > c.size())的回答中解释的if (size() > c.size())path:

这是TreeSetremove方法:

 public boolean remove(Object o) { return m.remove(o)==PRESENT; } 

它调用从其内部的TreeMap remove

 public V remove(Object key) { Entry<K,V> p = getEntry(key); if (p == null) return null; V oldValue = p.value; deleteEntry(p); return oldValue; } 

它调用getEntry

  final Entry<K,V> getEntry(Object key) { // Offload comparator-based version for sake of performance if (comparator != null) return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; Entry<K,V> p = root; while (p != null) { int cmp = k.compareTo(p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; } 

如果有一个Comparator (如你的例子),这个条目是根据这个Comparator (这是由getEntryUsingComparator完成的)来search的,这就是为什么它实际上被find(然后被移除),尽pipe有不同的情况。

这很有趣,所以这里有一些输出testing:

 static void test(String... args) { Set<String> s =new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); s.addAll(Arrays.asList( "a","b","c")); s.removeAll(Arrays.asList(args)); System.out.println(s); } public static void main(String[] args) { test("C"); output: [a, b] test("C", "A"); output: [b] test("C", "A","B"); output: [a, b, c] test("B","C","A"); output: [a, b, c] test("K","C"); output: [a, b] test("C","K","M"); output: [a, b, c] !! test("C","K","A"); output: [a, b, c] !! } 

现在没有比较器,它就像一个sorting的HashSet<String>()

  static void test(String... args) { Set<String> s = new TreeSet<String>();// s.addAll(Arrays.asList( "a","b","c")); s.removeAll(Arrays.asList(args)); System.out.println(s); } public static void main(String[] args) { test("c"); output: [a, b] test("c", "a"); output: [b] test("c", "a","b"); output: [] test("b","c","a"); output: [] test("k","c"); output: [a, b] test("c","k","m"); output: [a, b] test("c","k","m"); output: [a, b] } 

现在从文档:

public boolean removeAll(Collection c)

从该集合中移除指定集合中包含的所有元素(可选操作)。 如果指定集合也是集合,则此操作将有效地修改此集合,使其值为两个集合的非对称集合差异。

这个实现通过调用每一个的size方法来确定这个集合和指定的集合中哪一个更小。 如果这个集合有更less的元素,那么这个实现迭代这个集合,依次检查迭代器返回的每个元素,看看它是否包含在指定的集合中。 如果包含它,则使用迭代器的remove方法将其从此集中移除。 如果指定的集合有更less的元素,那么实现迭代指定的集合,从这个集合中删除迭代器返回的每个元素,使用这个集合的remove方法。

资源