Java解惑之TreeSet是如何去重的
引言: 最近在处理一个问题,大致是这个样子,从数据库里面取出一个集合,取出来的数据放到一个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是如何去重的的更多相关文章
- Java中5种List的去重方法及它们的效率对比,你用对了吗?
01.使用两个for循环实现List去重(有序) /**使用两个for循环实现List去重(有序) * * @param list * */ public static ...
- Java解惑五:类之谜
本文是依据JAVA解惑这本书,做的笔记.电子书见:http://download.csdn.net/detail/u010378705/7527721 谜题46 函数重载的问题. JAVA重载解析过程 ...
- 【Java解惑】表达式问题
1. 如果判断一个参数是否是奇数? 我们通过下面代码来尝试一下,看看方法可行不: public static boolean isOdd(int i) { return i % 2 == 1; } p ...
- Java深入了解TreeSet
Java中的TreeSet是Set的一个子类,TreeSet集合是用来对象元素进行排序的,同样他也可以保证元素的唯一.那TreeSet为什么能保证元素唯一,它是怎样排序的呢?先看一段代码: publi ...
- Java解惑八:很多其它库之谜
本文是依据JAVA解惑这本书,做的笔记. 电子书见:http://download.csdn.net/detail/u010378705/7527721 谜题76 将线程的启动方法start(),写成 ...
- Java HashSet对txt文本内容去重(统计小说用过的字或字数)
Java HashSet对txt文本内容去重(统计小说用过的字或字数) 基本思路: 1.字节流读需要去重的txt文本.(展示demo为当前workspace下名为utf-8.txt的文本) 2.对读取 ...
- java解惑之常常忘记的事
java解惑之常常忘记的事 2012-10-17 18:38:57| 分类: JAVA | 标签:基础知识 软件开发 |举报|字号 订阅 针对刚接触java的菜鸟来说,java基础知识 ...
- Java HashSet和TreeSet【笔记】
Java HashSet和TreeSet[笔记] PS:HashSet.TreeSet 两个类是在 Map 的基础上组装起来的类 HashSet 类注释 1.底层实现基于 HashMap,所以迭代时不 ...
- JAVA中的TreeSet
TreeSet简介 TreeSet是一个有序的集合,它的作用是提供一个有序的Set集合,它继承于AbstractSet抽象类实现了NavigableSet<E>, Cloneable, j ...
随机推荐
- 容易忽略的expect脚本问题,暗藏的僵尸进程,wait命令不要漏掉
问题描述 前几天有个小需求,用到expect脚本去循环的发送一些数据,主要问题代码如下: #! /usr/bin/expect while {true} { set timeout 60 spawn ...
- C#多个if与if+多个else if有何不同?
int a=1; if(a==1){System.out.println("1");} if(a==2){System.out.println("2");} i ...
- (Oracle)DBMS_SYSTEM工具-01[20180510]
分析描述: 使用DBMS_SYSTEM分析SQL语句执行,并且获取SQL会话中的绑定变量.等待事件.消耗资源和执行计划等等. 环境介绍: Oracle 11.2.0.4.0 L ...
- 【Linux】进程管理
进程是什么? 程序 保存在硬盘.光盘等介质中的可执行代码和数据 是静态保存的代码 进程 在CPU及内存中运行的动态执行的程序代码 进程是程序运行的实例 同一个程序可能对应多个进程 子进程和父进 ...
- jQuery.validate.js表单验证插件
jQuery.validate.js表单验证插件的使用 效果: 代码: <!DOCTYPE html> <html lang="en"> <head& ...
- 执行mongod其他实例出现的问题
windows环境下,配置其他mongo实例,会出现一些问题 1.配置路径不对,执行bat文件时出现闪屏 根据提示创建C:\data\db\ 目录(因为mongodb默认在/data/db下创建数据库 ...
- FROM_UNIXTIME/CONCAT
将mysql查询结果中时间戳转化为时间格式 FROM_UNIXTIME( c.createtime, '%Y-%m-%d %H:%i:%S' ) 2个字段合并查询 CONCAT(d.`name`, ' ...
- Flink-on-yarn
介绍 官网下载 https://www.apache.org/dyn/closer.lua/flink/flink-1.6.1/flink-1.6.1-bin-hadoop28-scala_2.11. ...
- 大数据:Map终结和Spill文件合并
当Mapper没有数据输入,mapper.run中的while循环会调用context.nextKeyValue就返回false,于是便返回到runNewMapper中,在这里程序会关闭输入通道和输出 ...
- python学习笔记:第8天 文件操作
目录 1. 文件操作介绍 2. 文件操作的几种方式 3. 文件的操作的方法 1. 文件操作介绍 说到操作文件我们肯定会想到流,文件的操作都是通过流来操作的.在python中文件的操作非常简单,并不像J ...