Java中的TreeMap、Comparable、Comparator
我们知道HashMap的存储位置是按照key这个对象的hashCode来存放的,而TreeMap则是不是按照hashCode来存放,他是按照实现的Comparable接口的compareTo这个方法来存储的,只要compareTo的返回结果为0就表示两个对象相等,那么就存不进去两个对象,后put的就把前面的覆盖掉,甚至我们都不用重写equasls和hashCode方法,而只需要实现Comparable接口来重写comparareTo方法就行了,但是我们不能保证在应用中不会用到HashMap,所以保持良好的习惯,当我们定义了一个对象之后习惯性的重写equals和hashCode方法。本文比较详细的解释了TreeMap、Comparable、Comparator这三者的关联。
测试Comparable接口:
第一次比较:定义一个User类,实现Comparable接口,按照年龄排序,我们让equals为true,而hashCode也始终相等。
public class User implements Comparable<User> {
private String id;
private String name;
private Integer age;
public User() {
}
public User(String id, String name, Integer age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
@Override
public boolean equals(Object obj) {
return true;
}
@Override
public int hashCode() {
return 0;
}
public int compareTo(User o) {
return this.age > o.getAge() ? 1 : this.age == o.getAge() ? 0 : -1;
}
}
测试代码:
public class TestUser {
public static void main(String[] args) {
Map<User, Integer> userHashMap = new HashMap<User, Integer>();
User user1 = new User("1", "Jay", 30);
User user2 = new User("2", "Jolin", 21);
User user3 = new User("3", "Jack Cheng", 22);
User user4 = new User("4", "Bruce Lee", 22);
userHashMap.put(user1, 100);
userHashMap.put(user2, 200);
userHashMap.put(user3, 300);
userHashMap.put(user4, 400);
System.out.println(userHashMap);
Map<User, Integer> userTreeMap = new TreeMap<User, Integer>();
userTreeMap.put(user1, 100);
userTreeMap.put(user2, 200);
userTreeMap.put(user3, 300);
userTreeMap.put(user4, 400);
System.out.println(userTreeMap);
}
}
结果:
{User [name=Jay, age=30]=400}
{User [name=Jolin, age=21]=200, User [name=Jack Cheng, age=22]=400, User [name=Jay, age=30]=100}
结论:对于HashMap而言,只要key的equals相等就表示两个元素相等,HashMap就存不进去;而TreeMap是不管equals和hashCode的,只要compareTo相等就表示两个元素相同,就存不进去。
2.第二次比较:现在我们按照id来重写hashCode和equals方法,如下:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
} @Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
测试代码不变,还是上面的测试代码,结果:
{User [name=Jolin, age=21]=200, User [name=Jay, age=30]=100, User [name=Bruce Lee, age=22]=400, User [name=Jack Cheng, age=22]=300}
{User [name=Jolin, age=21]=200, User [name=Jack Cheng, age=22]=400, User [name=Jay, age=30]=100}
说明:HashMap只要equals不等那就表示不等,而对于TreeMap如果compareTo相等,那么2个元素就相等,并且排序是按照compareTo方法定义的排序规则。
接下来再在测试代码里面添加List测试:
List<User> userList = new ArrayList<User>();
userList.add(user1);
userList.add(user2);
userList.add(user3);
userList.add(user4); System.out.println(userList); Collections.sort(userList);
System.out.println(userList);
结果:
{User [name=Jolin, age=21]=200, User [name=Jay, age=30]=100, User [name=Bruce Lee, age=22]=400, User [name=Jack Cheng, age=22]=300}
{User [name=Jolin, age=21]=200, User [name=Jack Cheng, age=22]=400, User [name=Jay, age=30]=100}
[User [name=Jay, age=30], User [name=Jolin, age=21], User [name=Jack Cheng, age=22], User [name=Bruce Lee, age=22]]
[User [name=Jolin, age=21], User [name=Jack Cheng, age=22], User [name=Bruce Lee, age=22], User [name=Jay, age=30]]
当调用sort方法之后List里面的元素就按照age排序了。
对于Comparator,一种叫做策略模式的设计模式,我们在User中实现Comparable接口,重写里面的方法,可以实现对User按age排序,这个排序算法是存在于User内部的,换句话说User和排序算法是相互依存的,User只能用compareTo这个算法,而compareTo算法也只能为User服务,那么如果我们其他地方也有同样的需求,就只能再实现一次Comparable,来为另外一个对象服务,这就有点重复的感觉,而且代码没有得到复用,样子对象和算法混在一起耦合性很强,于是希望把算法和模型分离出来,让算法单独存在,不同的对象可以使用同一个算法,并且二者是分离的,没有混为一体。当然可以分开那必然还是可以放在一起的。用一句比较专业点的话来讲就是策略模式有两个特点:1.封装变化。2.变成中使用接口而不实现接口。如果我再说得直白一点,策略模式就是一种面向接口编程的思想。这点在多线程里面也有所体现,就是为什么我们在开启新线程的时候要new Thread(Runnable接口),参数传接口而不推荐去继承Thread,虽然也可以继承,这也是体现面向对象的封装特性,将run的算法和线程分离,可以实现run所在类的复用,虽然一般都不会去复用,当然这里还有一点就是Java只能单继承,如果继承了Thread就不能在继承其他的类。此时我们的User是按照age排序,那如果想按照名字排序呢,那就没办法了,但是如果将算法分离出来,需要用名字排序我们就传用名字排序的算法进去,需要用age排序就传age排序算法进去。环境持有接口引用,通过set方法将接口实现传入到环境,在环境中来调用接口方法,这完完全全是面向接口的特点。下面的第一个例子就是存放在一起的,因为TreeMap的构造方法其中就有一个是待Comparator接口参数的构造方法,该参数只要实现了Comparator接口就行,如下:
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
举例:
有一个Person类,实现了Comparator接口,并且按照岁数排序:
public class Person implements Comparator<Person> {
private String id;
private String name;
private Integer age;
public Person() {
}
public Person(String id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
public int compare(Person o1, Person o2) {
return o1.getAge() > o2.getAge() ? 1 : o1.getAge() == o2.getAge() ? 0 : -1;
}
}
测试:
public class TestPerson {
public static void main(String[] args) {
Map<Person, Integer> personHashMap = new HashMap<Person, Integer>();
Person person1 = new Person("1", "Jay", 30);
Person person2 = new Person("2", "Jolin", 21);
Person person3 = new Person("3", "Jack Cheng", 22);
Person person4 = new Person("4", "Bruce Lee", 22);
personHashMap.put(person1, 100);
personHashMap.put(person2, 200);
personHashMap.put(person3, 300);
personHashMap.put(person4, 400);
System.out.println(personHashMap);
Map<Person, Integer> personTreeMap = new TreeMap<Person, Integer>(new Person());
personTreeMap.put(person1, 100);
personTreeMap.put(person2, 200);
personTreeMap.put(person3, 300);
personTreeMap.put(person4, 400);
System.out.println(personTreeMap);
}
}
结果:
{Person [name=Jolin, age=21]=200, Person [name=Jay, age=30]=100, Person [name=Bruce Lee, age=22]=400, Person [name=Jack Cheng, age=22]=300}
{Person [name=Jolin, age=21]=200, Person [name=Jack Cheng, age=22]=400, Person [name=Jay, age=30]=100}
依然是compara方法相等的元素表示相同,只能存放一个进去,并且是按照age排序的。
在List中的结果:
public class TestPerson {
public static void main(String[] args) {
Map<Person, Integer> personHashMap = new HashMap<Person, Integer>();
Person person1 = new Person("1", "Jay", 30);
Person person2 = new Person("2", "Jolin", 21);
Person person3 = new Person("3", "Jack Cheng", 22);
Person person4 = new Person("4", "Bruce Lee", 22);
personHashMap.put(person1, 100);
personHashMap.put(person2, 200);
personHashMap.put(person3, 300);
personHashMap.put(person4, 400);
System.out.println(personHashMap);
Map<Person, Integer> personTreeMap = new TreeMap<Person, Integer>(new Person());
personTreeMap.put(person1, 100);
personTreeMap.put(person2, 200);
personTreeMap.put(person3, 300);
personTreeMap.put(person4, 400);
System.out.println(personTreeMap);
List<Person> personList = new ArrayList<Person>();
personList.add(person1);
personList.add(person2);
personList.add(person3);
personList.add(person4);
System.out.println(personList);
Collections.sort(personList, new Person());
System.out.println(personList);
}
}
结果:
{Person [name=Jolin, age=21]=200, Person [name=Jay, age=30]=100, Person [name=Bruce Lee, age=22]=400, Person [name=Jack Cheng, age=22]=300}
{Person [name=Jolin, age=21]=200, Person [name=Jack Cheng, age=22]=400, Person [name=Jay, age=30]=100}
[Person [name=Jay, age=30], Person [name=Jolin, age=21], Person [name=Jack Cheng, age=22], Person [name=Bruce Lee, age=22]]
[Person [name=Jolin, age=21], Person [name=Jack Cheng, age=22], Person [name=Bruce Lee, age=22], Person [name=Jay, age=30]]
调用sort方法前没排序,而调用之后按照age排序了。
其实将算法和模型分离也非常类似,只需要将实现Comparator接口的类单独拿出去就ok了。现在定义Person类,不实现Comparator接口,而将SortPerson类来实现该接口,其实就是用SortPerson类代替之前TreeMap中的参数,之前的参数是实现了Comparator接口的Person对象。
Person类:
public class Person {
private String id;
private String name;
private Integer age; public Person() {
} public Person(String id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
} public String getId() {
return id;
} public void setId(String id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} @Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
} @Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
} @Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
SortPerson类:
public class SortPerson implements Comparator<Person> { public int compare(Person o1, Person o2) {
return o1.getAge() > o2.getAge() ? 1 : o1.getAge() == o2.getAge() ? 0 : -1;
} }
测试:
public class TestPerson {
public static void main(String[] args) {
Map<Person, Integer> personHashMap = new HashMap<Person, Integer>();
Person person1 = new Person("1", "Jay", 30);
Person person2 = new Person("2", "Jolin", 21);
Person person3 = new Person("3", "Jack Cheng", 22);
Person person4 = new Person("4", "Bruce Lee", 22);
personHashMap.put(person1, 100);
personHashMap.put(person2, 200);
personHashMap.put(person3, 300);
personHashMap.put(person4, 400);
System.out.println(personHashMap);
Map<Person, Integer> personTreeMap = new TreeMap<Person, Integer>(new SortPerson());
personTreeMap.put(person1, 100);
personTreeMap.put(person2, 200);
personTreeMap.put(person3, 300);
personTreeMap.put(person4, 400);
System.out.println(personTreeMap);
List<Person> personList = new ArrayList<Person>();
personList.add(person1);
personList.add(person2);
personList.add(person3);
personList.add(person4);
System.out.println(personList);
Collections.sort(personList, new SortPerson());
System.out.println(personList);
}
}
结果:
{Person [name=Jolin, age=21]=200, Person [name=Jay, age=30]=100, Person [name=Bruce Lee, age=22]=400, Person [name=Jack Cheng, age=22]=300}
{Person [name=Jolin, age=21]=200, Person [name=Jack Cheng, age=22]=400, Person [name=Jay, age=30]=100}
[Person [name=Jay, age=30], Person [name=Jolin, age=21], Person [name=Jack Cheng, age=22], Person [name=Bruce Lee, age=22]]
[Person [name=Jolin, age=21], Person [name=Jack Cheng, age=22], Person [name=Bruce Lee, age=22], Person [name=Jay, age=30]]
很明显:结果与之前的将算法实现在Person类里面的结果一致的。
写的太多了,就不从JDK源码的角度来看问题了,如果有兴趣可以看看JDK源码,大致思路是才用红黑树来存数据,通过比较器的比较结果来判断当前需要保存的元素位于什么地方,还是那句话,既然底层用到了比较排序,那么性能肯定是比较差的,可以说是牺牲性能来做到排序功能,这个很容易理解,因为Java里面或者说计算机语言里面都是这个思路,要获得某种特定的能力,必然是需要牺牲性能,当然有时候为了获得良好的性能,那就必须要牺牲内存或者硬盘空间。比如HashMap为了获得非常强大的根据key来读取数据的性能,采用hash算法来牺牲一定的内存空间,再比如,数据库里面为了获得查询性能,给某些列添加索引,牺牲硬盘存储空间来达到效果,当然像数据库的为了获得查询和统计等性能为某些列添加聚簇索引,这种对硬盘空间的牺牲那是相当的惊人。索引占用空间比表里面的数据还大,但是获得的性能也是非常优秀的,所以,从某种意义上说,为了获取某种特性来牺牲另外一些资源那也是值得的。
Java中的TreeMap、Comparable、Comparator的更多相关文章
- java中Comparatable接口和Comparator接口的区别
1.不同类型的排序规则 .自然排序是什么? 自然排序是一种升序排序.对于不同的数据类型,升序规则不一样: BigDecimal BigInteger Byte Double Float Int ...
- java中的TreeMap如何顺序按照插入顺序排序
java中的TreeMap如何顺序按照插入顺序排序 你可以使用LinkedHashMap 这个是可以记住插入顺序的. 用LinkedHashMap吧.它内部有一个链表,保持插入的顺序.迭代的时候,也 ...
- python中实现将普通字典dict转换为java中的treeMap
上代码: from heapq import heappush,heappop from collections import OrderedDict def toTreeMap(paramMap): ...
- Java 中的 TreeMap 是采用什么树实现的?
Java 中的 TreeMap 是使用红黑树实现的.
- 1.java中Comparor与Comparable的问题
1.Comparator中compare()与Comparable中compareTo()方法的区别 Treeset集合创建对象后, A:如果是空构造,即TreeSet<Student> ...
- Java中的TreeMap及红黑树
TreeMap: http://blog.csdn.net/tobeandnottobe/article/details/7232664 红黑树: http://blog.chinaunix.net/ ...
- Java提高篇---TreeMap
TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的了解,其实这篇博文的名字叫做:根据红黑树的算法来分析TreeMap的实现,但是为了与Java提高篇系列博文保持一致 ...
- java集合类之TreeMap
转自:http://blog.csdn.net/chenssy/article/details/26668941 TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的 ...
- 第八节:详细讲解Java中的异常处理情况与I/O流的介绍以及类集合框架
前言 大家好,给大家带来详细讲解Java中的异常处理情况与I/O流的介绍以及类集合框架的概述,希望你们喜欢 JAVA 异常 try...catch...finally结构的使用方法 class Tes ...
随机推荐
- Ubuntu 安装软件
1,安装pthread的man文档 sudo apt-get install manpages-posix manpages-posix-dev
- 为什么玩VR眼镜会头晕?
为什么玩VR眼镜会头晕?看完本文你就懂了 很多用户都体验过各式各样的VR眼镜或者说头盔,但是不管哪一款,都很容易出现头晕的情况.相信大家都纳闷过,为什么玩VR眼镜会头晕?实际上这是和设备本身的技术 ...
- C++学了这么多年,你也许不知道为什么类定义要放在.h文件,类实现放在cpp文件。它们如何关联?
原文 http://blog.csdn.net/ithzhang/article/details/8119286 主题 C++ C++学了这么多年你知道为什么定义类时,类的定义放在.h文件中,而类 ...
- 在虚拟机中安装Ubuntu Server 15.04
学Linux,上红联! 红联Linux门户|Linux通用技术|Linux发行版技术|Linux企业应用|Linux实验室|红联Linux论坛 Linux系统教程 Linux入门 Linux管理 Li ...
- 给JBoss种蛊分析
JBoss又发现漏洞了,安全圈儿为之一紧. 知道创宇安全研究团队再次本着科普的情怀收集跟JBoss安全相关的材料,为安全行业再出一把力. 这里先给JBoss正下名.通常所说的JBoss,全称是JBos ...
- javascript设计模式学习之四——单例模式,缓存与对象池
单例模式的定义:确保一个实例,并提供全局访问. 惰性单例的定义:只在需要的时候才创建对象. 在开发中,有些对象往往只需要一个,比如线程池.全局缓存.浏览器中的window对象等. java中的单例 关 ...
- GPU 优化总结
前面说了对我这一年多的工作进行一个总结,由于工作比较紧,加上本人比较懒,一直没能抽出时间来写,最近稍微闲下来了.先写一篇GPU优化的,后续的文章希望能慢慢补齐.这些基本都是我个人优化的实际经验,也参考 ...
- c# 多线程与异步调用
异步操作的本质 在方法调用前为异步方法指定一个回调函数,方法调用后被线程池中的一个线程接管,执行该方法.主线程立即返回,继续执行其他工作或响应用户请求.如果异步方法执行完 毕,回调函数被自动执行,以处 ...
- Java内存分配全面浅析
本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java.这类文章网上有很多,但大多比较零碎.本文从认知过程角度出发,将带给读者一个系统的介绍. 进入正题前首先要知道的是Java程 ...
- Snapchat面经(师兄的)
给一个LinkedList环,给其中任一个节点的reference,求删去LinkedList中所有value=k的点 我的想法:假设给定的点事ListNode oneNode, 设置ListNode ...