Java从入门到精通 - 集合框架(一)
集合框架(一)
此笔记参考黑马教程,仅学习使用,如有侵权,联系必删
文章目录
- 集合框架(一)
- 1. 集合概述
- 1.1 集合体系结构
- 1.2 Collection 集合体系
- 1.3 Collection 集合特点
- 代码演示
- 2. Collection 的常用方法
- 2.1 常用方法
- 代码演示
- 3. Collection 的遍历方式
- 3.1 迭代器
- 3.1.1 迭代器概述
- 3.1.2 Collection 集合获取迭代器的方法
- 3.1.3 Iterator 迭代器中的常用方法
- 代码演示
- 总结
- 3.2 增强 for
- 3.2.1 格式
- 代码演示
- 3.3 lambda 表达式
- 3.3.1 Lambda 表达式遍历集合
- 代码演示
- 案例:遍历集合中的自定义对象
- 代码实现
- 集合存储对象的原理
- 总结
- 4. List 集合
- 4.1 特点、特有方法
- 4.1.1 List 集合的特有方法
- 代码演示
- 总结
- 4.2 遍历方式
- 4.2.1 List 集合支持的遍历方式
- 代码演示
- 4.3 ArrayList 集合的底层原理
- 4.3.1 ArrayList 集合的底层原理
- 4.3.2 ArrayList 集合适合的应用场景
- 4.4 LinkedList 集合的底层原理
- 4.4.1 什么是链表?有啥特点?
- 双向链表
- 4.4.2 LinkedList 新增了:很多首尾操作的特有方法
- 4.4.3 LinkedList 的应用场景之一:可以用来设计队列
- 代码实现
- 4.4.4 LinkedList 的应用场景之一:可以用来设计栈
- 代码演示
- 5. Set 集合
- 5.1 特点
- 代码演示
- 5.2 HashSet 集合的底层原理
- 5.2.1 哈希值
- 对象哈希值的特点
- 代码演示
- 5.2.2 HashSet 集合的底层原理
- 哈希表
- 5.2.3 JDK8 之前 HashSet 集合的底层原理,基于哈希表:数组 + 链表
- 5.2.4 JDK8 开始 HashSet 集合的底层原理,基于哈希表:数组 + 链表 + 红黑树
- 5.2.5 了解一下数据结构(树)
- 普通二叉树
- 二叉查找树
- 平衡二叉树
- 红黑树
- 5.2.6 深入理解 HashSet 集合去重复的机制
- 代码演示
- 5.3 LinkedHashSet 集合的底层原理
- 5.3.1 LinkedHashSet 底层原理
- 5.4 TreeSet 集合
- 5.4.1 自定义排序规则
- 方式一
- 方式二
- 两种方式中,关于返回值的规则:
- 代码演示
- 总结
1. 集合概述
集合是一种容器,用来装数据的,类似于数组,但集合的大小可变,开发中也非常常用
1.1 集合体系结构
- Collection 代表单列集合,每个元素(数据)只包含一个值
- Map 代表双列集合,每个元素包含两个值(键值对)
1.2 Collection 集合体系
1.3 Collection 集合特点
- List 系列集合:添加的元素是有序、可重复、有索引
- ArrayList、LinekdList:有序、可重复、有索引
- Set 系列集合:添加的元素是无序、不重复、无索引
- HashSet:无序、不重复、无索引
- LinkedHashSet:有序、不重复、无索引
- TreeSet:按照大小默认升序排序、不重复、无索引
代码演示
package Advanced.e_collection.d1_collection;import java.util.ArrayList;
import java.util.HashSet;/*** 目标:认识Collection体系的特点*/
public class CollectionTest1 {public static void main(String[] args) {// 简单确认一下Collection集合的特点ArrayList<String> list = new ArrayList<>(); // 有序、可重复、有索引list.add("java1");list.add("java2");list.add("java1");list.add("java2");System.out.println(list); // [java1, java2, java1, java2]HashSet<String> set = new HashSet<>(); // 无序、不重复、无索引set.add("java1");set.add("java2");set.add("java1");set.add("java2");set.add("java3");System.out.println(set); // [java3, java2, java1]}
}
2. Collection 的常用方法
- Collection 是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的
2.1 常用方法
方法名 | 说明 |
---|---|
public boolean add(E e) | 添加元素,添加成功返回 true |
public void clear() | 清空集合的元素 |
public boolean isEmpty() | 判断集合是否为空,是空返回 true |
public int size() | 获取集合的大小 |
public boolean contains(Object obj) | 判断集合中是否包含某个元素 |
public boolean remove(E e) | 删除某个元素:如果有多个重复元素默认删除前面的第一个 |
public Object[] toArray() | 把集合准换成数组 |
代码演示
package Advanced.e_collection.d1_collection;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;public class CollectionTest2API {public static void main(String[] args) {Collection<String> c = new ArrayList<>(); // 多态写法// 1. public boolean add(E e):添加元素,添加成功返回truec.add("java1");c.add("java1");c.add("java2");c.add("java2");c.add("java3");System.out.println(c); // [java1, java1, java2, java2, java3]// 2. public void clear():清空集合的元素c.clear();System.out.println(c); // []// 3. public boolean isEmpty():判断集合是否为空,是空返回trueSystem.out.println(c.isEmpty()); // true// 4. public int size():获取集合的大小System.out.println(c.size()); // 0// 5. public boolean contains(Object obj):判断集合中是否包含某个元素System.out.println(c.contains("java1"));System.out.println(c.contains("Java1"));// 6. public boolean remove(E e):删除某个元素:如果有多个重复元素默认删除前面的第一个System.out.println(c.remove("java1"));System.out.println(c);// 7. public Object[] toArray():把集合准换成数组Object[] arr = c.toArray();System.out.println(Arrays.toString(arr));// 非要转成String类型的数组String[] arr2 = c.toArray(new String[c.size()]);System.out.println(Arrays.toString(arr2));System.out.println("--------------------------------------------------------");// 把一个集合的全部数据导入到另一个集合中去Collection<String> c1 = new ArrayList<>();c1.add("java1");c1.add("java2");Collection<String> c2 = new ArrayList<>();c2.add("java3");c2.add("java4");c1.addAll(c2); // 把c2集合的全部数据导入到c1集合中去System.out.println(c1); // [java1, java2, java3, java4]System.out.println(c2); // [java3, java4]}
}
3. Collection 的遍历方式
3.1 迭代器
3.1.1 迭代器概述
- 迭代器是用来遍历集合的专用方式(数组没有迭代器),在 Java 中迭代器的代表是 Iterator
3.1.2 Collection 集合获取迭代器的方法
方法名称 | 说明 |
---|---|
Iterator iterator() | 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素 |
3.1.3 Iterator 迭代器中的常用方法
方法名称 | 说明 |
---|---|
boolean hsaNext() | 询问当前位置是否有元素存在,存在返回 true,不存在返回 false |
E next() | 获取当前位置的元素,并同时将迭代器对象指向下一个元素处 |
代码演示
package Advanced.e_collection.d2_collection_traverse;import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;/*** 目标:Collection集合的遍历方式一:使用迭代器Iterator遍历*/
public class CollectionDemo1 {public static void main(String[] args) {Collection<String> c = new ArrayList<>();c.add("赵敏");c.add("小昭");c.add("素素");c.add("灭绝");System.out.println(c); // [赵敏, 小昭, 素素, 灭绝]// 使用迭代器遍历集合// 1. 从集合对象中获取迭代器对象Iterator<String> it = c.iterator();
// System.out.println(it.next()); // 赵敏
// System.out.println(it.next()); // 小昭
// System.out.println(it.next()); // 素素
// System.out.println(it.next()); // 灭绝
// System.out.println(it.next()); // 报错// 2. 我们应该使用循环结合迭代器遍历集合while (it.hasNext()) {String ele = it.next();System.out.println(ele);}}
}
总结
- 如何获取集合的迭代器?迭代器遍历集合的代码具体怎么写?
-
Iterator<String> it = c.iterator();
-
while (it.hasNext()) {String ele = it.next();System.out.println(ele); }
- 通过迭代器获取集合的元素,如果取元素越界会出现什么异常?
- NoSuchElementException
3.2 增强 for
3.2.1 格式
for (元素的数据类型 变量名 : 数组或者集合) {}
- 增强 for 可以用来遍历集合或者数组
- 增强 for 遍历集合,本质就是迭代器遍历集合的简化写法
代码演示
package Advanced.e_collection.d2_collection_traverse;import java.util.ArrayList;
import java.util.Collection;/*** 目标:Collection集合的遍历方式二:增强for*/
public class CollectionDemo2 {public static void main(String[] args) {Collection<String> c = new ArrayList<>();c.add("赵敏");c.add("小昭");c.add("素素");c.add("灭绝");System.out.println(c); // [赵敏, 小昭, 素素, 灭绝]// 使用增强for遍历集合或者数组for (String ele : c) {System.out.println(ele);}String[] names = {"张三", "李四", "王五"};for (String name : names) {System.out.println(name);}}
}
3.3 lambda 表达式
3.3.1 Lambda 表达式遍历集合
- 得益于 JDK8 开始的新技术 Lambda 表达式,提供了一种更简单、更直接的方式来遍历集合
需要使用 Collection 的如下方法来完成
方法名称 | 说明 |
---|---|
default void forEach(Consumer<? super T> action) | 结合 lambda 遍历集合 |
代码演示
package Advanced.e_collection.d2_collection_traverse;import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;/*** 目标:Collection集合的遍历方式三:JDK8开始新增的Lambda表达式*/
public class CollectionDemo3 {public static void main(String[] args) {Collection<String> c = new ArrayList<>();c.add("赵敏");c.add("小昭");c.add("素素");c.add("灭绝");System.out.println(c); // [赵敏, 小昭, 素素, 灭绝]// default void forEach(Consumer<? super T> action):结合Lambda表达式遍历集合
// c.forEach(new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// });
//
// c.forEach((String s) -> {
// System.out.println(s);
// });
//
// c.forEach((s) -> {
// System.out.println(s);
// });
//
// c.forEach(s -> System.out.println(s));c.forEach(System.out::println);}
}
案例:遍历集合中的自定义对象
需求:
- 展示多部电影信息
分析:
- 每部电影都是一个对象,多部电影要使用集合装起来
- 遍历集合中的3个电影对象,输出每部电影的详情信息
代码实现
package Advanced.e_collection.d2_collection_traverse;import java.util.ArrayList;
import java.util.Collection;/*** 目标:完成电影信息的展示* new Movie("《肖申克的救赎》", 9.7, "罗宾斯")* new Movie("《霸王别姬》", 9.6, "张国荣、张丰毅")* new Movie("《阿甘正传》", 9.5, "汤姆.汉克斯")*/
public class CollectionTest4 {public static void main(String[] args) {// 1. 创建一个集合容器负责存储多部电影对象Collection<Movie> movies = new ArrayList<>();movies.add(new Movie("《肖申克的救赎》", 9.7, "罗宾斯"));movies.add(new Movie("《霸王别姬》", 9.6, "张国荣、张丰毅"));movies.add(new Movie("《阿甘正传》", 9.5, "汤姆.汉克斯"));System.out.println(movies); // 集合的存储对象存的并不是元素本身,而是元素的地址,通过元素地址到栈里面获取元素for (Movie movie : movies) {System.out.println("电影名:" + movie.getName());System.out.println("评分:" + movie.getScore());System.out.println("主演:" + movie.getActor());System.out.println("---------------------------------------");}}
}
集合存储对象的原理
总结
- 集合中存储的是元素的什么信息?
- 集合中存储的是元素对象的地址
4. List 集合
4.1 特点、特有方法
4.1.1 List 集合的特有方法
- List 集合因为支持索引,所以多了很多与索引相关的方法,当然,Collection 的功能 List 也都继承了
方法名称 | 说明 |
---|---|
void add(int index, E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index, E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
代码演示
package Advanced.e_collection.d3_collection_list;import java.util.ArrayList;
import java.util.List;/*** 目标:掌握List系列集合的特点,以及其提供的特有方法*/
public class ListTest1 {public static void main(String[] args) {// 1. 创建一个ArrayList集合对象(有序、可重复、有索引)List<String> list = new ArrayList<>(); // 一行经典代码list.add("蜘蛛精");list.add("至尊宝");list.add("至尊宝");list.add("牛夫人");System.out.println(list); // [蜘蛛精, 至尊宝, 至尊宝, 牛夫人]// 2. public void add(int index, E element):在某个索引位置插入元素list.add(2, "紫霞仙子");System.out.println(list); // [蜘蛛精, 至尊宝, 紫霞仙子, 至尊宝, 牛夫人]// 3. public E remove(int index):删除指定索引处的元素,返回被删除的元素System.out.println(list.remove(2)); // 紫霞仙子System.out.println(list); // [蜘蛛精, 至尊宝, 至尊宝, 牛夫人]// 4. public E get(int index):返回指定索引处的元素System.out.println(list.get(3)); // 牛夫人// 5. public E set(int index, E element):修改指定索引处的元素,返回被修改的元素System.out.println(list.set(3, "牛魔王")); // 牛夫人System.out.println(list); // [蜘蛛精, 至尊宝, 至尊宝, 牛魔王]}
}
总结
- List 系列集合的特点是什么?
- 有序、可重复、有索引
- List 提供了哪些独有的方法?
- set、get
4.2 遍历方式
4.2.1 List 集合支持的遍历方式
- for 循环(因为 List 集合有索引)
- 迭代器
- 增强 for 循环
- Lambda 表达式
代码演示
package Advanced.e_collection.d3_collection_list;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class ListTest2 {public static void main(String[] args) {List<String> list = new ArrayList<>(); // 一行经典代码list.add("糖宝宝");list.add("蜘蛛精");list.add("至尊宝");// 1. for循环for (int i = 0; i < list.size(); i++) {String s = list.get(i);System.out.println(s);}// 2. 迭代器Iterator<String> it = list.iterator();while (it.hasNext()) {System.out.println(it.next());}// 3. 增强for循环(foreach遍历)for (String s : list) {System.out.println(s);}// 4. JDK1.8开始之后的Lambda表达式list.forEach(s -> {System.out.println(s);});}
}
4.3 ArrayList 集合的底层原理
ArrayList 和 LinkedList 底层采用的数据结构不同,应用场景不同
数据结构:存储、组织数据的方式
4.3.1 ArrayList 集合的底层原理
-
基于数组实现的
-
查询速度快(注意:是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同
-
删除效率低:可能需要把后面很多的数据进行前移
-
添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容
-
- 利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组
- 添加第一个元素时,底层会创建一个新的长度为10的数组
- 存满时,会扩容1.5倍
- 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
4.3.2 ArrayList 集合适合的应用场景
- ArrayList 适合:根据索引查询数据,比如根据随机索引取数据(高效)!或者数据量不是很大时!
- ArrayList 不适合:数据量大的同时,又要频繁的进行增删操作
4.4 LinkedList 集合的底层原理
- 基于双链表实现的
- 特点:查询慢。增删相对较快,但对首尾元素进行增删改查的速度是极快的
4.4.1 什么是链表?有啥特点?
- 链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址
- 链表的特点1:查询慢,无论查询哪个数据都要从头开始找
- 链表的特点2:链表的增删相对快
双向链表
- 特点:查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的
4.4.2 LinkedList 新增了:很多首尾操作的特有方法
方法名称 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
4.4.3 LinkedList 的应用场景之一:可以用来设计队列
队列的特点:先进先出,后进后出
只是在首尾增删元素,用 LinkedList 来实现很合适
代码实现
package Advanced.e_collection.d3_collection_list;import java.util.LinkedList;/*** 目标:掌握LinkedList集合的使用*/
public class ListTest3 {public static void main(String[] args) {// 1. 创建一个队列LinkedList<String> queue = new LinkedList<>();queue.addLast("第1号人");queue.addLast("第2号人");queue.addLast("第3号人");queue.addLast("第4号人");System.out.println(queue); // [第1号人, 第2号人, 第3号人, 第4号人]// 出队System.out.println(queue.removeFirst()); // 第1号人System.out.println(queue.removeFirst()); // 第2号人System.out.println(queue.removeFirst()); // 第3号人System.out.println(queue); // [第4号人]}
}
4.4.4 LinkedList 的应用场景之一:可以用来设计栈
栈的特点:后进后出,先进后出
只是在首部增删元素,用 LinkedList 来实现很合适
代码演示
package Advanced.e_collection.d3_collection_list;import java.util.LinkedList;/*** 目标:掌握LinkedList集合的使用*/
public class ListTest3 {public static void main(String[] args) {// 2. 创建一个栈对象LinkedList<String> stack = new LinkedList<>();// 进栈stack.push("第1颗子弹");stack.push("第2颗子弹");stack.push("第3颗子弹");stack.push("第4颗子弹");System.out.println(stack); // [第4颗子弹, 第3颗子弹, 第2颗子弹, 第1颗子弹]// 出栈System.out.println(stack.pop()); // 第4颗子弹System.out.println(stack.pop()); // 第3颗子弹System.out.println(stack); // [第2颗子弹, 第1颗子弹]}
}
5. Set 集合
5.1 特点
Set 系列集合特点:无序:添加数据的顺序和获取出的数据顺序不一致;不重复;无索引;
- HashSet:无序、不重复、无索引
- LinkedHashSet:有序、不重复、无索引
- TreeSet:排序、不重复、无索引
注意:
Set 要用到的常用方法,基本上就是 Collection 提供的!!!自己几乎没有额外新增一些常用功能!
代码演示
package Advanced.e_collection.d4_collection_set;import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;/*** 目标:整体了解一下Set系列集合的特点*/
public class SetTest1 {public static void main(String[] args) {// 1. 创建一个Set集合的对象Set<Integer> set = new HashSet<>(); // 创建了一个HashSet的集合对象,一行经典代码 无序、不重复、无索引set.add(666);set.add(555);set.add(555);set.add(888);set.add(888);set.add(777);set.add(777);System.out.println(set); // [888, 777, 666, 555]System.out.println("-------------------------------");Set<Integer> set2 = new LinkedHashSet<>(); // 有序、不重复、无索引set2.add(666);set2.add(555);set2.add(555);set2.add(888);set2.add(888);set2.add(777);set2.add(777);System.out.println(set2); // [666, 555, 888, 777]System.out.println("-------------------------------");Set<Integer> set3 = new TreeSet<>(); // 可排序、不重复、无索引set3.add(666);set3.add(555);set3.add(555);set3.add(888);set3.add(888);set3.add(777);set3.add(777);System.out.println(set3); // [555, 666, 777, 888]}
}
5.2 HashSet 集合的底层原理
5.2.1 哈希值
- 就是一个 int 类型的数值,Java 中每个对象都有一个哈希值
- Java 中的所有对象,都可以调用 Object 类提供的 hashCode 方法,返回该对象自己的哈希值
public int hashCode(); // 返回对象的哈希码值
对象哈希值的特点
- 同一个对象多次调用 hashCode() 方法返回的哈希值是相同的
- 不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)
代码演示
package Advanced.e_collection.d4_collection_set;/*** 目标:了解一下哈希值*/
public class SetTest2 {public static void main(String[] args) {Student s1 = new Student("蜘蛛精", 25, 169.5);Student s2 = new Student("紫霞", 22, 166.5);System.out.println(s1.hashCode()); // 189568618System.out.println(s1.hashCode()); // 189568618System.out.println(s2.hashCode()); // 793589513String str1 = new String("abc");String str2 = new String("acD");// 这里例子是故意设计的,是为了演示哈希碰撞,实际中并不会这么容易就撞,感兴趣的朋友可以去阅读String的hashCode()源码,就能明白为什么这两个会撞了System.out.println(str1.hashCode()); // 96354System.out.println(str2.hashCode()); // 96354}
}
5.2.2 HashSet 集合的底层原理
- 基于哈希表实现
- 哈希表是一种增删改查数据,性能都较好的数据结构
哈希表
- JDK8 之前,哈希表 = 数组 + 链表
- JDK8 之后,哈希表 = 数组 + 链表 + 红黑树
5.2.3 JDK8 之前 HashSet 集合的底层原理,基于哈希表:数组 + 链表
- 创建一个默认长度为16的数组,默认加载因子为0.75,数组名为 table
- 使用元素的哈希值对数组的长度求余计算出应存入的位置
- 判断当前位置是否为 null,如果是 null 直接存入
- 如果不为 null,表示有元素,则调用 equals 方法比较;相等,则不存;不相等,则存入数组
- JDK8 之前,新元素存入数组,占老元素位置,老元素挂下面
- JDK8 开始之后,新元素直接挂在老元素下面
5.2.4 JDK8 开始 HashSet 集合的底层原理,基于哈希表:数组 + 链表 + 红黑树
JDK8 开始,当链表长度超过8,且数组长度 >= 64时,自动将链表转成红黑树
5.2.5 了解一下数据结构(树)
普通二叉树
- 度:每一个节点的子节点数量
- 二叉树中,任意节点的度 <= 2
- 树高:数的总层数
- 根节点:最顶层的节点
- 左子节点
- 右子节点
- 左子树
- 右子树
二叉查找树
二叉查找树存在的问题:
- 当数据已经是排好序的,导致查询的性能与单链表一样,查询速度变慢
平衡二叉树
- 在满足二叉查找树的大小规则下,让树尽可能矮小,以此提高查数据的性能
红黑树
- 就是可以自平衡的二叉树
- 红黑树是一种增删改查数据性能相对都较好的结构
5.2.6 深入理解 HashSet 集合去重复的机制
HashSet 集合默认不能对内容一样的两个不同对象去重复
- 比如内容一样的两个学生对象存入到 HashSet 集合中去,HashSet 集合是不能去重复的!
如果希望 Set 集合认为2个内容一样的对象是重复的,必须重写对象的 hashCode() 和 equals() 方法
代码演示
- Student.java
package Advanced.e_collection.d4_collection_set;import java.util.Objects;public class Student {private String name;private int age;private double height;public Student() {}public Student(String name, int age, double height) {this.name = name;this.age = age;this.height = height;}// 只要两个对象内容一样就返回true@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;return age == student.age && Double.compare(height, student.height) == 0 && Objects.equals(name, student.name);}// 只要两个对象内容一样,返回的哈希值就是一样的@Overridepublic int hashCode() {return Objects.hash(name, age, height);}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public double getHeight() {return height;}public void setHeight(double height) {this.height = height;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", height=" + height +'}';}
}
- SetTest3.java
package Advanced.e_collection.d4_collection_set;import java.util.HashSet;
import java.util.Set;/*** 目标:自定义的类型的对象,比如两个内容一样的学生对象,如果让HashSet集合能够去重复*/
public class SetTest3 {public static void main(String[] args) {Set<Student> students = new HashSet<>();Student s1 = new Student("至尊宝", 28, 169.6);Student s2 = new Student("蜘蛛精", 23, 169.6);Student s3 = new Student("蜘蛛精", 23, 169.6);System.out.println(s2.hashCode()); // 573521603System.out.println(s3.hashCode()); // 573521603Student s4 = new Student("牛魔王", 48, 169.6);students.add(s1);students.add(s2);students.add(s3);students.add(s4);System.out.println(students); // [Student{name='至尊宝', age=28, height=169.6}, Student{name='蜘蛛精', age=23, height=169.6}, Student{name='牛魔王', age=48, height=169.6}]}
}
5.3 LinkedHashSet 集合的底层原理
- 有序、不重复、无索引
5.3.1 LinkedHashSet 底层原理
- 依然是基于哈希表**(数组、链表、红黑树)**实现的
- 但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置
因为有双链表机制记住它前一个元素的地址和下一个元素的地址,所以能实现它有序这个特点
5.4 TreeSet 集合
-
特点:不重复、无索引、可排序(默认升序排序,按照元素的大小,由小到大排序)
-
底层是基于红黑树实现的排序
注意:
- 对于数值类型:Integer、Double,默认按照数值本身的大小进行升序排序
- 对于字符串类型:默认按照首字符的编号升序排序
- 对于自定义类型如 Student 对象,TreeSet 默认是无法直接排序的
5.4.1 自定义排序规则
- TreeSet 集合存储自定义类型的对象时,必须指定排序规则,支持如下两种方式来指定比较规则
方式一
- 让自定义的类(如学生类)实现 Comparable 接口,重写里面的 compareTo 方法来指定比较规则
方式二
- 通过调用 TreeSet 集合有参构造器,可以设置 Comparator 对象(比较器对象,用于指定比较规则)
public TreeSet(Comparator<? super E> comparator)
两种方式中,关于返回值的规则:
- 如果认为第一个元素 > 第二个元素 返回正整数即可
- 如果认为第一个元素 < 第二个元素 返回负整数即可
- 如果如果认为第一个元素 = 第二个元素 返回0即可,此时 TreeSet 集合只会保留一个元素,认为两者重复
注意:如果类本身有实现 Comparable 接口,TreeSet 集合同时也自带比较器,默认使用集合自带的比较器排序
代码演示
- Student.java
package Advanced.e_collection.d4_collection_set;import java.util.Objects;public class Student implements Comparable<Student> {private String name;private int age;private double height;@Overridepublic int compareTo(Student o) {// 如果认为左边对象大于右边对象返回正整数// 如果认为左边对象小于右边对象返回负整数// 如果认为左边对象等于右边对象返回0// 需求:按照年龄排序return this.age - o.age;}public Student() {}public Student(String name, int age, double height) {this.name = name;this.age = age;this.height = height;}// 只要两个对象内容一样就返回true@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;return age == student.age && Double.compare(height, student.height) == 0 && Objects.equals(name, student.name);}// 只要两个对象内容一样,返回的哈希值就是一样的@Overridepublic int hashCode() {return Objects.hash(name, age, height);}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public double getHeight() {return height;}public void setHeight(double height) {this.height = height;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", height=" + height +'}';}
}
- SetTest4.java
package Advanced.e_collection.d4_collection_set;import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;/*** 目标:掌握TreeSet集合的使用*/
public class SetTest4 {public static void main(String[] args) {Set<Integer> set1 = new TreeSet<>();set1.add(6);set1.add(5);set1.add(5);set1.add(7);System.out.println(set1); // [5, 6, 7]// 就近选择自己自带的比较器对象进行排序
// Set<Student> students = new TreeSet<>(new Comparator<Student>() {
// @Override
// public int compare(Student o1, Student o2) {
// // 需求:按照身高升序排序
// return Double.compare(o1.getHeight(), o2.getHeight());
// }
// });Set<Student> students = new TreeSet<>((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight()));students.add(new Student("蜘蛛精", 23, 169.7));students.add(new Student("紫霞", 22, 169.8));students.add(new Student("至尊宝", 26, 165.5));students.add(new Student("牛魔王", 22, 183.5));System.out.println(students);}
}
总结
- 如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据?
- 用 ArrayList 集合(有序、可重复、有索引),底层基于数组的**(常用)**
- 如果希望记住元素的添加顺序,且增删首尾数据的情况较多?
- 用 LinkedList 集合(有序、可重复、有索引),底层基于双链表实现的
- 如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快?
- 用 HashSet 集合(无序、不重复、无索引),底层基于哈希表实现的**(常用)**
- 如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快?
- 用 LinkedHashSet 集合(有序、不重复、无索引),底层基于哈希表和双链表
- 如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快?
- 用 TreeSet 集合,基于红黑树实现