引言: 最近在处理一个问题,大致是这个样子,从数据库里面取出一个集合,取出来的数据放到一个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. 容易忽略的expect脚本问题,暗藏的僵尸进程,wait命令不要漏掉

    问题描述 前几天有个小需求,用到expect脚本去循环的发送一些数据,主要问题代码如下: #! /usr/bin/expect while {true} { set timeout 60 spawn ...

  2. C#多个if与if+多个else if有何不同?

    int a=1; if(a==1){System.out.println("1");} if(a==2){System.out.println("2");} i ...

  3. (Oracle)DBMS_SYSTEM工具-01[20180510]

    分析描述:     使用DBMS_SYSTEM分析SQL语句执行,并且获取SQL会话中的绑定变量.等待事件.消耗资源和执行计划等等. 环境介绍:     Oracle 11.2.0.4.0     L ...

  4. 【Linux】进程管理

    进程是什么? 程序 保存在硬盘.光盘等介质中的可执行代码和数据 是静态保存的代码 进程 在CPU及内存中运行的动态执行的程序代码 进程是程序运行的实例 同一个程序可能对应多个进程     子进程和父进 ...

  5. jQuery.validate.js表单验证插件

    jQuery.validate.js表单验证插件的使用 效果: 代码: <!DOCTYPE html> <html lang="en"> <head& ...

  6. 执行mongod其他实例出现的问题

    windows环境下,配置其他mongo实例,会出现一些问题 1.配置路径不对,执行bat文件时出现闪屏 根据提示创建C:\data\db\ 目录(因为mongodb默认在/data/db下创建数据库 ...

  7. FROM_UNIXTIME/CONCAT

    将mysql查询结果中时间戳转化为时间格式 FROM_UNIXTIME( c.createtime, '%Y-%m-%d %H:%i:%S' ) 2个字段合并查询 CONCAT(d.`name`, ' ...

  8. Flink-on-yarn

    介绍 官网下载 https://www.apache.org/dyn/closer.lua/flink/flink-1.6.1/flink-1.6.1-bin-hadoop28-scala_2.11. ...

  9. 大数据:Map终结和Spill文件合并

    当Mapper没有数据输入,mapper.run中的while循环会调用context.nextKeyValue就返回false,于是便返回到runNewMapper中,在这里程序会关闭输入通道和输出 ...

  10. python学习笔记:第8天 文件操作

    目录 1. 文件操作介绍 2. 文件操作的几种方式 3. 文件的操作的方法 1. 文件操作介绍 说到操作文件我们肯定会想到流,文件的操作都是通过流来操作的.在python中文件的操作非常简单,并不像J ...