集合框架(详解)
什么是集合框架?
集合框架是一个统一的架构,用于表示和操作集合,使集合能够独立于其实现细节进行操作,Java集合框架提供了一系列接口和类,用于存储检索操作和传输数据对象
为什么需要集合框架?
-
提供高性能、高质量的数据结构和算法实现
-
减少编程工作量,通过提供现成的数据结构
-
提供接口和实现分离的设计,使代码更具可重用性和互操作性
-
建立通用语言,方便开发者交流算法和数据结构
集合框架的组成部分
Java集合框架主要由三部分组成:
-
接口(Interfaces):表示集合的抽象数据类型。允许集合独立于其实现细节进行操作。
-
实现类(Implementations):接口的具体实现,即可重用的数据结构。
-
算法(Algorithms):对实现了集合接口的对象进行操作的方法,如搜索和排序。
集合体系结构
分为单列集合(collection)与双列结合(Map)
单列集合:添加数据的时候,每次只能添加一个元素
双列集合:添加数据时,每次需要添加两个元素
单列集合(collection)
List系列集合:添加的元素是有序,可重复,有索引
-
有序:存、取元素的顺序一致
-
可重复:集合中存储的元素是可以重复的
-
有索引:我们可以通过索引获得集合中的每一个元素
Set系列集合:添加的元素是无序、不重复,无索引
-
无序:存或取的元素的顺序可能是一致的,也可能不是
-
不重复:集合中不能存储重复的元素,我们可以利用这个特性去重
-
无索引:我们不可以通过索引获得set中的每一个元素
Collection接口
collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的
主要方法:
boolean add(E e) - 添加元素boolean remove(Object o) - 移除元素 注:是共性方法,不能通过索引删除,只能通过元素的对象进行删除boolean contains(Object o) - 检查是否包含指定元素int size() - 返回集合中的元素数量boolean isEmpty() - 检查集合是否为空Iterator<E> iterator() - 返回迭代器,默认指向当前集合的0索引void clear() - 清空集合boolean containsAll(Collection<?> c) - 检查是否包含另一个集合的所有元素boolean addAll(Collection<? extends E> c) - 添加另一个集合的所有元素boolean removeAll(Collection<?> c) - 移除同时存在于指定集合中的所有元素boolean retainAll(Collection<?> c) - 仅保留同时存在于指定集合中的元素Object[] toArray() - 返回包含集合中所有元素的数组
collection的遍历方式
Iterator接口
-
JDK专门提供了一个接口
java.util.Iterator
遍历集合中的所有元素
-
Collection接口与Map接口主要用于存储元素
-
Iterator
,被称为迭代器接口,本身并不提供存储对象的能力,主要用于遍历
Collection中的元素
-
-
Collection接口继承了java.lang.Iterable接口
-
该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象
-
public Iterator iterator()
: 获取集合对应的迭代器,用来遍历集合中的元素的 -
集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前
-
-
Iterator接口的常用方法如下
public E next():,回去当前元素,并移动指针public boolean hasNext():如果仍有元素可以迭代,则返回 true
-
注意:在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常
@Testpublic void test3(){Collection coll = new ArrayList();coll.add("小李广");coll.add("扫地僧");coll.add("石破天");Iterator iterator = coll.iterator(); //获取迭代器对象while(iterator.hasNext()) { //判断是否还有元素可迭代System.out.println(iterator.next()); //取出下一个元素}}
-
使用Iterator迭代器删除元素:java.util.Iterator迭代器中有一个方法:
void remove()
@Testpublic void test4(){Collection coll = new ArrayList();coll.add(1);coll.add(2);coll.add(3);coll.add(4);coll.add(5);coll.add(6);Iterator iterator = coll.iterator();while(iterator.hasNext()){Integer element = (Integer) iterator.next();if(element % 2 == 0){iterator.remove();}}System.out.println(coll); // [1, 3, 5]}
2、迭代器的执行原理
-
Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素
-
接下来通过一个图例来演示Iterator对象迭代元素的过程
增强for循环便利
Iterable接口
public interface Iterable<T> {Iterator<T> iterator();// JDK 1.8default void forEach(Consumer<? super T> action) {Objects.requireNonNull(action);for (T t : this) {action.accept(t);}}}
可以看到Iterable
接口里面提供了Iterator
接口,所以实现了Iterable
接口的集合依旧可以使用迭代器
遍历和操作集合中的对象;
格式:
for ( 元素的数据类型 变量名 :数组或者集合){}
案例展示:
package com;import java.util.*;public class iteratorTest {public static void main(String[] args) {Collection<String> strings = new ArrayList<>();//{"苏宇","星宇","周天","星辰","日月"};strings.add("苏宇");strings.add("星宇");strings.add("周天");strings.add("星辰");Iterator<String> iterator = strings.iterator();//迭代器遍历集合while (iterator.hasNext()){System.out.println(iterator.next());}System.out.println("=============================");//增强for循环 本质上就是迭代器遍历集合for (String string : strings) {System.out.println(string);}System.out.println("=============================");//foreach 源码中就是迭代器遍历集合strings.forEach(System.out::println);}}
重点总结:
-
Iterator是提供集合操作内部对象的一个迭代器,他可以便历,移除对象,而且只能单向移动
-
Iterable是对Iterator的封装,在
JDK 1.8
时,实现了Iterable
接口的集合可以使用增强 for 循环遍历集合对象,我们通过反编译后发现底层还是使用Iterator
迭代器进行遍历 -
ListIterator
。它继承 Iterator 接口,在遍历List
集合时可以从任意索引下标开始遍历,而且支持双向遍历。 -
ListIterator 存在于 List 集合之中,通过调用方法可以返回起始下标为
index
的迭代器 -
ListIterator 中有几个重要方法,大多数方法与 Iterator 中定义的含义相同,但是比 Iterator 强大的地方是可以在任意一个下标位置返回该迭代器,且可以实现双向遍历。(可以自己去源码中查看)
lambda表达式遍历
提供了更简单更直接的方式来遍历集合
@FunctionalInterface//这个注解代表这个接口可以使用lambda表达式strings.forEach(new Consumer<String>() {@Overridepublic void accept(String s) {System.out.println(s);}});//lambda表达式strings.forEach((String s)->{System.out.println(s);});strings.forEach(s->{System.out.println(s);});strings.forEach(s -> System.out.println(s));strings.forEach(System.out::println);
List集合
List
接口直接继承 Collection 接口,它定义为可以存储重复元素的集合,并且元素按照插入顺序有序排列,且可以通过索引访问指定位置的元素。常见的实现有:ArrayList、LinkedList、Vector和Stack
-
Arraylist:有序、可重复、有索引
-
Linked List:有序、可重复、有索引
-
不同点:底层采用的数据结构(存储,组织数据的方式)不同,应用场景不同
额外的主要方法:
-
E get(int index) - 获取指定位置的元素E set(int index, E element) - 替换指定位置的元素void add(int index, E element) - 在指定位置插入元素E remove(int index) - 移除指定位置的元素int indexOf(Object o) - 返回指定元素第一次出现的索引int lastIndexOf(Object o) - 返回指定元素最后一次出现的索引List<E> subList(int fromIndex, int toIndex) - 返回指定范围内的子列表
public static void main(String[] args) {List<String> strings = new ArrayList<>();strings.add("hello");strings.add("world");strings.add("java");strings.add("javaSE");strings.forEach(System.out::println);strings.remove(1);strings.forEach(System.out::println);strings.get(1);strings.forEach(System.out::println);// 修改 索引位置处的元素,修改成功后返回原来的数据strings.set(1, "javaEE");strings.forEach(System.out::println);}
List集合支持的遍历方式
-
for循环
-
迭代器
-
增强for循环
-
Lambda表达式
拓展知识点
数组概述:数组是在我们内存中的一块连续区域,并且会把这块区域分割成若干个相等的小区域,每块区域都有自己的索引,每个区域都是用来装数据的
数组特点:查询速度快(注意:是根据索引查询数据快)原因:数组有自己的起始地址,一般都需要一个一个遍历到想要的元素,但数组有索引,它可以根据指定索引来直接加几个单位的长度来获取索要的元素(element)
增删慢:可能需要把后面很多的数据进行前移或后移
ArrayList的底层原理
-
基于动态数组实现的
-
查询速度快(是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同
-
删除效率低:可能需要把后面很多的数据进行前移
-
添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容。
-
非同步(线程不安全)
底层原理分析
-
利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组
-
添加第一个元素时,底层会创建一个长度为10的数组,将元素填入第0个索引,并将size后移到第一个索引,再添加也是如此
-
存满时,扩容1.5倍,再创建一个相对于原数组1.5倍的新数组,再将数据迁移进去
-
如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准即10+添加的数组长度
Array List集合使用的应用场景
1.适合场景:根据索引查询数据,比如根据随机索引取数据(高效)!或者数据量不是很大时
2.不适合场景:数据量大的同时,又要频繁的进行增删操作
拓展知识
什么是链表?有啥特点?
-
链表是由一个一个的结点组成的,结点都是独立的对象,和数组不一样,在内存中是分散存储的,每个结点除了包含的数据内容,还会包含下一个结点的地址信息,通过这个地址信息是可以找到下一个结点的
-
链表的特点:查询慢,无论查询你那个数据都要从头开始找,根据索引寻找的时候,也要从头开始找,因为结点在内存中是分散存储的
-
链表的特点:链表增删相对快,原理:链表可以直接让想要删除的元素的前一个元素的地址指向想要删除的元素的后一个元素,元素就删除了
-
增加的时候,将要插入的地方的两边插入地址即可,
-
单向链表:只能从前往后遍历,因为结点只记得了下一个结点的地址,单链表只有头结点
-
双向链表:每个结点除了要包含下一个结点的地址信息,还要包含上一个结点的地址信息,双链表是有头结点和尾结点
-
双向链表特特点:查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的 原因:双向链表中有具体的头结点地址和结节点地址,通过地址可以快速找到第一个数据和最后一个数据,
LinkList的底层原理
-
基于双链表实现的
-
查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的
LinkedList新增了许多守卫操作的特有方法
void addFirst(E e)- 在该列表开头插入指定的元素void addLast(E e)- 将指定的元素追加到列表的末尾E getFirst()- 返回该列表的第一个元素E getLast() - 返回该列表的最后一个元素E removeFirst() - 从此列表删除并返回第一个元素E removeLast() - 从此列表删除并返回最后一个元素
LinkedList的应用场景之一:可以用来设计队列
队列特点:先进先出,后进后出,大多数的叫号,排队系统,都是在尾部增加数据,首部删除数据,且要保持数据有序,所以用LinkedList来实现很合适
代码示例
package com.lyc.test;import java.util.LinkedList;public class LinkedListTest {public static void main(String[] args) {LinkedList<String> queue = new LinkedList<>();//入队操作queue.addLast("第一号人");queue.addLast("第二号人");queue.addLast("第三号人");queue.addLast("第四号人");System.out.println(queue);System.out.println("出队操作");while (!queue.isEmpty()){System.out.println(queue.removeFirst());}System.out.println(queue);}}
LinkedList的应用场景之一:可以用来设计栈
栈的特点:后进先出,先进后出。一段开口,一段封闭,
数据进入栈的模型的过程称为压/进栈(push)
数据离开栈模型的过程称为弹/出栈(pop)
只是在首部增删元素,用LinkedList来实现很合适
举例:在设计子弹的射出情况时,就可以设计一个栈的模型来描述,先压进去的子弹最后发射,最后压进去的子弹先射击
代码展示:
LinkedList<String> stack = new LinkedList<>();//压栈(push)stack.push("第一发子弹"); //push和addFisrt功能一样 在源码中stack.push("第二发子弹");stack.push("第三发子弹");System.out.println(stack);//弹栈(pop)while (!stack.isEmpty()){System.out.println(stack.pop());//pop和removeFirst功能一样 在源码中}System.out.println(stack);
set集合
set 系列集合特点:
-
无序:存或取的元素的顺序可能是一致的,也可能不是
-
不重复:集合中不能存储重复的元素,我们可以利用这个特性去重
-
无索引:我们不可以通过索引获得set中的每一个元素
-
Set
接口没有在Collection
接口之外添加新方法,只是改变了一些方法的行为语义。例如,add
方法对于已存在的元素将返回false
。-
HashSet:无序,不重复,无索引
-
LinkedHashSet:有序,不重复,无索引
-
TreeSet:排序、不重复、无索引
根据源码得:
HashSet
底层利用了HashMap
,TreeSet
底层用了TreeMap
,LinkedHashSet
底层用了LinkedHashMap
。 -
代码展示:
package com.lyc.test;import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;public class SetTest {public static void main(String[] args) {//创建一个set对象 无序,不重复,无索引Set<Integer> set = new HashSet<>();//一行经典代码,在开发中用得较多getSet(set);System.out.println(set);System.out.println("========================");//创建一个LinkedSet对象 有序,不重复,有索引Set<Integer> linkedSet = new LinkedHashSet<>();getSet(linkedSet);System.out.println(linkedSet);System.out.println("========================");//创建一个TreeSet对象 排序,不重复,无索引,默认升序排序Set<Integer> treeSet = new TreeSet<>();getSet(treeSet);System.out.println(treeSet);}public static void getSet(Set<Integer> set){set.add(111);set.add(222);set.add(322);set.add(444);set.add(322);set.add(345);}
}
注:Set要用到的常用方法,基本上就是collection提供的,自己几乎没有额外的新增功能
HsahSet集合的底层原理
需要解决的问题
-
为什么添加的元素无序,不重复,无索引?
-
增删改查数据有什么特点?适合什么场景?
前置知识点:哈希值
-
就是一个int类型的数值,每个Java对象都有一个哈希值
-
Java所有的对象,都可以调用Object类提供的hashCode方法,返回对象自己的hash值
public int hashCode()
:返回对象的哈希值
对象哈希值的特点
-
同一个对象多次调用hashCode()方法返回的哈希值是相同的。
-
不同的对象,他们的哈希值一般不相同,有极小概率相同(哈希碰撞)
代码展示:
package com.lyc.test;import com.lyc.pojo.Student;public class HsahCodeTest {public static void main(String[] args) {Student s1 = new Student("张三", 18);Student s2 = new Student("lisi", 18);System.out.println(s1.hashCode());System.out.println(s2.hashCode());System.out.println(s1.equals(s2));}}
HashSet集合的底层原理
-
基于哈希表实现
-
哈希表是一种给增删改查数据,性能都较好的数据结构
哈希表
JDK8以前:哈希表 = 数组+链表
JDK8之后:哈希表=数组+链表+红黑树
JDK8以前HashSet集合的底层原理,基于哈希表:数组+链表
-
创建一个默认长度16的数组,默认加载因子为0.75,数组名 table
-
使用元素的哈希值对数组长度求余计算出应存入的位置 (无序的原因)
-
判断当前位置是否为null,如果是null直接存入
-
如果不为null,表示有元素,则调用equals方法比较 相等,则不存(不重复的原因),不相等,则存入数组(JDK8之前,新元素存入数组,占老元素位置,老元素挂下面 JDK8开始之后,新元素直接挂老元素下面)
-
无序就不支持索引
哈希表是一种增删改查数据性能都较好的结构。 原因:可以直接跟据哈希值来获取元素位置,进行增删改查
HashSet则继承了哈希表这种特性
问题思考:
-
如果数组快占满了,会出现什么问题?应该怎么做?
数组快占满会让链表过长,会导致查询性能降低,
解决方法:哈希表 扩容:将底层的数组长度扩大,再将原来的数据迁移进去,让其分散开来,让链表的长度短些,这样性能就得到了优化,
注:加载因子: 16 x 0.75 = 12,哈希表会在数组元素超过12的时候,哈希表就会扩容成原来的数组的两部,再将原来的数组元素转移
-
就算是扩容了,那链表的长度还是有可能过长,该怎么解决呢
解:JDK8开始,当链表长度超过8时,且数组长度>=64时,自动将链表转成红黑树
小结:JDK8开始后,哈希表中引入了红黑树后,进一步提高了操作数据的性能。且红黑树的左右两边,由哈希值来确定,哈希值小的在左边,哈希值大的在右边
拓展知识:数据结构(树)
二叉树:
-
度:每一个节点的子节点数量 二叉树中,任一结点的度<=2
-
树高:树的总层数
-
根节点:最顶层的节点
-
左子节点
-
右子节点
-
左子树
-
右子树
常用二叉树:二叉查找树(二叉排序树) 规则:小的存左边,大的存右边 ,一样的不存
-
二叉查找树存在的问题:
-
当数据已经是排好序的,导致查询的性能与单链表一样,查询速度变慢!
-
-
解决方法:平衡二叉树
-
在满足查找二叉树的大小规则下,让树尽量矮小,以此提高查找数据的性能
-
性质:它是一颗空树或者它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一颗平衡二叉树
-
红黑树:就是一张可以自平衡的二叉树
红黑树是一种增删改查数据性能相对都较好的结构,(二分查找)
拓展:深入理解HashSet集合去重复的机制
HashSet集合默认不能对内容一样的两个不同对象去重复!
比如内容一样的两个学生对象存入到HashSet集合中,HashSet集合是不能去重复的
代码展示:
package com.lyc.test;import com.lyc.pojo.Student;import java.util.HashSet;import java.util.Set;public class HashSetTest {public static void main(String[] args) {Set<Student> students = new HashSet<>();Student s1 = new Student("张三", 18);Student s2 = new Student("张三", 18);Student s3 = new Student("里斯", 18);Student s4 = new Student("王五", 18);System.out.println(s1.hashCode() == s2.hashCode());students.add(s1);students.add(s2);students.add(s3);students.add(s4);System.out.println(students);}}
如何让HashSet集合能够实现对内容一样的两个不同对象也能去重复?
只需要重写hashcode()方法,与equals()方法
代码展示:
@Overridepublic boolean equals(Object o) {if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;return age == student.age && Objects.equals(name, student.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}
LinkedHashset集合的底层原理
特点:有序、不重复,无索引
代码展示:
package com.lyc.test;import java.util.LinkedHashSet;import java.util.Set;//测试LinkedHashSet集合public class LinkedHashSetTest {public static void main(String[] args) {Set<Integer> set = new LinkedHashSet<>();set.add(111);set.add(233);set.add(444);set.add(333);set.add(222);System.out.println(set);}}
LinkedHashSet底层原理
-
依然是基于哈希表(数组+链表+红黑树)实现的
-
但是,它的每一个元素都额外的多了一个双链表的机制记录他前后元素的位置
过程解析:当存入第一个元素时,依旧是使用哈希值去求余数组长度,但找到位置之后,因为它的每一个元素都多了一个双链表记录机制,所以现在他的首地址和尾地址都是指向它自己的,当第二个元素存储进来后,第二个元素会将自己的地址送给第一个元素,这样我们第一个元素就指向了第二个元素,同时第一个元素的地址也被拿到第二个元素中,接着第二个元素就变成了最后一个元素,同时也拿到了尾地址变量,当第三个元素存储进来后,会通过尾地址变量来找到第二个元素,将自己的地址与第二个元素的地址进行交换,这样,第三个元素成为了最后一个元素,拿到了尾地址变量,以此类推,
这样,再查询数据的时候,就会按照双链表的顺序查找,所以LinkedHashSet是有序的
因为还是基于哈希表实现的,所以LinkedHashSet的增删改查性能也是不错的
缺点:因为LinkedHashSet的每一个元素除了要记住自己的地址,还需要去记住上一个元素与下一个元素的地址,里面记录的信息特别多,所以占用的内存较大,它是通过占用内存来实现自己有序这一特点的
TreeSet集合的底层原理
TreeSet
-
特点:排序(默认升序排序,按照元素的大小,由大到小排序),不重复,无索引
-
底层是基于红黑树实现的排序
代码展示:
Set<Integer> treeSet = new TreeSet<>();getSet(treeSet);public static void getSet(Set<Integer> set){set.add(111);set.add(222);set.add(322);set.add(444);set.add(322);set.add(345);}
注意:
-
对于数值类型:Integer,Double,默认按照数值本身的大小来进行升序排序
-
对于字符串类型:默认按照首字符的编号升序排序
-
对于自定义类型对象,TreeSet默认是无法直接排序的
代码展示:
public class TreeSetTest {public static void main(String[] args) {TreeSet<Movie> treeSet = new TreeSet<>();treeSet.add(new Movie("《三傻大闹宝莱坞》", 1995, "罗三佑"));treeSet.add(new Movie("《大傻大闹宝莱坞》", 1996, "罗二佑"));treeSet.add(new Movie("《二傻大闹宝莱坞》", 1997, "罗大佑"));System.out.println(treeSet);}}
效果:报错: java.lang.ClassCastException
说明我们要自定义排序规则,这样TreeSet可以按照我们的排序规则来为我们排序
自定义排序规则:
-
TreeSet集合存储自定义类型的对象时,必须指定排序规则,支持如下两种方式来制定比较规则
-
方式一:让自定义的类实现Comparable接口,重写里面的compareTo方法来制定比较规则
-
方式二:通过调用TreeSet集合有参构造器,可以设置Compareto对象(比较器对象,用于指定比较规则)
方式一代码展示:
public class Movie implements Comparable<Movie>{private String name;private int date;private String actor;@Overridepublic int compareTo(Movie o) {//如果认为左边对象大于右边对象,返回正整数//如果认为左边对象小于右边对象,返回负整数//如果认为左边对象等于右边对象,返回0return this.getDate()-o.getDate();}}public static void main(String[] args) {TreeSet<Movie> treeSet = new TreeSet<>();treeSet.add(new Movie("《三傻大闹宝莱坞》", 1995, "罗三佑"));treeSet.add(new Movie("《大傻大闹宝莱坞》", 1996, "罗二佑"));treeSet.add(new Movie("《二傻大闹宝莱坞》", 1997, "罗大佑"));System.out.println(treeSet);}
-
方式二代码展示:
public static void main(String[] args) {// Set<Movie> treeSet = new TreeSet<>(new Comparator<Movie>() {// @Override// public int compare(Movie o1, Movie o2) {// //按照日期进行排序// return o2.getDate()-o1.getDate();// }//// });//lambda表达式简化Set<Movie> treeSet = new TreeSet<>((o1,o2)->o2.getDate()-o1.getDate());treeSet.add(new Movie("《三傻大闹宝莱坞》", 1995, "罗三佑"));treeSet.add(new Movie("《大傻大闹宝莱坞》", 1996, "罗二佑"));treeSet.add(new Movie("《二傻大闹宝莱坞》", 1997, "罗大佑"));System.out.println(treeSet);}}
两种方式中,关于返回值得规则:
-
如果认为第一个元素>第二个元素 返回正整数
-
如果认为第一个元素<第二个元素 返回负整数
-
如果认为第一个元素=第二个元素 返回0,此时TreeSet集合只会保留同一个元素,认为两者重复
注:如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序
collection系列集合小结
各集合的适用场景
-
如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据
-
arrayList (有序,可重复,有索引),底层基于数组(常用)
-
-
如果希望记住元素的添加顺序,且增删首尾数据的情况较多
-
LinkedList(有序,可重复,有索引),底层基于双链表实现的(栈,队列)
-
-
如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快
-
HashSet(无序,不重复,无索引),底层基于哈希表实现
-
-
如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快
-
LinkeeHashSet(有序,不重复,无索引),底层基于哈希表和双链表
-
-
如果对元素进行排序,也没有重复元素需要存储?且希望增删改查都快
-
TreeSet(排序,不重复,无索引),基于红黑树实现
-
注意事项:集合的并发修改异常问题
-
使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误
案例展示:
package com.lyc.test;import java.util.ArrayList;import java.util.List;//理解集合的并发修改异常问题,并解决public class ConcurrentTest {public static void main(String[] args) {List<String> objects = new ArrayList<>();objects.add("张三");objects.add("李四");objects.add("王五");objects.add("赵六");objects.add("李存勖");objects.add("李存礼");objects.add("李星云");System.out.println(objects);//需求:找出集合中全部带”李“的名字,并从集合中删除for (String s :objects){if (s.contains("李")){objects.remove(s);}}System.out.println(objects);}}
报错:并发修改异常
原因:在使用for循环遍历删除时,可能会出现漏删这种情况,这是因为数组中删除元素后,其后面的元素会自动上一位,而索引却向下走一位,这样就会导致漏删
代码展示:
public static void main(String[] args) {List<String> objects = new ArrayList<>();objects.add("张三");objects.add("李四");objects.add("王五");objects.add("赵六");objects.add("李存勖");objects.add("李存礼");objects.add("李星云");System.out.println(objects);for (int i = 0; i < objects.size(); i++) {if (objects.get(i).contains("李")){objects.remove(objects.get(i));}}System.out.println(objects);}
而在使用迭代器遍历中,开发人员意识到了这一问题,就直接提醒异常,
解决方案展示:
//如何解决 集合的并发修改异常问题?//方法1:在for循环中加上i--让其回到上一个索引for (int i = 0; i < objects.size(); i++) {if (objects.get(i).contains("李")){objects.remove(i);i--;}}System.out.println(objects);//方法2:倒着删除for (int i = objects.size()-1; i >=0 ; i--) {if (objects.get(i).contains("李")){objects.remove(i);}}System.out.println(objects);//方法3:使用迭代器删除Iterator<String> iterator = objects.iterator();while (iterator.hasNext()){if (iterator.next().contains("李")){iterator.remove();//使用迭代器来删除,每删除一个数据,相当于底层做了i--}}System.out.println(objects);}
注:增强for循环遍历集合并删除数据,是没有方法解决的,一定会报错 并发修改异常
Collection的其他相关知识
前置知识点:可变参数
-
本质上就是一种特殊形参,定义在方法、构造器的形参列表里,格式是:数据类型...参数名称;
-
可变参数的特点与好处
-
特点:可以不传数据给它;可以传一个或同时传多个数据给它;也可以传一个数组给它。
-
好处:常常灵活地接受数据
-
代码展示:
package com.lyc.test;import java.util.Arrays;//测试可变参数public class ParamTest {public static void main(String[] args) {//特点test(1,2,3,4,5,6,7,8,9,10);test();test(10);test(new int[]{1,2,3,4,5,6,7,8,9,10});}public static void test(int... args) {//可变参数在方法内部,本质就是一个数组for (int i : args) {System.out.println(i);}System.out.println(Arrays.toString(args));System.out.println("=======================");}}
对外是参数,对内是数组
注意事项:
-
一个形参列表中,只能有一个可变参数(因为可变参数会将所有的参数归为自己,如果有第二个可变参数,则会起冲突)
-
可变参数必须放在形参列表的后面(因为可变参数会将所有的参数归为自己,如果放在后面,则也会起冲突)
集合的工具类
collections
-
是一个用来操作集合的工具类
collections提供的常用静态方法
方法名称 | 说明 |
---|---|
public static <T> boolean addAll(collection<? super T> c, T... elements) | 给集合批量增加元素 |
public static void shuffle(List<?> list) | 打乱list集合中的元素顺序 |
public static <T> void sort(List<T> list) | 对list集合中的元素进行升序排序 |
public static <T> void sort(List<T> list, Comparator<? super T> c) | 对list集合中元素,按照比较器对象制定的规则进行排序 |
代码展示:
package com.lyc.test;import com.lyc.pojo.Movie;import java.sql.Array;import java.util.ArrayList;import java.util.Collections;import java.util.Comparator;import java.util.List;//掌握collection集合工具类的使用public class CollectionsTest {public static void main(String[] args) {List<String> list = new ArrayList<>();//1.public static void sort(List list):将集合中的元素按照默认升序排序Collections.sort(list);System.out.println(list);System.out.println("=======================================");//2. public static <T> boolean addAll(Collection<? super T> c, T... elements):为集合批量添加数据Collections.addAll(list,"张三","李四","王五");System.out.println(list);System.out.println("=======================================");//2.public static void sort(List list, Comparator c):将集合中的元素按照指定规则排序ArrayList<Movie> movies = new ArrayList<>();Collections.addAll(movies,new Movie("张三",1999,"张三"),new Movie("李四",1998,"李四"),new Movie("王五",1997,"王五"));movies.sort((o1, o2) -> o1.getDate() - o2.getDate());System.out.println(movies);System.out.println("=======================================");//3.public static void shuffle(List list):将list中的元素随机排序Collections.shuffle(list);System.out.println(list);System.out.println("=======================================");//4.public static void reverse(List list):反转集合中的元素顺序//5.public static void swap(List list, int i, int j):将集合中指定位置的元素进行交换//6.public static int binarySearch(List list, Object key):进行二分查找,返回索引,未找到返回-1//7.public static int max(Collection coll):获取Collection中元素的最大值,要求Collection中元素必须实现Comparable接口//8.public static int max(Collection coll, Comparator c):获取Collection中元素的最大值,要求Collection中元素必须实现Comparable接口//9.public static int min(Collection coll):获取Collection中元素的最小值,要求Collection中元素必须实现Comparable接口}}
综合案例:斗地主业务分析
分析业务需求
-
总共有54张牌
-
点数 ”3“,"4","5","6","7","8","9","J","Q","K","A","2"
-
花色:”♥“,”♠“,”♣“,”♦“
-
大小王:”🤡”,”🃏“
-
斗地主:发出51张牌,剩下三张作为底牌
分析实现:
-
在启动游戏房间的时候,应该准备好54张牌
-
接着,需要完成洗牌,发牌,对牌排序,看牌
代码实现:
首先先创建一副牌,一个房间,一个启动类
牌类里面应该有点数,花色,以及大小
Card.java
package com.lyc.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data@NoArgsConstructor@AllArgsConstructorpublic class Card {//点数private String number;//花色private String color;//大小private int value;@Overridepublic String toString() {return color+number;}}
再创建一个房间类,房间类中首先要拿到一副牌,在打乱牌的顺序,再发给三个玩家,最后给三个玩家手中的牌排序,看牌(其中打牌需要用到网络通信,这里还不熟练,就只运行到看牌这一阶段)
1.先拿到一副牌,并且将54张牌放进去
House.java
public class House {//一副新牌private ArrayList<Card> cards= new ArrayList<Card>();//初始化public House(){//将52张牌加入到cards中// 定义两个数组,用来存储牌的花色和牌面//定义一个value来存储牌的值int value=0;String[] numbers= {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};String[] colors= {"♥","♠","♣","♦"};//利用增强for循环,先循环点数,在点数内循环花色for (String n : numbers) {value++;for (String c : colors) {cards.add(new Card(n,c,value));}}//将大小王加入到cards中cards.add(new Card("🃏","",++value));cards.add(new Card("🤡","",++value));//查看牌System.out.println("新牌:"+cards);}
效果:
2.在room中写入一个启动方法,在启动方法中打乱牌的顺序
public void start(){//洗牌 可以使用Collections工具类中的shuffle方法,打乱list中的元素Collections.shuffle(cards);//查看牌System.out.println("洗牌后:"+cards);}
效果:
3.在启动方法中,给三名玩家发牌,并且留下最后三张牌,
ArrayList<Card> playerFirst = new ArrayList<>();ArrayList<Card> playerSecond = new ArrayList<>();ArrayList<Card> playerThird = new ArrayList<>();//如何将牌均匀的发给三个人,并且最后留三张底牌,可以for循环,然后使用%3来确定发牌的人,最后三张不发,可以循环cards.size()-3for (int i = 0; i < cards.size()-3; i++) {if (i%3==0){playerFirst.add(cards.get(i));}else if (i%3==1){playerSecond.add(cards.get(i));}else {playerThird.add(cards.get(i));}}//查看三人手中的牌 以及 底牌System.out.println("第一个玩家:"+playerFirst);System.out.println("第二个玩家:"+playerSecond);System.out.println("第三个玩家:"+playerThird);//将三张底牌放在一个集合中List<Card> LastThreeCards = cards.subList(cards.size() - 3, cards.size());System.out.println("底牌:"+LastThreeCards);
4.在启动方法中,抢到地主,并且将牌排序,展示
//排序 看牌sortCards(playerFirst);sortCards(playerSecond);sortCards(playerThird);System.out.println("看牌!!!");System.out.println("第一个玩家:"+playerFirst);System.out.println("第二个玩家:"+playerSecond);System.out.println("第三个玩家:"+playerThird);//抢地主System.out.println("抢地主!!!");Random random = new Random();int i = random.nextInt(3);switch (i){case 0:playerFirst.addAll(LastThreeCards);System.out.println("第一个玩家抢到了地主牌!!!");sortCards(playerFirst);System.out.println("抢到地主后:playerFirst:"+playerFirst);break;case 1:playerSecond.addAll(LastThreeCards);System.out.println("第二个玩家抢到了地主牌!!!");sortCards(playerSecond);System.out.println("抢到地主后:playerSecond:"+playerSecond);break;case 2:playerThird.addAll(LastThreeCards);System.out.println("第三个玩家抢到了地主牌!!!");sortCards(playerThird);System.out.println("抢到地主后:playerThird:"+playerThird);break;}}public void sortCards(ArrayList<Card> cards){cards.sort(new Comparator<Card>() {@Overridepublic int compare(Card o1, Card o2) {return o1.getValue()-o2.getValue();//升序//return o2.getValue()-o1.getValue();//降序}});}
效果:
5.打牌
敬请期待
启动类:
LandlordsDemo.java
package com.lyc.test;import com.lyc.pojo.House;public class LandlordsDemo {/** 分析业务需求- 总共有54张牌- 点数 ”3“,"4","5","6","7","8","9","J","Q","K","A","2"- 花色:”♥“,”♠“,”♣“,”♦“- 大小王:”🤡”,”🃏“- 斗地主:发出51张牌,剩下三张作为底牌*/public static void main(String[] args) {// 1.创建一个集合,用来存储54张牌// 要有一个房间House house = new House();house.start();//启动游戏}}
Map集合
概述
-
Map集合称为双列集合(键值对形式),格式:{key1=value1,key2=value2.key3=value3,...}, 一次需要存一对数据作为一个元素
-
Map集合的每一个元素“key=value”称为一个键值对/键值对对象/Entry对象,Map集合也被叫做”键值对集合“
-
Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值。
Map集合在什么业务场景下使用
比如在做一个简易版的购物车案例中,商品信息和购买数量就可以使用Map集合,又比如丈夫与妻子,这种需要存储一一对应的数据时,都可以使用Map集合
Map集合体系
Map集合体系的特点
注意:Map系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的
-
HashMap(由键决定特点):无序,不重复,无索引;(最常用)
-
LinkedHashMap(由键决定特点):由键决定的特点:有序(先添加的数据在前面,后添加的数据在后面,顺序不变),不重复,无索引
-
TreeMap(由键决定特点):按照大小默认升序排序,不重复,无索引
-
这样看是不是有点眼熟,没错,
HashSet
底层利用了HashMap
,TreeSet
底层用了TreeMap
,LinkedHashSet
底层用了LinkedHashMap
。所以,你的感觉是没错的
代码展示:
public static void main(String[] args) {Map<String,Integer> map = new HashMap<>();//经典代码 无序,不重复,无索引getValue(map);System.out.println(map);System.out.println("========================");LinkedHashMap<String,Integer> map1 = new LinkedHashMap<>();//有序,不重复,无索引getValue(map1);System.out.println(map1);System.out.println("========================");TreeMap<Integer, String> map2 = new TreeMap<>();//不重复,排序,无索引map2.put(11,"张三");map2.put(23,"李四");map2.put(13,"王五");map2.put(24,"赵六");System.out.println(map2);System.out.println("========================");}public static void getValue(Map map){map.put("张三",18);map.put("张三",19);//后面重复的数据会覆盖前面的数据 只关注键,值无所谓,键不重复,值可以重复map.put("李四",20);map.put("王五",21);map.put(null,null);}
Map的常用方法
-
Map是双列集合的最大的父类,他的功能是全部双列集合都可以继承过来使用的。
Map常用方法如下:
方法名 说明 public int size()
获取当前集合的长度 public V put(K key,V value)
添加元素,如果key不存在,则添加,如果key存在,则修改value值 public V get(Object key)
根据key获取value值,如果键不存在,则返回null public boolean containsKey(Object key)
判断集合中是否包含指定的key public boolean containsValue(Object value)
判断集合中是否包含指定的value public Set<K> keySet()
获取所有key的集合 public Collection<V> values()
获取所有value的集合 public Set<Map.Entry<K,V>> entrySet()
获取所有key-value的集合 public V remove(Object key)
删除指定key的元素,返回被删除元素的value值 public void putAll(Map<? extends K,? extends V> m)
将m集合中的所有key-value添加到当前集合中,原本集合不变 public void clear()
清空集合 public boolean isEmpty()
判断集合是否为空 代码展示:
import org.junit.Test;//测试Map下的常用方法import java.util.Collection;import java.util.HashMap;import java.util.Set;public class MapTest {@Testpublic void test1(){HashMap<String,Integer> map = new HashMap<>();map.put("张三",23);map.put("李四",24);map.put("李四",29);map.put("王五",25);map.put("赵六",26);map.put(null,null);//1.public int size():获取当前集合的长度System.out.println(map.size());//2.public V put(K key,V value):添加元素,如果key不存在,则添加,如果key存在,则修改value值map.put("赵六",27);System.out.println(map);//3.public V get(Object key):根据key获取value值,如果键不存在,则返回nullSystem.out.println(map.get("赵六"));//4.public boolean containsKey(Object key):判断集合中是否包含指定的keySystem.out.println(map.containsKey("赵六"));//5.public boolean containsValue(Object value):判断集合中是否包含指定的valueSystem.out.println(map.containsValue(27));//6.public Set<K> keySet():获取所有key的集合Set<String> keys = map.keySet();System.out.println(keys);//7.public Collection<V> values():获取所有value的集合Collection<Integer> values = map.values();System.out.println(values);//8.public Set<Map.Entry<K,V>> entrySet():获取所有key-value的集合System.out.println(map.entrySet());//9.public V remove(Object key):删除指定key的元素,返回被删除元素的value值System.out.println(map.remove("赵六"));System.out.println(map);//11.public void putAll(Map<? extends K,? extends V> m):将m集合中的所有key-value添加到当前集合中,原本集合不变HashMap<String,Integer> map1 = new HashMap<>();map1.putAll(map);System.out.println(map1);System.out.println(map);//10.public void clear():清空集合map.clear();System.out.println(map);//12.public boolean isEmpty():判断集合是否为空System.out.println(map.isEmpty());}}
效果:
Map集合的遍历方式
-
键找值:先获取Map集合全部的键,再通过遍历键来找值
-
键值对:把“键值对”看成一个整体进行遍历(难度较大)
-
Lambda表达式
第一种:键找值,先拿到键的集合,根据键获取值,正好可以用到上面的方法
public Set<K> keySet() | 获取所有key的集合 |
---|---|
public V get(Object key) | 根据key获取value值,如果键不存在,则返回null |
代码展示:
import org.junit.Test;import java.util.HashMap;import java.util.Set;public class MapLoopTest {@Testpublic void test() {HashMap<String,Double> map = new HashMap<>();map.put("张三", 100.0);map.put("李四", 200.0);map.put("李四", 123.9);map.put("王五", 456.6);System.out.println(map);Set<String> keys = map.keySet();for (String key : keys) {System.out.println(key + ":" + map.get(key));}}}
第二种:键值对遍历
设计思想:需要将map集合中的键和值看成一个整体对象来遍历,使用增强for循环来遍历的话,无法确定元素类型为什么,但是Java为我们提供了一个方法entrySet():将map集合中的键和值看成一个entry对象,这样我们就可以将其当作一个对象来遍历了
public Set<Map.Entry<K,V>> entrySet() 获取所有key-value的集合
代码展示:
public void test() {HashMap<String,Double> map = new HashMap<>();map.put("张三", 100.0);map.put("李四", 200.0);map.put("李四", 123.9);map.put("王五", 456.6);System.out.println(map);Set<Map.Entry<String, Double>> entries = map.entrySet();for (Map.Entry<String, Double> entry : entries) {System.out.println(entry.getKey() + ":" + entry.getValue());}}
第三种:Lambda表达式
使用foreEach
方法
public void forEach(BiConsumer<? super K, ? super V> action) //结合lambda表遍历Map集合
// lambda表达式 实际还是使用了第二种方法,使用EntrySetmap.forEach(new BiConsumer<String, Double>() {@Overridepublic void accept(String k, Double v) {System.out.println(k + ":" + v);}});map.forEach((k, v) -> System.out.println(k + ":" + v));}
Map集合案例--统计投票人数
需求
-
某个班级80名学生,现在需要组织秋游活动,班长提供了四个景点依次是(A,B,C,D),每个学生只能选择一个经典,请统计出最终那个景点想去的人多?
分析
-
将80个学生选择的数据拿到程序中,【A,A,B,A,C,D... 】会有重复的,所以我们可以使用ArrayList集合
-
准备一个Map集合用于存储统计的结果,Map<String ,Integer>,键是景点,只代表投票人数
-
遍历80个学生选择的景点,每遍历一个景点,就看Map集合中是否存在该景点,不存在则存入“景点=1”,存在则是对应值加一
代码展示:
package com.lyc.test;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Random;public class MapDemo {public static void main(String[] args) {//将80个学生堆积选择的景点数据放入arrayList中String[] sites = {"A","B","C","D"};Random random = new Random();List<String> list = new ArrayList<>();for (int i = 0; i < 80; i++) {//随机选择景点int index = random.nextInt(sites.length);//将学生选择的景点放入list中list.add(sites[index]);}System.out.println(list);//添加一个Map集合用来存放景点和学生人数HashMap<String,Integer> map = new HashMap<>();for (String s : list) {if (map.containsKey(s)){map.put(s,map.get(s)+1);}else{map.put(s,1);}}//遍历集合map.entrySet().forEach(System.out::println);}}
HashMap的底层原理
-
HashMap的底层是与HashSet的底层原理一致,都是基于哈希表实现的,因为在HashSet的底层源码中,是基于HashMap实现的
public HashSet() {map = new HashMap<>();}
实际上:原来学的Set系列集合的底层都是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已
哈希表
JDK8以前:哈希表 = 数组+链表
JDK8之后:哈希表=数组+链表+红黑树
JDK8以前HashMap集合的底层原理,基于哈希表:数组+链表
-
创建一个默认长度16的数组,默认加载因子为0.75,数组名 table
-
使用元素的哈希值对数组长度求余计算出应存入的位置 (无序的原因)
-
判断当前位置是否为null,如果是null直接存入
-
如果不为null,表示有元素,则调用equals方法比较 相等,则不存(不重复的原因),不相等,则存入数组(JDK8之前,新元素存入数组,占老元素位置,老元素挂下面 JDK8开始之后,新元素直接挂老元素下面)
-
无序就不支持索引
哈希表是一种增删改查数据性能都较好的结构。 原因:可以直接跟据哈希值来获取元素位置,进行增删改查
无序就不支持索引
哈希表是一种增删改查数据性能都较好的结构。
原因:可以直接跟据哈希值来获取元素位置,进行增删改查
HashMap则继承了哈希表这种特性
问题思考:
-
如果数组快占满了,会出现什么问题?应该怎么做?
数组快占满会让链表过长,会导致查询性能降低,
解决方法:哈希表扩容:将底层的数组长度扩大,再将原来的数据迁移进去,让其分散开来,让链表的长度短些,这样性能就得到了优化,
注:加载因子: 16 x 0.75 = 12,哈希表会在数组元素超过12的时候,哈希表就会扩容成原来的数组的两部,再将原来的数组元素转移
-
就算是扩容了,那链表的长度还是有可能过长,该怎么解决呢?
解:JDK8开始,当链表长度超过8时,且数组长度>=64时,自动将链表转成红黑树
小结:JDK8开始后,哈希表中引入了红黑树后,进一步提高了操作数据的性能。且红黑树的左右两边,由哈希值来确定,哈希值小的在左边,哈希值大的在右边
总结:
-
HashMap集合是一种增删改查数据,性能都较好的集合
-
但它无序,不重复,没有索引支持的(由键决定特点)
-
HashMap的键依赖HashCode方法和equals方法保证键的唯一
-
如果见存储的是自定义类型的对象,可以通过重写HashCode方法和equals方法,这样可以保证多个对象内容一致时,HashMap集合就能认为是重复的
代码展示:
package com.lyc.test;import com.lyc.pojo.Student;import java.util.HashMap;//测试HashMap方法public class HashMapTest {public static void main(String[] args) {HashMap<Student, String> map = new HashMap<>();map.put(new Student("张三", 18), "北京");map.put(new Student("李四", 19), "上海");map.put(new Student("李四", 19), "广西");map.put(new Student("王五", 20), "广州");map.put(new Student("赵六", 21), null);System.out.println(map);}}
需要在学生类里重写hasCode方法和equals方法
LinkedHashMap的底层原理
LinkedHashSet底层是基于LinkedHashMap的
public class LinkedHashSet<E>extends HashSet<E>implements Set<E>, Cloneable, java.io.Serializable {public HashSet() {map = new HashMap<>();}
-
依然是基于哈希表(数组+链表+红黑树)实现的
-
但是,它的每一个元素都额外的多了一个双链表的机制记录他前后元素的位置
LinedHashMap将键值对当成一个整体对象,Entry对象,将Entry对象中的键元素存入
过程解析:
当存入第一个键元素时,依旧是使用哈希值去求余数组长度,但找到位置之后,因为它的每一个键元素都多了一个双链表记录机制,所以现在他的首指针变量和尾指针变量都是指向它自己的,
当第二个键元素存储进来后,第二个元素会将自己的地址送给第一个元素,这样我们第一个键元素就指向了第二个键元素,同时第一个键元素的地址也被拿到第二个键元素中,接着第二个键元素就变成了最后一个键元素,同时也拿到了尾指针变量,
当第三个键元素存储进来后,会通过尾指针变量来找到第二个键元素,将自己的地址与第二个键元素的地址进行交换,这样,第三个键元素成为了最后一个键元素,拿到了尾指针变量,以此类推...
这样,再查询数据的时候,就会按照双链表的顺序查找,所以LinkedHashMap是有序的
因为还是基于哈希表实现的,所以LinkedHashSet的增删改查性能也是不错的
缺点:因为LinkedHashMap的每一个键元素除了要记住自己的地址,还需要去记住上一个键元素与下一个键元素的地址,里面记录的信息特别多,所以占用的内存较大,它是通过占用内存来实现自己有序这一特点的
TreeMap的底层原理
public TreeSet() {this(new TreeMap<>());}
TreeSet的底层原理其实就是TreeMap的底层原理
-
特点:排序(默认升序排序,按照元素的大小,由大到小排序),不重复,无索引
-
底层是基于红黑树实现的排序
注意:
-
对于数值类型:Integer,Double,默认按照数值本身的大小来进行升序排序
-
对于字符串类型:默认按照首字符的编号升序排序
-
对于自定义类型对象,TreeSet默认是无法直接排序的
自定义排序规则:
-
方式一:让自定义的类实现Comparable接口,重写里面的compareTo方法来制定比较规则
-
方式二:通过调用TreeSet集合有参构造器,可以设置Compareto对象(比较器对象,用于指定比较规则)
方式一代码展示:
public class Movie implements Comparable<Movie>{private String name;private int date;private String actor;@Overridepublic int compareTo(Movie o) {//如果认为左边对象大于右边对象,返回正整数//如果认为左边对象小于右边对象,返回负整数//如果认为左边对象等于右边对象,返回0return this.getDate()-o.getDate();}}public static void main(String[] args) {TreeSet<Movie> treeSet = new TreeSet<>();treeSet.add(new Movie("《三傻大闹宝莱坞》", 1995, "罗三佑"));treeSet.add(new Movie("《大傻大闹宝莱坞》", 1996, "罗二佑"));treeSet.add(new Movie("《二傻大闹宝莱坞》", 1997, "罗大佑"));System.out.println(treeSet);}
方式二代码展示:
public static void main(String[] args) {// Map<Movie,String> treeMap = new TreeMap<>(new Comparator<Movie>() {// @Override// public int compare(Movie o1, Movie o2) {// //按照日期进行排序// return o2.getDate()-o1.getDate();// }//// });//lambda表达式简化Set<Movie,String> treeMap = new TreeMap<>((o1,o2)->o2.getDate()-o1.getDate());treeMap.add(new Movie("《三傻大闹宝莱坞》", 1995, "罗三佑"),"四川");treeMap.add(new Movie("《大傻大闹宝莱坞》", 1996, "罗二佑"),"上海");treeMap.add(new Movie("《二傻大闹宝莱坞》", 1997, "罗大佑"),"北京");System.out.println(treeMap);}}
两种方式中,关于返回值得规则:
-
如果认为第一个元素>第二个元素 返回正整数
-
如果认为第一个元素<第二个元素 返回负整数
-
如果认为第一个元素=第二个元素 返回0,此时TreeMap集合只会保留同一个元素,认为两者重复
注:如果类本身有实现Comparable接口,TreeMap集合同时也自带比较器,默认使用集合自带的比较器排序
补充知识:集合的嵌套
集合的嵌套:指的是集合中的元素又是一个集合
案例理解:Map集合案例-省和市
需求
-
要求在程序中记住如下省份和其对应的城市信息,记录成功后,要求可以查询出湖北省的城市信息。
分析
-
定义一个Map集合,键表示省份名称,值表示城市名称,注:城市会有多个,可以用array List表示值
-
根据”湖北省“这个键获取对应的值即可
代码展示:
package com.lyc.test;import java.util.ArrayList;import java.util.Collection;import java.util.Collections;import java.util.HashMap;//理解集合的嵌套public class collectionDemo {public static void main(String[] args) {//定义一个Map集合来存储全部的省份信息,和其对应的城市信息HashMap<String, ArrayList<String>> map = new HashMap<>();//定义一个集合来存储每个省份的城市信息ArrayList<String> value1 = new ArrayList<>();ArrayList<String> value2 = new ArrayList<>();ArrayList<String> value3 = new ArrayList<>();Collections.addAll(value1,"武汉市","黄石市","十堰市","宜昌市","襄阳市");Collections.addAll(value2,"石家庄市","唐山市","邢台市","保定市","张家口市");Collections.addAll(value3,"南京市","无锡市","徐州市","常州市","苏州市");map.put("湖北省", value1);map.put("河北省", value2);map.put("江苏省", value3);//查询江苏省的所有城市ArrayList<String> list = map.get("江苏省");for (String s : list) {System.out.println(s);}for (String key : map.keySet()) {System.out.println(key+":"+map.get(key));}map.forEach((k,v)-> System.out.println(k+":"+v));}}
以上就是集合框架中的内容,希望对大家有所帮助!