Java中的TreeMap、Comparable、Comparator

写的非常到位

转载:http://www.it165.net/pro/html/201407/18366.html

我们知道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也始终相等。

  

01. public class User implements Comparable<User> {
02. private String id;
03. private String name;
04. private Integer age;
05.  
06. public User() {
07. }
08.  
09. public User(String id, String name, Integer age) {
10. super();
11. this.id = id;
12. this.name = name;
13. this.age = age;
14. }
15.  
16. public String getId() {
17. return id;
18. }
19.  
20. public void setId(String id) {
21. this.id = id;
22. }
23.  
24. public String getName() {
25. return name;
26. }
27.  
28. public void setName(String name) {
29. this.name = name;
30. }
31.  
32. public Integer getAge() {
33. return age;
34. }
35.  
36. public void setAge(Integer age) {
37. this.age = age;
38. }
39.  
40. @Override
41. public String toString() {
42. return "User [name=" + name + ", age=" + age + "]";
43. }
44.  
45. @Override
46. public boolean equals(Object obj) {
47. return true;
48. }
49.  
50. @Override
51. public int hashCode() {
52. return 0;
53. }
54. public int compareTo(User o) {
55. return this.age > o.getAge() ? 1 this.age == o.getAge() ? 0 : -1;
56. }
57. }

  测试代码:

01. public class TestUser {
02. public static void main(String[] args) {
03. Map<User, Integer> userHashMap = new HashMap<User, Integer>();
04. User user1 = new User("1""Jay"30);
05. User user2 = new User("2""Jolin"21);
06. User user3 = new User("3""Jack Cheng"22);
07. User user4 = new User("4""Bruce Lee"22);
08. userHashMap.put(user1, 100);
09. userHashMap.put(user2, 200);
10. userHashMap.put(user3, 300);
11. userHashMap.put(user4, 400);
12.  
13. System.out.println(userHashMap);
14.  
15. Map<User, Integer>  userTreeMap = new TreeMap<User, Integer>();
16. userTreeMap.put(user1, 100);
17. userTreeMap.put(user2, 200);
18. userTreeMap.put(user3, 300);
19. userTreeMap.put(user4, 400);
20.  
21. System.out.println(userTreeMap);
22. }
23. }

  结果:

1. {User [name=Jay, age=30]=400}
2. {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方法,如下:

01. @Override
02. public int hashCode() {
03. final int prime = 31;
04. int result = 1;
05. result = prime * result + ((id == null) ? 0 : id.hashCode());
06. return result;
07. }
08.  
09. @Override
10. public boolean equals(Object obj) {
11. if (this == obj)
12. return true;
13. if (obj == null)
14. return false;
15. if (getClass() != obj.getClass())
16. return false;
17. User other = (User) obj;
18. if (id == null) {
19. if (other.id != null)
20. return false;
21. else if (!id.equals(other.id))
22. return false;
23. return true;
24. }

测试代码不变,还是上面的测试代码,结果:

1. {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}
2. {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测试:

01. List<User> userList = new ArrayList<User>();
02. userList.add(user1);
03. userList.add(user2);
04. userList.add(user3);
05. userList.add(user4);
06.  
07. System.out.println(userList);
08.  
09. Collections.sort(userList);
10. System.out.println(userList);

结果:

1. {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}
2. {User [name=Jolin, age=21]=200, User [name=Jack Cheng, age=22]=400, User [name=Jay, age=30]=100}
3. [User [name=Jay, age=30], User [name=Jolin, age=21], User [name=Jack Cheng, age=22], User [name=Bruce Lee, age=22]]
4. [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接口就行,如下:

1. public TreeMap(Comparator<? super K> comparator) {
2. this.comparator = comparator;
3. }

举例:

有一个Person类,实现了Comparator接口,并且按照岁数排序:

01. public class Person implements Comparator<Person> {
02. private String id;
03. private String name;
04. private Integer age;
05.  
06. public Person() {
07. }
08.  
09. public Person(String id, String name, Integer age) {
10. this.id = id;
11. this.name = name;
12. this.age = age;
13. }
14.  
15. public String getId() {
16. return id;
17. }
18.  
19. public void setId(String id) {
20. this.id = id;
21. }
22.  
23. public String getName() {
24. return name;
25. }
26.  
27. public void setName(String name) {
28. this.name = name;
29. }
30.  
31. public Integer getAge() {
32. return age;
33. }
34.  
35. public void setAge(Integer age) {
36. this.age = age;
37. }
38.  
39. @Override
40. public String toString() {
41. return "Person [name=" + name + ", age=" + age + "]";
42. }
43.  
44. @Override
45. public int hashCode() {
46. final int prime = 31;
47. int result = 1;
48. result = prime * result + ((id == null) ? 0 : id.hashCode());
49. return result;
50. }
51.  
52. @Override
53. public boolean equals(Object obj) {
54. if (this == obj)
55. return true;
56. if (obj == null)
57. return false;
58. if (getClass() != obj.getClass())
59. return false;
60. Person other = (Person) obj;
61. if (id == null) {
62. if (other.id != null)
63. return false;
64. else if (!id.equals(other.id))
65. return false;
66. return true;
67. }
68.  
69. public int compare(Person o1, Person o2) {
70. return o1.getAge() > o2.getAge() ? 1 : o1.getAge() == o2.getAge() ? 0 : -1;
71. }
72.  
73.  
74. }

测试:

01. public class TestPerson {
02. public static void main(String[] args) {
03. Map<Person, Integer> personHashMap = new HashMap<Person, Integer>();
04. Person person1 = new Person("1""Jay"30);
05. Person person2 = new Person("2""Jolin"21);
06. Person person3 = new Person("3""Jack Cheng"22);
07. Person person4 = new Person("4""Bruce Lee"22);
08. personHashMap.put(person1, 100);
09. personHashMap.put(person2, 200);
10. personHashMap.put(person3, 300);
11. personHashMap.put(person4, 400);
12.  
13. System.out.println(personHashMap);
14.  
15. Map<Person, Integer>  personTreeMap = new TreeMap<Person, Integer>(new Person());
16. personTreeMap.put(person1, 100);
17. personTreeMap.put(person2, 200);
18. personTreeMap.put(person3, 300);
19. personTreeMap.put(person4, 400);
20.  
21. System.out.println(personTreeMap);
22.  
23. }
24. }

结果:

1. {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}
2. {Person [name=Jolin, age=21]=200, Person [name=Jack Cheng, age=22]=400, Person [name=Jay, age=30]=100}

依然是compara方法相等的元素表示相同,只能存放一个进去,并且是按照age排序的。

在List中的结果:

01. public class TestPerson {
02. public static void main(String[] args) {
03. Map<Person, Integer> personHashMap = new HashMap<Person, Integer>();
04. Person person1 = new Person("1""Jay"30);
05. Person person2 = new Person("2""Jolin"21);
06. Person person3 = new Person("3""Jack Cheng"22);
07. Person person4 = new Person("4""Bruce Lee"22);
08. personHashMap.put(person1, 100);
09. personHashMap.put(person2, 200);
10. personHashMap.put(person3, 300);
11. personHashMap.put(person4, 400);
12.  
13. System.out.println(personHashMap);
14.  
15. Map<Person, Integer>  personTreeMap = new TreeMap<Person, Integer>(new Person());
16. personTreeMap.put(person1, 100);
17. personTreeMap.put(person2, 200);
18. personTreeMap.put(person3, 300);
19. personTreeMap.put(person4, 400);
20.  
21. System.out.println(personTreeMap);
22.  
23. List<Person> personList = new ArrayList<Person>();
24.  
25. personList.add(person1);
26. personList.add(person2);
27. personList.add(person3);
28. personList.add(person4);
29.  
30. System.out.println(personList);
31.  
32. Collections.sort(personList, new Person());
33.  
34. System.out.println(personList);
35.  
36. }
37. }

结果:

1. {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}
2. {Person [name=Jolin, age=21]=200, Person [name=Jack Cheng, age=22]=400, Person [name=Jay, age=30]=100}
3. [Person [name=Jay, age=30], Person [name=Jolin, age=21], Person [name=Jack Cheng, age=22], Person [name=Bruce Lee, age=22]]
4. [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对象。

01. Person类:<br>public class Person {
02. private String id;
03. private String name;
04. private Integer age;
05.  
06. public Person() {
07. }
08.  
09. public Person(String id, String name, Integer age) {
10. this.id = id;
11. this.name = name;
12. this.age = age;
13. }
14.  
15. public String getId() {
16. return id;
17. }
18.  
19. public void setId(String id) {
20. this.id = id;
21. }
22.  
23. public String getName() {
24. return name;
25. }
26.  
27. public void setName(String name) {
28. this.name = name;
29. }
30.  
31. public Integer getAge() {
32. return age;
33. }
34.  
35. public void setAge(Integer age) {
36. this.age = age;
37. }
38.  
39. @Override
40. public String toString() {
41. return "Person [name=" + name + ", age=" + age + "]";
42. }
43.  
44. @Override
45. public int hashCode() {
46. final int prime = 31;
47. int result = 1;
48. result = prime * result + ((id == null) ? 0 : id.hashCode());
49. return result;
50. }
51.  
52. @Override
53. public boolean equals(Object obj) {
54. if (this == obj)
55. return true;
56. if (obj == null)
57. return false;
58. if (getClass() != obj.getClass())
59. return false;
60. Person other = (Person) obj;
61. if (id == null) {
62. if (other.id != null)
63. return false;
64. else if (!id.equals(other.id))
65. return false;
66. return true;
67. }
68. }
1. SortPerson类:
2. public class SortPerson implements Comparator<Person> {
3.  
4. public int compare(Person o1, Person o2) {
5. return o1.getAge() > o2.getAge() ? 1 : o1.getAge() == o2.getAge() ? 0 : -1;
6. }
7.  
8. }

测试:

01. public class TestPerson {
02. public static void main(String[] args) {
03. Map<Person, Integer> personHashMap = new HashMap<Person, Integer>();
04. Person person1 = new Person("1""Jay"30);
05. Person person2 = new Person("2""Jolin"21);
06. Person person3 = new Person("3""Jack Cheng"22);
07. Person person4 = new Person("4""Bruce Lee"22);
08. personHashMap.put(person1, 100);
09. personHashMap.put(person2, 200);
10. personHashMap.put(person3, 300);
11. personHashMap.put(person4, 400);
12.  
13. System.out.println(personHashMap);
14.  
15. Map<Person, Integer>  personTreeMap = new TreeMap<Person, Integer>(new SortPerson());
16. personTreeMap.put(person1, 100);
17. personTreeMap.put(person2, 200);
18. personTreeMap.put(person3, 300);
19. personTreeMap.put(person4, 400);
20.  
21. System.out.println(personTreeMap);
22.  
23. List<Person> personList = new ArrayList<Person>();
24.  
25. personList.add(person1);
26. personList.add(person2);
27. personList.add(person3);
28. personList.add(person4);
29.  
30. System.out.println(personList);
31.  
32. Collections.sort(personList, new SortPerson());
33.  
34. System.out.println(personList);
35.  
36. }
37. }

结果:

1. {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}
2. {Person [name=Jolin, age=21]=200, Person [name=Jack Cheng, age=22]=400, Person [name=Jay, age=30]=100}
3. [Person [name=Jay, age=30], Person [name=Jolin, age=21], Person [name=Jack Cheng, age=22], Person [name=Bruce Lee, age=22]]
4. [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算法来牺牲一定的内存空间,再比如,数据库里面为了获得查询性能,给某些列添加索引,牺牲硬盘存储空间来达到效果,当然像数据库的为了获得查询和统计等性能为某些列添加聚簇索引,这种对硬盘空间的牺牲那是相当的惊人。索引占用空间比表里面的数据还大,但是获得的性能也是非常优秀的,所以,从某种意义上说,为了获取某种特性来牺牲另外一些资源那也是值得的。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值