深圳幻海软件技术有限公司 欢迎您!

JavaSE进阶 | Map集合、HashMap集合、TreeMap集合

2023-07-12

目录🏀Map集合概述 🥅Map接口常用的方法🥅哈希表(散列表)数据结构🥅同时重写HashCode和equals🥅HashMap和Hashtable的区别🥅Properties类🥅TreeSet(TreeMap)集合🥅自平衡二叉树数据结构🥅实现比较器接口🥅集合工具类Col

目录

🏀Map集合概述 

🥅Map接口常用的方法

🥅哈希表(散列表)数据结构

🥅同时重写HashCode和equals

🥅HashMap和Hashtable的区别

🥅Properties类

🥅TreeSet(TreeMap)集合

🥅自平衡二叉树数据结构

🥅实现比较器接口

🥅集合工具类Collections


🏀Map集合概述 

(1)Map和Collection没有继承关系,是一个平级的关系。

(2)Map集合以key和value的方式存储数据:

键值对key和value都是引用数据类型。

key和value都是存储对象的内存地址。

key起到主导的地位,value是key的一个附属品。

④HashMap中的所有的key彼此之间是不可重复的、无序的。所有的key构成一个Set集合。key所在的类重写hashCode()方法和equals()方法。

⑤HashMap中的value彼此之间是可重复的、无序的。所有的value就构成一个Collection集合。value所在的类要重写equals()方法。

⑥HashMap中的一个key-value,就构成一个entry,所有的entry之间是不可重复的、无序的。所有的entry就构成一个Set集合。

(3)HashMap: 底层是哈希表,非线程安全的

(4)Hashtable: 底层也是哈希表,只不过线程是安全的,效率较低使用较少。

(5)Prorerties:线程是安全的,并且key和value只能储存字符串String类型

(6)TreeMap:底层是二叉树,TreeMap集合的key可以自动按照大小顺序排序

(7)HashMap底层使用的是:数组+单向链表+红黑树结构进行存储(JDK8之后)!

 ⭐️Map集合继承结构图

🥅Map接口常用的方法

Map接口中常用方法:

V put(K key, V value)  向Map集合中添加键值对      
V get(Object key)  通过key获取value

void clear()    清空Map集合
boolean containsKey(Object key)  判断Map中是否包含某个key
boolean containsValue(Object value)  判断Map中是否包含某个value
boolean isEmpty()   判断Map集合中元素个数是否为0
V remove(Object key)  通过key删除键值对
int size()  获取Map集合中键值对的个数
Set<K> keySet()  获取Map集合所有的key(所有的键(Key)是一个Set集合)

Collection<V> values()  获取Map集合中所有的value(所有的values是一个Collection集合)
Set<Map.Entry<K,V>> entrySet()  将Map集合转换成Set集合;对于Map结合转换成Set集合怎么理解?假设现在有一个Map集合,如下所示:       

Map集合对象    

  1.    key             value
  2.             ----------------------------
  3.             1               zhangsan
  4.             2               lisi
  5.             3               wangwu
  6.             4               zhaoliu

通过Set set = map1.entrySet();set集合对象的形式,把Map集合的两个值通过=的方式撮合成一个了  

  1. 1=zhangsan 
  2. 2=lisi  
  3. 3=wangwu
  4. 4=zhaoliu 

注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素的类型是 Map.Entry<K,V>Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态内部类 。

 ⭐️回顾静态内部类

  1. package com.bjpowernode.javase.map;
  2. public class Myclass {
  3. //声明一个静态内部类
  4. public static class InnerClass{
  5. // 静态内部类的静态方法
  6. public static void m1(){
  7. System.out.println("静态内部类的静态方法执行!");
  8. }
  9. // 静态内部类的实例方法
  10. public void m2(){
  11. System.out.println("静态内部类的实例方法执行!");
  12. }
  13. }
  14. public static void main(String[] args) {
  15. // 静态方法执行,类名:Myclass.InnerClass
  16. Myclass.InnerClass.m1();
  17. // 创建静态内部类对象
  18. Myclass.InnerClass mi = new Myclass.InnerClass();
  19. mi.m2(); //执行
  20. //给一个Set集合;改Set集合中存储的对象是:MyClass.InnerClass类型
  21. Set<Myclass.InnerClass> set = new HashSet<>();
  22. }
  23. }

⭐️例1:

  1. package com.bjpowernode.javase.map;
  2. import java.util.Collection;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. public class MapTest02 {
  6. public static void main(String[] args) {
  7. // 创建Map集合对象
  8. Map<Integer,String> map = new HashMap<>();
  9. // 1、向Map集合中添加键值对;put方法
  10. map.put(1,"张三"); // 1是自动装箱
  11. map.put(2,"李四");
  12. map.put(3,"王五");
  13. // 2、通过key获取value;get方法
  14. String str = map.get(1);
  15. System.out.println(str); //张三
  16. // 3、获取Map集合中键值对的个数;size方法
  17. System.out.println(map.size()); //3
  18. // 4、 通过key删除键值对;remove方法
  19. map.remove(1);
  20. System.out.println(map.size()); //2
  21. // contains方法底层调用的都是equals进行比对的,所以自定义的类型需要重写equals方法。
  22. // 5、判断Map中是否包含某个key;containsKey方法
  23. boolean b1 = map.containsKey(2);
  24. System.out.println(b1); // true
  25. // 6、判断Map中是否包含某个value;containsValue方法
  26. boolean b2 = map.containsValue("李四");
  27. System.out.println(b2); // true
  28. // 7、获取所有的value;values方法,返回的是一个Collection集合
  29. Collection<String> c = map.values();
  30. // 遍历打印这个集合
  31. for(String s:c){ //增加for循环进行打印
  32. System.out.println(s);
  33. }
  34. // 8、清空集合;clear方法
  35. map.clear();
  36. System.out.println(map.size()); //0
  37. // 9、判断Map集合是否为空;isEmpty方法
  38. System.out.println(map.isEmpty()); // true
  39. }
  40. }

⭐️例2:遍历Map结合(重点)

对于Map集合的打印方式主要有两种:

(1)第一种方式:先获取所有的key,通过key获取value

Map集合的所有key是一个Set集合,可以用keySet()方法进行获得;

Map集合的所有value是一个Collection集合,可以用values()方法进行获得;

所以我们就可以分为两步:

第一步:先调用keySet()方法获取所有的key,并返回一个Set集合;

第二步:再对Set集合迭代打印时,取出key;然后调用get(key)方法,通过key获取到value

(2)第二种方式:调用entrySet()方法,将Map集合转换成Set集合

①调用Set<Map.Entry<K,V>> entrySet() 转换成Set集合;Map.Entry<K,V>是静态内部类;

②转换成Set集合后利用迭代器打印,在迭代器中获取每一个节点Node(或者说entry,因为Node实现了Map.Entry接口)Map.Entry<Integer,String> node = it2.next();

③然后利用这个节点获取key(node.getkey()方法) 和 value(value(node.getvalue()方法))

  1. package com.bjpowernode.javase.map;
  2. import java.util.HashMap;
  3. import java.util.Iterator;
  4. import java.util.Map;
  5. import java.util.Set;
  6. // Map集合遍历
  7. public class MapTest03 {
  8. public static void main(String[] args) {
  9. //第一种方式:获取所有的key,通过遍历key,来遍历value
  10. Map<Integer, String> map = new HashMap<>();
  11. map.put(1, "zhangsan");
  12. map.put(2, "lisi");
  13. map.put(3, "wangwu");
  14. // 先获取所有的key,所有的key是一个Set集合
  15. Set<Integer> keys = map.keySet();
  16. // 在遍历key,通过key获取value
  17. // 方法1:利用迭代器
  18. Iterator<Integer> it = keys.iterator();
  19. while (it.hasNext()){
  20. Integer key = it.next(); //获取其中一个key
  21. String value = map.get(key); //通过key获取value
  22. System.out.println(key+"="+value);
  23. }
  24. // 方法2:foreach
  25. for(Integer key:keys){
  26. System.out.println(key+"="+map.get(key));
  27. }
  28. //第二种方式:   Set<Map.Entry<K,V>> entrySet()  将Map集合转换成Set集合
  29. Set<Map.Entry<Integer,String>> set = map.entrySet();
  30. // 方法1:使用迭代器进行打印
  31. Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
  32. while(it2.hasNext()){
  33. Map.Entry<Integer,String> node = it2.next(); //取出一个节点
  34. Integer key = node.getKey(); //取出节点的key
  35. String value = node.getValue(); //取出节点的value
  36. System.out.println(key+"="+value);
  37. }
  38. // 方法2:foreach方式
  39. // 这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。
  40. // 这种方式比较适合于大数据量。
  41. for( Map.Entry<Integer,String> node:set){
  42. System.out.println(node.getKey()+"="+node.getValue());
  43. }
  44. }
  45. }

⭐️补充:LinkedHashMap是HashMap的子类,在HashMap使用的数据结构的基础上,增加了一对双向链表,用于记录添加的元素的先后顺序,进而我们在遍历元素时,就可以按照天机的顺序显示!开发中,对于频繁的遍历操作,建议使用此类!

🥅哈希表(散列表)数据结构

哈希表的存储形式:哈希表实际上是一个数组和单向链表的结合体;底层实际上就是一个数组,这个数组中每一个元素是一个单向链表
数组:在查询方面效率很高,随机增删方面效率很低。
单向链表:在随机增删方面效率较高,在查询方面效率很低。
哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。

(1)HashMap集合底层是哈希表/散列表的数据结构。
(2)对于存储数据map.put(k,v)的原理:

第一步:先将k和v封装到一个Node对象当中;

第二步:底层会调用k的hashCode()方法得到对应的hash值;然后通过哈希算法,将hash值转换成数组的下标,下标位置如果没有任何元素,就把Node添加到这个位置上;如果说下标对应的位置有链表存在,就把k和链表上的每一个节点中的k调用equals方法进行对比;如果所有的equals方法返回false,那么这个新节点就将添加到链表的末尾;如果其中一个equals方法返回true,那么这个节点的value将会被覆盖!

(3)对于存储数据map.get(k)的原理

第一步:先调用k的hashCode()方法得到对应的hash值;然后通过哈希算法,将hash值转换成数组的下标,通过下标可以快速定位到某个位置

第二步:如果这个位置什么也没有,返回null;如果这个位置上有链表,那么就会拿着k和链表上的每一个节点中的k调用equals方法进行对比;如果所有的equals方法都返回false,那么就反回null;如果其中有一个节点的equals返回true,那么此时这个节点的value就是我们get方法所需要的返回值

(4)HashMap集合底层的源代码:

  1.     public class HashMap{
  2.             // HashMap底层实际上就是一个数组。(一维数组)
  3.             Node<K,V>[] table;
  4.             // 静态的内部类HashMap.Node
  5.             static class Node<K,V> {
  6.                 final int hash; // 哈希值(哈希值是key的hashCode()方法的执行结果。
  7. // hash值通过哈希函数/算法,可以转换存储成数组的下标。)
  8.                 final K key; // 存储到Map集合中的那个key
  9.                 V value; // 存储到Map集合中的那个value
  10.                 Node<K,V> next; // 下一个节点的内存地址。
  11.             }
  12.         }

(5)结论
重点:通过上面的讲解可以得出HashMap集合的key,会先后调用两个方法, 一个方法是hashCode(),一个方法是equals(),这两个方法都需要重写!
注意:同一个单向链表上所有节点的hash值相同,因为他们的数组下标是一样的;但同一个链表上k和k的equals方法肯定返回的是false,都不相同(无序不可重复)
(6)HashMap集合的key部分特点无序,不可重复(重复会覆盖)
为什么无序? 因为不一定挂到哪个单向链表上。
不可重复是怎么保证的? equals方法来保证HashMap集合的key不可重复。
如果key重复了,value会覆盖。
放在HashMap集合key部分的元素其实就是放到HashSet集合中了;所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。

(7)哈希表HashMap使用不当时无法发挥性能!(我们考虑两个极端情况)
第一种情况:假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们成为:散列分布不均匀。
什么是散列分布均匀?
假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,是散列分布均匀的。
第二种情况:假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗?

不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了,也是散列分布不均匀。所以要想散列分布均匀,需要重写hashCode()方法时有一定的技巧。

注意:hashCode()方法返回值都一样;实际上是一个单链表;

           hashCode()方法返回值都不一样;实际上就是一个数组
(8)重点放在HashMap集合key部分的元素,实际上就是放在HashSet集合中的元素,需要同时重写hashCode和equals方法。
(9)HashMap集合的默认初始化容量是16,默认加载因子是0.75;扩容:扩容后的容量是原容量的2倍;这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容。
记住:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。

  1. package com.bjpowernode.javase.map;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import java.util.Set;
  5. public class HashMapTest01 {
  6. public static void main(String[] args) {
  7. // 测试HashMap 集合key部分的元素特点
  8. // Integer是key,它的hashCode和equals都重写了
  9. Map<Integer,String> map = new HashMap<>();
  10. map.put(111,"zhangsan");
  11. map.put(222,"lisi");
  12. map.put(333,"wangwu");
  13. map.put(333,"zhaoliu"); //key重复,value会覆盖上面的值
  14. // 元素个数
  15. System.out.println(map.size()); //3
  16. // 遍历
  17. Set<Map.Entry<Integer,String>> set = map.entrySet();
  18. for(Map.Entry<Integer,String> entry:set){
  19. System.out.println(entry.getKey()+"="+entry.getValue());
  20. }
  21. /*333=zhaoliu
  22. 222=lisi
  23. 111=zhangsan
  24. 验证结果:无序不可重复,key重复会覆盖掉原来的值!
  25. */
  26. }
  27. }

🥅同时重写HashCode和equals

(1)向Map集合中存,以及从Map集合中取,都是先调用key的hashCode()方法然后再调用equals()方法equals方法有可能调用,也有可能不调用。 
①拿put(k,v)举例,什么时候equals不会调用?
k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标;数组下标位置上如果是null,equals不需要执行。
拿get(k)举例,什么时候equals不会调用?
k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标,数组下标位置上如果是null,equals不需要执行。

(2)重要结论:如果一个类的equals方法重写了,那么hashCode()方法必须重写;并且equals方法返回如果是true,hashCode()方法返回的值必须一样。
equals方法返回true表示两个对象相同,在同一个单向链表上比较;那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的,所以hashCode()方法的返回值也应该相同。

(3)hashCode()方法和equals()方法可以直接使用IDEA工具生成,但是这两个方法需要同时生成。

(4)终极结论:放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。

(5)对于哈希表数据结构来说:
如果o1和o2的hash值相同,一定是放到同一个单向链表上。
如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。

(6)JDK8对HashMap集合的改进:

如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树;当红黑树节点数量小于6时,会重新把红黑树变成单向链表;这种方式也是为了提高检索效率,二叉树的检索效率会再次缩小扫描范围,提高效率。

⭐学生类

  1. package com.bjpowernode.javase.map;
  2. import java.util.Objects;
  3. public class Student {
  4. private String name;
  5. // 构造方法
  6. public Student() {
  7. }
  8. public Student(String name) {
  9. this.name = name;
  10. }
  11. // setter and getter
  12. public String getName() {
  13. return name;
  14. }
  15. public void setName(String name) {
  16. this.name = name;
  17. }
  18. // 同时生成equals方法和hashCode方法
  19. public boolean equals(Object o) {
  20. if (this == o) return true;
  21. if (o == null || getClass() != o.getClass()) return false;
  22. Student student = (Student) o;
  23. return Objects.equals(name, student.name);
  24. }
  25. public int hashCode() {
  26. return Objects.hash(name);
  27. }
  28. }

⭐测试 

  1. package com.bjpowernode.javase.map;
  2. import java.util.HashSet;
  3. import java.util.Set;
  4. public class HashMapTest02 {
  5. public static void main(String[] args) {
  6. Student s1 = new Student("张三");
  7. Student s2 = new Student("张三");
  8. // 重写equlas方法之前
  9. System.out.println(s1.equals(s2)); // false
  10. // 重写equals方法之后
  11. System.out.println(s1.equals(s2)); // true
  12. // 1163157884(重写hashCode之后:774920)
  13. System.out.println("s1的hashCode是:"+s1.hashCode());
  14. // 1956725890(重写hashCode之后:774920)
  15. System.out.println("s2的hashCode是:"+s2.hashCode());
  16. System.out.println(s1.hashCode() == s2.hashCode()); // false
  17. // s1.equals(s2)结果已经是true了,表示s1和s2是一样的,相同的,
  18. // 那么往HashSet集合中放的话,按说只能放进去1个。
  19. // (HashSet集合特点:无序不可重复)
  20. Set<Student> students = new HashSet<>();
  21. students.add(s1);
  22. students.add(s2);
  23. // 按理说应该是1,无序不可重复!未重写hashCode方法
  24. System.out.println(students.size()); // 2
  25. // 重写hashCode以后结果就是1
  26. System.out.println(students.size()); // 1
  27. }
  28. }

❤️HashMap的key可以为null(HashTable的key不能为null)

HashMap集合key部分允许null吗?
答:允许但是要注意HashMap集合的key null值只能有一个;多了会被后面的覆盖

  1. package com.bjpowernode.javase.map;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. public class HashMapTest03 {
  5. public static void main(String[] args) {
  6. Map map = new HashMap();
  7. // HashMap集合允许key为null
  8. map.put(null,null);
  9. System.out.println(map.size()); // 1
  10. // key重复的话value会被覆盖
  11. map.put(null,100);
  12. System.out.println(map.size()); // 还是1
  13. // 通过key获取value
  14. System.out.println(map.get(null)); // 100
  15. }
  16. }

🥅HashMap和Hashtable的区别

(1)Hashtable的key可以为null吗?

HashMap集合的key和value都是可以为null的
Hashtable的key和value都是不能为null的。  

(2)Hashtable方法都带有synchronized:线程安全的。线程安全有其它的方案,Hashtable对线程的处理导致效率较低,使用较少了。Hashtable和HashMap一样,底层都是哈希表数据结构。

(3)容量

①HashMap集合的默认初始化容量是16,默认加载因子是0.75;扩容后的容量是原容量的2倍
②Hashtable集合的默认初始化容量是11,默认加载因子是:0.75,扩容后:原容量 * 2 + 1

(4)底层数据结构

①HashMap底层使用的是:数组+单向链表+红黑树结构进行存储(JDK8之后)!

②Hashtable底层使用的是:数组+单向链表!

  1. package com.bjpowernode.javase.table;
  2. import java.util.HashMap;
  3. import java.util.Hashtable;
  4. import java.util.Map;
  5. public class HashtableTest01 {
  6. public static void main(String[] args) {
  7. //1、 HashMap集合的key和value都是可以为null的
  8. Map map1 = new HashMap();
  9. map1.put(null,null); // 编译和运行都没问题
  10. //2、 Hashtable的key和value都是不能为null的。
  11. Map map2 = new Hashtable();
  12. map2.put(null,"zhangsan"); // java.lang.NullPointerException
  13. map2.put(10,null); // java.lang.NullPointerException
  14. }
  15. }

关于容量,扩容小总结:

Collection集合:

(1)ArrayList集合初始化容量是10;扩容到原容量的1.5倍
(2)Vector集合初始化容量是10;扩容到原容量的2倍
(3)HashSet集合初始化容量16,初始化容量建议是2的倍数;扩容:扩容之后是原容量2倍

Map集合:

(1)HashMap初始化容量16,默认加载因子0.75,扩容之后的容量是量的2倍
(2)Hashtable集合初始化容量11,默认加载因子0.75,扩容之后的容量是原容量*2 + 1

🥅Properties类

(1)Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。
(2)Properties被称为属性类对象,因为Hashtable是线程安全的,所以Properties是线程安全的。

(3)两个重要方法

调用setProperties(key,value)方法存取数据。

调用getProperties(key)方法:通过key获取value。

  1. package com.bjpowernode.javase;
  2. import java.util.Properties;
  3. public class PropertiesTest01 {
  4. public static void main(String[] args) {
  5. // 创建一个Properties对象
  6. Properties pro = new Properties();
  7. // 存setProperyies
  8. pro.setProperty("username","root");
  9. pro.setProperty("password","1234");
  10. pro.setProperty("driver","com.mysql.jdbc.Driver");
  11. // 通过key取value
  12. System.out.println(pro.getProperty("username")); //root
  13. System.out.println(pro.getProperty("password")); //1234
  14. System.out.println(pro.getProperty("driver")); //com.mysql.jdbc.Driver
  15. }
  16. }

注:Properties常用来处理属性文件!

  1. package com.zl.TreeSetTest;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.IOException;
  5. import java.util.Properties;
  6. public class PropertiesTest {
  7. public static void main(String[] args) throws IOException {
  8. File file = new File("info.properties");
  9. // 查看这个属性配置文件默认的位置(在工程的根目录下)
  10. System.out.println(file.getAbsoluteFile()); // C:\Users\86177\IdeaProjects\spring-data-redis\info.properties
  11. // 需要一个输入流
  12. FileInputStream fis = new FileInputStream(file);
  13. // 创建Properties集合
  14. Properties pro = new Properties();
  15. // 加载
  16. pro.load(fis);
  17. // 读取数据(根据key)
  18. System.out.println(pro.getProperty("name")); // zhangsan
  19. System.out.println(pro.getProperty("password")); // 123
  20. }
  21. }

🥅TreeSet(TreeMap)集合

(1)TreeSet集合底层实际上是一个TreeMap;放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。
(2)TreeMap集合底层是一个红黑树(自平衡的排序二叉树)。
(3)TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序(可排序集合),所以也叫作可排序集合(自然排序和定制排序)

(4)TreeSet集合有什么用呢?我们拿一个很常见的需求来作为例子;数据库中有很多数据:

  1.     userid  name     birth
  2.     -------------------------------------
  3.     1       zs          1980-11-11
  4.     2       ls          1980-10-11
  5.     3       ww          1981-11-11
  6.     4       zl          1979-11-11

编写程序从数据库当中取出数据,在页面展示用户信息的时候按照生日升序或者降序。
这个时候可以使用TreeSet集合,因为TreeSet集合放进去,拿出来就是有顺序的。

  1. package com.bjpowernode.javase.map;
  2. import java.util.TreeSet;
  3. public class TreeSetTest01 {
  4. public static void main(String[] args) {
  5. // 演示TreeSet对String是可排序的
  6. TreeSet<String> ts = new TreeSet<>();
  7. // 添加元素
  8. ts.add("zhangsan");
  9. ts.add("lisi");
  10. ts.add("wangwu");
  11. ts.add("wangliu");
  12. // 遍历---升序排
  13. for(String s :ts){
  14. System.out.println(s); // lisi、wangliu、wangwu、zhangsan
  15. }
  16. TreeSet<Integer> ts2 = new TreeSet<>();
  17. ts2.add(300);
  18. ts2.add(600);
  19. ts2.add(100);
  20. ts2.add(10);
  21. ts2.add(200);
  22. for(Integer i :ts2){
  23. System.out.println(i); // 10、100、200、300、600
  24. }
  25. }
  26. }

❤️TreeSet无法对自定义类型排序

(1)对自定义的类型来说(例如Person类),TreeSet不可以排序。
以下程序中对于Person类型来说,无法排序;因为没有指定Person对象之间的比较规则,谁大谁小并没有说明。

(2)以下程序运行的时候出现了这个异常: java.lang.ClassCastException
(3)出现这个异常的原因是:Person类没有实现java.lang.Comparable接口。

(4)所以对于自定义的类型,使用TreeSet集合,因为没有比较规则,所以是无法排序的;所以我们需要实现java.lang.Comparable接口并且重写compareTo方法,在compareTo方法里面进行比较规则的编写,才可以!

⭐没有实现Comparable接口,无法进行比较 

  1. package com.bjpowernode.javase.map;
  2. import java.util.TreeSet;
  3. public class TreeSetTest02 {
  4. public static void main(String[] args) {
  5. // 创建对象
  6. Person p1 = new Person(32);
  7. Person p2 = new Person(20);
  8. Person p3 = new Person(30);
  9. // 创建集合
  10. TreeSet<Person> ts = new TreeSet<>();
  11. // 添加元素
  12. ts.add(p1);
  13. ts.add(p2);
  14. ts.add(p3);
  15. // 遍历打印
  16. for(Person p:ts){
  17. System.out.println(p);
  18. }
  19. }
  20. }
  21. //自定义Person类
  22. class Person{
  23. int age;
  24. public Person(int age){
  25. this.age = age;
  26. }
  27. // 重写toString()方法
  28. public String toString(){
  29. return "Person[age="+age+"]";
  30. }
  31. }

⭐实现Comparable接口,重写compareTo方法,编写比较的逻辑

  1. package com.bjpowernode.javase.map;
  2. import java.util.TreeSet;
  3. public class TreeSetTest03 {
  4. public static void main(String[] args) {
  5. Customer c1 = new Customer(32);
  6. Customer c2 = new Customer(20);
  7. Customer c3 = new Customer(30);
  8. Customer c4 = new Customer(25);
  9. // 创建TreeSet集合
  10. TreeSet<Customer> customers = new TreeSet<>();
  11. // 添加元素
  12. customers.add(c1);
  13. customers.add(c2);
  14. customers.add(c3);
  15. customers.add(c4);
  16. // 遍历
  17. for (Customer c : customers){
  18. System.out.println(c);
  19. }
  20. }
  21. }
  22. // 放在TreeSet集合中的元素需要实现java.lang.Comparable接口。
  23. // 并且实现compareTo方法。equals可以不写。
  24. class Customer implements Comparable<Customer>{
  25. int age;
  26. public Customer(int age){
  27. this.age = age;
  28. }
  29. // 需要在这个方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较!
  30. // k.compareTo(t.key)
  31. // 拿着参数k和集合中的每一个k进行比较,返回值可能是>0 <0 =0
  32. // 比较规则最终还是由程序员指定的:例如按照年龄升序。或者按照年龄降序。
  33. public int compareTo(Customer c) { // c1.compareTo(c2);
  34. // this是c1
  35. // c是c2
  36. // c1和c2比较的时候,就是this和c比较。
  37. /*int age1 = this.age;
  38. int age2 = c.age;
  39. if(age1 == age2){
  40. return 0;
  41. } else if(age1 > age2) {
  42. return 1;
  43. } else {
  44. return -1;
  45. }*/
  46. //return this.age - c.age; //升序
  47. return c.age - this.age; //降序
  48. }
  49. public String toString(){
  50. return "Customer[age="+age+"]";
  51. }
  52. }

⭐比较规则怎么写

我们不妨编写一个:先按照年龄升序,如果年龄一样的再按照姓名升序。
compareTo方法的返回值很重要:
①返回0表示相同,value会覆盖。
②返回>0,会继续在右子树上找。
③返回<0,会继续在左子树上找。

我们也可以定义两个比较规则,当一种比较规则失效时,在使用另外一种规则;

这里需要注意的是对于字符串的比较equals只能说明相等不相等;compareTof方法才能比较到底谁大谁小。

  1. package com.bjpowernode.javase.map;
  2. import java.util.TreeSet;
  3. //先按照年龄升序,如果年龄一样的再按照姓名升序。
  4. public class TreeSetTest04 {
  5. public static void main(String[] args) {
  6. Vip v1 = new Vip("zhangsan",18);
  7. Vip v2 = new Vip("zhangzl",18);
  8. Vip v3 = new Vip("lisi",32);
  9. Vip v4 = new Vip("wangwu",20);
  10. Vip v5 = new Vip("zhaoliu",22);
  11. // 创建集合
  12. TreeSet<Vip> ts = new TreeSet<>();
  13. // 增加元素
  14. ts.add(v1);
  15. ts.add(v2);
  16. ts.add(v3);
  17. ts.add(v4);
  18. ts.add(v5);
  19. // 遍历打印
  20. for(Vip v :ts){
  21. System.out.println(v);
  22. }
  23. }
  24. }
  25. class Vip implements Comparable<Vip>{
  26. String name;
  27. int age;
  28. // 构造方法
  29. public Vip(String name, int age) {
  30. this.name = name;
  31. this.age = age;
  32. }
  33. // 重写toString方法
  34. public String toString() {
  35. return "Vip{" +
  36. "name='" + name + '\'' +
  37. ", age=" + age +
  38. '}'; //Vip{name='zhangsan', age=18}
  39. }
  40. // 重写Comparable接口中中的方法
  41. public int compareTo(Vip o) {
  42. if(this.age != o.age){
  43. return this.age - o.age;
  44. }else{
  45. // 年龄相同,按照名字排序
  46. // 名字是String类型,可以直接比,调用compareTo来比较
  47. return this.name.compareTo(o.name);
  48. }
  49. }
  50. }
  51. /*
  52. 运行结果:
  53. Vip{name='zhangsan', age=18}
  54. Vip{name='zhangzl', age=18}
  55. Vip{name='wangwu', age=20}
  56. Vip{name='zhaoliu', age=22}
  57. Vip{name='lisi', age=32}
  58. */

🥅自平衡二叉树数据结构

(1)TreeSet/TreeMap是自平衡二叉树遵循左小右大原则存放。

(2)遍历二叉树的时候有三种方式:
①前序遍历∶根左右
②中序遍历∶左根右

③后序遍历∶左右根
注意:前中后说的是“根”的位置,根在前面是前序、根在中间是中序、根在后面是后序

(3)TreeSet/TreeMap集合采用的是中序遍历方式即Iterator迭代器采用的是中序遍历方式

(4)我们就拿一组数据,写出它的二叉树结构,来体验一下中序遍历的方式,例如:100 200 50 60 80 120 140 130 135 180 666 40 55

(5)中序遍历方式取出:40 50 55 60 80 100 120 130 135 140 180 200 666,按照中序遍历的方式取出来的刚好是有序的。

🥅实现比较器接口

TreeSet集合中元素可排序的第二种方式:使用比较器的方式

(1)这种实现方式是根据源码码来定义的
如果我们使用无参的:调用的是Comparable接口里的compareTo方法;注意是自己定义的类型实现Comparable接口;这种方式不需要在构造方法new这个对象。

如果我们使用有参的:调用的是Comparator接口里的compare方法;注意是再另写一个比较类去实现Comparator接口;这种方式需要在构造方法中去new实现的比较类。
Comparable是java.lang包下的,Comparator是java.util包下的
(2)结论:放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
第一种:放在集合中的元素实现java.lang.Comparable接口。
第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器Comparator对象。
(3)Comparable和Comparator怎么选择呢?
①当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口,重写compareTo方法
②如果比较规则有多个并且需要多个比较规则之间频繁切换,建议使用Comparator接口;重写compare方法,Comparator接口的设计符合OCP原则,。

  1. package com.bjpowernode.javase.map;
  2. import java.util.Comparator;
  3. import java.util.TreeSet;
  4. public class TreeSetTest05 {
  5. public static void main(String[] args) {
  6. Animals animals = new Animals(15);
  7. // 创建TreeSet集合的时候,需要使用这个比较器
  8. // TreeSet<Animals> animal = new TreeSet<>(); //原来这种方式就不行,没有使用比较器
  9. // TreeSet<Animals> animal = new TreeSet<>(new AnimalComparator()); //new一个比较器对象传过去
  10. // 这里也可以不写Comparator接口的实现,使用匿名内部类
  11. TreeSet<Animals> animal = new TreeSet<>(new Comparator<Animals>() {
  12. public int compare(Animals o1, Animals o2) {
  13. return o1.age - o2.age;
  14. }
  15. });
  16. animal.add(new Animals(100));
  17. animal.add(new Animals(80));
  18. animal.add(new Animals(81));
  19. // 循遍历
  20. for(Animals a:animal){
  21. System.out.println(a);
  22. }
  23. }
  24. }
  25. class Animals{
  26. int age;
  27. // 构造方法
  28. public Animals(int age) {
  29. this.age = age;
  30. }
  31. // 重写toString方法
  32. public String toString() {
  33. return "Animals{" +
  34. "age=" + age +
  35. '}'; // Animals{age=15}
  36. }
  37. }
  38. /*
  39. // 单独在这里编写一个比较器
  40. // 比较器实现java.util.Comparator接口。
  41. class AnimalComparator implements Comparator<Animals> {
  42. public int compare(Animals o1, Animals o2) {
  43. // 指定比较规则;按照年龄排序
  44. return o1.age - o2.age;
  45. }
  46. }*/

🥅集合工具类Collections

参考操作数组的工具类:Arrays,Collections 是一个操作 Set、List 和 Map 等集合的工具类。

(1)区分两个类:

java.util.Collection 集合接口
java.util.Collections 集合工具类,方便集合的操作。

(2)通过Collections集合里的方法变成线程安全的;Collections.synchronizedList(集合)

(3)对于SortedSet集合是可排序集合,但是对于HashSet集合排序,可以先把HashSet集合通过new ArrayList<>(set);转换成List集合,然后就可以使用Collections.sort(); 进行排序,因为使用这个sort方法排序只能是List集合才可以!

(4)对于自定义的类型如何排序呢?

可以把比较器作为参数传进去,例如:Collections.sort(list集合,new Comparator比较器)

  1. package com.bjpowernode.javase;
  2. import java.util.*;
  3. public class CollectionsTest {
  4. public static void main(String[] args) {
  5. // 1、 ArrayList集合不线程安全的
  6. List<String> list = new ArrayList<>();
  7. // 通过Collections集合里的方法变成线程安全的
  8. Collections.synchronizedList(list);
  9. //排序
  10. list.add("abf");
  11. list.add("abx");
  12. list.add("abc");
  13. list.add("abe");
  14. Collections.sort(list); //排序
  15. // 打印
  16. for(String s:list){
  17. System.out.println(s); // abc abe abf abx
  18. }
  19. // 2、如果排序自己写的类,需要自己实现compareTo接口
  20. List<WuGui2> wuGui2s = new ArrayList<>();
  21. wuGui2s.add(new WuGui2(1000));
  22. wuGui2s.add(new WuGui2(800));
  23. // 注意:对List集合中元素排序,需要保证List集合中的元素实现了:Comparable接口。
  24. Collections.sort(wuGui2s);
  25. for(WuGui2 w : wuGui2s){
  26. System.out.println(w);
  27. }
  28. // 3、对Set集合怎么排序呢?
  29. Set<String> set = new HashSet<>();
  30. set.add("king");
  31. set.add("kingsoft");
  32. set.add("king2");
  33. set.add("king1");
  34. //Collections.sort(); //里面只能是List集合
  35. // 所以需要把Set集合转换成List集合
  36. List<String> mylist = new ArrayList<>(set);// 把set集合转过来
  37. Collections.sort(mylist); //这样就能排序了
  38. for(String s:mylist){
  39. System.out.println(s); // king king1 king2 kingsoft
  40. }
  41. // 自定义类型也可以排序,多传一个我们上面讲过的比较器参数。
  42. //Collections.sort(list集合, 比较器对象);
  43. }
  44. }
  45. class WuGui2 implements Comparable<WuGui2>{
  46. int age;
  47. public WuGui2(int age){
  48. this.age = age;
  49. }
  50. public int compareTo(WuGui2 o) {
  51. return this.age - o.age;
  52. }
  53. public String toString() {
  54. return "WuGui2{" +
  55. "age=" + age +
  56. '}';
  57. }
  58. }

​常用方法

Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法(均为static方法)!

排序操作

  1. - reverse(List):反转 List 中元素的顺序
  2. - shuffle(List):对 List 集合元素进行随机排序
  3. - sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
  4. - sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
  5. - swap(List,intint):将指定 list 集合中的 i 处元素和 j 处元素进行交换
  1. package com.zl.TreeSetTest;
  2. import java.util.Arrays;
  3. import java.util.Collections;
  4. import java.util.List;
  5. public class CollectionsTest {
  6. public static void main(String[] args) {
  7. List<Integer> list = Arrays.asList(1, 3, 7, 5, 4, 2, 15, 12);
  8. // reverse翻转
  9. Collections.reverse(list);
  10. System.out.println(list); // [12, 15, 2, 4, 5, 7, 3, 1]
  11. // 随机排序(相当于洗牌,每次都是随机排序)
  12. Collections.shuffle(list);
  13. System.out.println(list); // [1, 15, 12, 5, 3, 2, 7, 4]
  14. // 进行排序(自然排序)
  15. Collections.sort(list);
  16. System.out.println(list); // [1, 2, 3, 4, 5, 7, 12, 15]
  17. // 指定排序(逆序)
  18. Collections.sort(list,(o1,o2)->{
  19. if (o1 instanceof Integer && o2 instanceof Integer){
  20. return -(o1.intValue() - o2.intValue()); // 逆序排加一个负号-
  21. }
  22. throw new RuntimeException("类型不匹配");
  23. });
  24. System.out.println(list); // [15, 12, 7, 5, 4, 3, 2, 1]
  25. // 交换(指定下标位置进行交换)
  26. Collections.swap(list,0,list.size()-1);
  27. System.out.println(list); // [1, 12, 7, 5, 4, 3, 2, 15]
  28. }
  29. }

查找

  1. - Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素(最右边的数)
  2. - Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素(还是最右边的数,如果此时是按照降序排列的,实际上最右边取的就是最小的数了)
  3. - Object min(Collection):根据元素的自然顺序,返回给定集合中的最小元素(最左边的数)
  4. - Object min(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最小元素
  5. - int binarySearch(List list,T key)在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且必须是可比较大小的,即支持自然排序的。而且集合也事先必须是有序的,否则结果不确定。
  6. - int binarySearch(List list,T key,Comparator c)在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且集合也事先必须是按照c比较器规则进行排序过的,否则结果不确定。
  7. - int frequency(Collection c,Object o):返回指定集合中指定元素的出现次数
  1. package com.zl.TreeSetTest;
  2. import java.util.Arrays;
  3. import java.util.Collections;
  4. import java.util.List;
  5. public class CollectionsTest {
  6. public static void main(String[] args) {
  7. List<Integer> list = Arrays.asList(1, 3, 7, 5, 4, 2, 15, 12);
  8. // 获取最大值
  9. Integer max = Collections.max(list);
  10. System.out.println(max); // 15
  11. // 获取最小值
  12. Integer min = Collections.min(list);
  13. System.out.println(min); // 1
  14. // 二分查找(必须提前有序)
  15. Collections.sort(list); // 排序
  16. System.out.println(list); // [1, 2, 3, 4, 5, 7, 12, 15]
  17. int flag = Collections.binarySearch(list, 5);// 二分查找
  18. System.out.println(flag); // 4
  19. // 某个元素在集合中出现的次数
  20. int frequency = Collections.frequency(list, 1);
  21. System.out.println(frequency); // 1
  22. }
  23. }

复制、替换

  1. - void copy(List dest,List src):将src中的内容复制到dest中
  2. - boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
  3. - 提供了多个unmodifiableXxx()方法,该方法返回指定 Xxx的不可修改的视图。
  1. package com.zl.TreeSetTest;
  2. import java.util.Arrays;
  3. import java.util.Collections;
  4. import java.util.List;
  5. public class CollectionsTest {
  6. public static void main(String[] args) {
  7. List<Integer> src = Arrays.asList(1, 3, 7, 5, 4, 2, 15, 12);
  8. // 直接拷贝会出现问题
  9. // 此时拷贝会出现异常,通过底层源码发现,会srcSize > dest.size()进行比较
  10. // 而dest.size()目前没有元素肯定就是0,所以会抛出 Source does not fit in dest异常
  11. /*ArrayList<Integer> dest = new ArrayList<>();
  12. Collections.copy(dest,src);*/
  13. // 正确的做法
  14. List dest = Arrays.asList(new Object[src.size()]); // 相当于里面放了很多null
  15. Collections.copy(dest,src); // 拷贝的时候会覆盖原来的值
  16. System.out.println(dest); // [1, 3, 7, 5, 4, 2, 15, 12]
  17. // 替换(也相当于修改)
  18. Collections.replaceAll(src,3,6);
  19. System.out.println(src); // [1, 6, 7, 5, 4, 2, 15, 12]
  20. // 把src变成只读
  21. List<Integer> unmodifiableList = Collections.unmodifiableList(src);
  22. unmodifiableList.set(1,666); // UnsupportedOperationException不支持的操作异常,不能在修改
  23. }
  24. }

添加

boolean addAll(Collection  c,T... elements)将所有指定元素添加到指定 collection 中。
  1. package com.zl.TreeSetTest;
  2. import java.util.ArrayList;
  3. import java.util.Collections;
  4. import java.util.List;
  5. public class CollectionsTest {
  6. public static void main(String[] args) {
  7. List<Integer> list = new ArrayList<>();
  8. Collections.addAll(list,666,888,999); // 是一个可变长度的参数
  9. System.out.println(list); // [666, 888, 999]
  10. }
  11. }

同步

Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题:

 

例题实战:模拟斗地主洗牌和发牌,牌没有排序

  1. package com.zl.TreeSetTest;
  2. import java.util.ArrayList;
  3. import java.util.Collections;
  4. /**
  5. * Author:朗朗乾坤
  6. * Package:com.zl.TreeSetTest
  7. * Project:spring-data-redis
  8. *
  9. * @Date:2023/4/15 16:51
  10. */
  11. public class CollectionsTest {
  12. public static void main(String[] args) {
  13. // 准备两个字符数组
  14. String[] num = {"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"};
  15. String[] color = {"方片", "梅花", "红桃", "黑桃"};
  16. ArrayList<String> poker = new ArrayList<>();
  17. // 嵌套遍历,生成24张排放入poker集合当中
  18. for (int i = 0; i < color.length; i++) {
  19. for (int j = 0; j < num.length; j++) {
  20. poker.add(color[i] + num[j]);
  21. }
  22. }
  23. // 添加大小王
  24. poker.add("大王");
  25. poker.add("小王");
  26. // 洗牌
  27. Collections.shuffle(poker);
  28. //发牌
  29. ArrayList tomCards = new ArrayList();
  30. ArrayList jerryCards = new ArrayList();
  31. ArrayList meCards = new ArrayList();
  32. ArrayList lastCards = new ArrayList();
  33. for (int i = 0; i < poker.size(); i++) {
  34. if (i >= poker.size() - 3) {
  35. lastCards.add(poker.get(i));
  36. } else if (i % 3 == 0) {
  37. tomCards.add(poker.get(i));
  38. } else if (i % 3 == 1) {
  39. jerryCards.add(poker.get(i));
  40. } else {
  41. meCards.add(poker.get(i));
  42. }
  43. }
  44. //看牌
  45. System.out.println("Tom:\n" + tomCards);
  46. System.out.println("Jerry:\n" + jerryCards);
  47. System.out.println("me:\n" + meCards);
  48. System.out.println("底牌:\n" + lastCards);
  49. }
  50. }

执行结果:

 

文章知识点与官方知识档案匹配,可进一步学习相关知识
Java技能树集合Set接口122963 人正在系统学习中
商务合作加V
微信名片