引言: 最近在处理一个问题,大致是这个样子,从数据库里面取出一个集合,取出来的数据放到一个JavaBean里面。结果得到的集合长度为1.

TreeSetSet的一个实现,默认实现排序;故TreeSet的泛型类型必须是Comparable或者Comparator。TreeSet基于TreeMap实现。

实例

public class Person implements Comparable<Person>{
private String name;
private int order;
public Person(String name , int order){
this.name = name;
this.order = order;
}
public static void main(String [] args){
TreeSet<Person> users = new TreeSet<Person>();
users.add(new Person("Jason Kidd",1));
users.add(new Person("Kobe Bryant",1));
users.add(new Person("Jasme Harden",1));
users.add(new Person("Leborn James",1));
System.out.println(" size= "users.size());
}
// getter and setter
}
size= 1

认知被改写

上面代码的结果应该是4,因为我们没有重写hashCode和equals方法啊,Set的去重逻辑变了吗?

重写上面的例子:

public class Person implements Comparable<Person>{
public static void main(String [] args){
TreeSet<Person> users = new TreeSet<Person>();
users.add(new Person("Jason Kidd",1));
users.add(new Person("Kobe Bryant",2));
users.add(new Person("Jasme Harden",1));
users.add(new Person("Leborn James",1));
System.out.println(" size= "users.size());
}
// getter and setter
}
size= 2

表面看好像是compareTo方法觉得了去重;为了证实这个猜测,进入下面的代码里面去证实下吧。

源码分析

理论依据

 * <p>Note that the ordering maintained by a set (whether or not an explicit
* comparator is provided) must be <i>consistent with equals</i> if it is to
* correctly implement the {@code Set} interface. (See {@code Comparable}
* or {@code Comparator} for a precise definition of <i>consistent with
* equals</i>.) This is so because the {@code Set} interface is defined in
* terms of the {@code equals} operation, but a {@code TreeSet} instance
* performs all element comparisons using its {@code compareTo} (or
* {@code compare}) method, so two elements that are deemed equal by this method
* are, from the standpoint of the set, equal. The behavior of a set
* <i>is</i> well-defined even if its ordering is inconsistent with equals; it
* just fails to obey the general contract of the {@code Set} interface.
//翻译
注意,如果要正确实现 Set 接口,则 set 维护的顺序(无论是否提供了显式比较器)必须与 equals 一致。
(关于与 equals 一致 的精确定义,请参阅 Comparable 或 Comparator。)这是因为 Set 接口是按照
equals 操作定义的,但 TreeSet 实例使用它的 compareTo(或 compare)方法对所有元素进行比较
,因此从 set 的观点来看,此方法认为相等的两个元素就是相等的。即使 set 的顺序与 equals 不一致,
其行为也是 定义良好的;它只是违背了 Set 接口的常规协定。

上面提到了TreeSet的equal方法是依据Comparable或者Comparator接口判断的,和前面测试的结果一致。

源码解读:

代码流转:

前面提过了,TreeSet是底层是基于TreeMap存储数据的:

public class TreeSet{
public TreeSet() {
this(new TreeMap<E,Object>());
}
}

构造TreeSet的时候,并没有检查参数类型必须是Comparable或者Comparator的实现类。

public class TreeSet{
private static final Object PRESENT = new Object();
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
}

添加元素的操作,实际就是如何给TreeMap增加元素的操作。

// java.util.TreeMap.java
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}

上面一段看起来很长,我们重看其中几句就好了

  • TreeMap内置默认的Comparator比较器,一旦设定,后续所有的比较必须依据此比较器进行;发现不符合比较器方式的,抛出异常。
  • 核心的两个do-while循环,依据比较器结果变量__cmp__,判断是将值插入的觉提位置。一旦__cmp=0__将value放到相同位置。

相同的key会覆盖,所有前面测试的时候,集合长度不增加,主要因为compareTo方法返回了0.

Java解惑之TreeSet是如何去重的的更多相关文章

  1. Java中5种List的去重方法及它们的效率对比,你用对了吗?

    01.使用两个for循环实现List去重(有序) /**使用两个for循环实现List去重(有序)     *     * @param list     * */    public static  ...

  2. Java解惑五:类之谜

    本文是依据JAVA解惑这本书,做的笔记.电子书见:http://download.csdn.net/detail/u010378705/7527721 谜题46 函数重载的问题. JAVA重载解析过程 ...

  3. 【Java解惑】表达式问题

    1. 如果判断一个参数是否是奇数? 我们通过下面代码来尝试一下,看看方法可行不: public static boolean isOdd(int i) { return i % 2 == 1; } p ...

  4. Java深入了解TreeSet

    Java中的TreeSet是Set的一个子类,TreeSet集合是用来对象元素进行排序的,同样他也可以保证元素的唯一.那TreeSet为什么能保证元素唯一,它是怎样排序的呢?先看一段代码: publi ...

  5. Java解惑八:很多其它库之谜

    本文是依据JAVA解惑这本书,做的笔记. 电子书见:http://download.csdn.net/detail/u010378705/7527721 谜题76 将线程的启动方法start(),写成 ...

  6. Java HashSet对txt文本内容去重(统计小说用过的字或字数)

    Java HashSet对txt文本内容去重(统计小说用过的字或字数) 基本思路: 1.字节流读需要去重的txt文本.(展示demo为当前workspace下名为utf-8.txt的文本) 2.对读取 ...

  7. java解惑之常常忘记的事

    java解惑之常常忘记的事 2012-10-17 18:38:57|  分类: JAVA |  标签:基础知识  软件开发  |举报|字号 订阅     针对刚接触java的菜鸟来说,java基础知识 ...

  8. Java HashSet和TreeSet【笔记】

    Java HashSet和TreeSet[笔记] PS:HashSet.TreeSet 两个类是在 Map 的基础上组装起来的类 HashSet 类注释 1.底层实现基于 HashMap,所以迭代时不 ...

  9. JAVA中的TreeSet

    TreeSet简介 TreeSet是一个有序的集合,它的作用是提供一个有序的Set集合,它继承于AbstractSet抽象类实现了NavigableSet<E>, Cloneable, j ...

随机推荐

  1. SharePoint2010代码启动工作流

    1. private void StartWorkFlow() { //获得该列表上的发布的所有工作流 SPWorkflowAssociationCollection wfAssociationCol ...

  2. 「微信小程序」PHP异步进程async-helper实例详解

    PHP异步进程async-helper实例详解 PHP 的异步进程助手,借助于 AMQP 实现异步执行 PHP 的方法,将一些很耗时.追求高可用.需要重试机制的操作放到异步进程中去执行,将你的 HTT ...

  3. 小白的Unity5之路(二)镜头平滑跟随角色

    这次要完成Camera跟随Player移动, 首先考虑Camera的跟随目标target和平滑移动速度smothing再考虑Camera与Player的偏移量(就是Camera与Player有一个永恒 ...

  4. Scala 语法基础

    一 简介 Scala 是一门多范式(multi-paradigm)的编程语言,设计初衷是要集成面向对象编程和函数式编程的各种特性.Scala 运行在Java虚拟机上,并兼容现有的Java程序.Scal ...

  5. PHP-----PHP程序设计基础教程----第二章PHP基本语法

    博文结构: 2.1 PHP语法风格 2.1.1 PHP标记 PHP有四种风格的标记,具体如表2-1所示: 表2-1 PHP开始和结束标记 标记类型 开始标记 结束标记 示例 说明 标准标记 <? ...

  6. 笔记:css中的position定位

    position的值可以是:static,relative,absolute,fixed. 默认值是 static.设置 left.top值无效. relative是相对定位,可以设置left.top ...

  7. Java实例 Part4:数组及其常用操作

    目录 Part4:数组及其常用操作 Example01:将二维数组的行列交换 Example02:使用选择排序法对数组进行排序 Example03:使用冒泡排序法对数组进行排序 Example04:使 ...

  8. Django 中跨 app 创建外键、多对多引用的方法

    问题描述 我的 Django 项目中有两个 app. 在 PersonalCenter app下的 models.py 下定义了一个 Footprint 类: 在 LoginAndRegister a ...

  9. C语言之数组和函数

    数组 一维数组 定义:类型符 数组名 [常量表达式]  int a[]={1,2,3,4,5,}; #include<stdio.h> #define NUM 6 void main() ...

  10. PetaLinux安装及使用

    Description/说明 PetaLinux版本:2016.4 操作系统版本:Ubuntu 16.04(如使用Ubuntu,墙裂建议使用16.04,其他版本官方手册并没有标明支持,可能会出现莫名其 ...