09_常用类_集合
概述
与数组类似,集合也是数据容器,两者相较而言:
-
数组定义后类型确定,长度固定
集合类型可以不固定,大小是可变的 -
数组可以存储基本类型和引用类型的数据。
集合只能存储引用数据类型的数据。
-
数组适合做数据个数和类型确定的场景。
集合适合做数据个数不确定,且要做增删元素的场景,集合种类更多,功能更强大。
集合包括两大类:
- Collection单列集合,每个元素(数据)只包含一个值。
- Map双列集合,每个元素包含两个值(键值对)。
集合都是泛型的形式,可以在编译阶段约束集合只能操作某种数据类型。
泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象。
数据结构
数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的。
通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。
常见的数据结构:
-
栈:后进先出,先进后出。一端开口,一端封闭。
压/进栈;弹/出栈;栈顶元素;栈底元素 -
队列:后进先出,先进后出。双开口。
入队;出队 -
数组:元素在内存中是连续存储的。
查询快,增删慢 -
链表:元素在内存中不连续存储,每个元素节点包含数据值和下一个元素的地址。
查询慢,增删快。
可分为单链表、双链表(存储前后两个节点的地址) -
二叉树:
只能有一个根节点,每个节点最多支持2个直接子节点。
节点的度:节点拥有的子树的个数,二叉树的度不大于2 叶子节点度为0的节点,也称之为终端结点。
高度:叶子结点的高度为1,叶子结点的父节点高度为2,以此类推,根节点的高度最高。
层:根节点在第一层,以此类推
兄弟节点:拥有共同父节点的节点互称为兄弟节点
-
二叉查找树:为了提高二叉树的检索效率而设,特点:
- 每一个节点上最多有两个子节点
- 左子树上所有节点的值都小于根节点的值
- 右子树上所有节点的值都大于根节点的值
-
平衡二叉树:平衡二叉树是在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能。因为查找二叉树可能出现单边度过高的情况。要求:
- 任意节点的左右两个子树的高度差不超过1
- 任意节点的左右两个子树都是一颗平衡二叉树
-
红黑树:二叉树自平衡的一种算法,每一个节点可以是红或者黑;不通过高度平衡,而是通过“红黑规则”:
- 每一个节点或是红色的,或者是黑色的,根节点必须是黑色。
- 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)。
- 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
添加节点时,默认用红色,效率高。
增删改查性能都很好。
Collection
体系
特点:
- List系列:添加的元素是有序、可重复、有索引
- Set系列集合:添加的元素是无序、不重复、无索引
- HashSet: 无序、不重复、无索引;
LinkedHashSet: 有序、不重复、无索引。 - TreeSet:默认升序排序、不重复、无索引。
- HashSet: 无序、不重复、无索引;
常用API
方法名称 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数。 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
遍历
-
迭代器
获取迭代器的方法Iterator
iterator(),迭代器常用方法: 方法名称 说明 boolean hasNext() 询问当前位置是否有元素存在,存在返回true ,不存在返回false E next() 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界。 -
foreach
增强for循环(也可以遍历数组),格式:
for(元素数据类型 变量名 : 数组或者Collection集合) { //在此处使用变量即可,该变量就是元素 }
-
Lambda表达式
JDK8开始支持,Collection结合Lambda遍历的API:
方法名称 说明 default void forEach(Consumer<? super T> action): 结合lambda遍历集合 例如:
Collection<String> lists = new ArrayList<>(); ... lists.forEach(s -> { System.out.println(s); }); //lists.forEach(s -> System.out.println(s));
并发修改异常:遍历集合的时候,删除元素,包括:
-
迭代器遍历集合且直接用集合删除元素的时候可能出现。
-
增强for循环遍历集合且直接用集合删除元素的时候可能出现。
解决:
- 迭代器遍历集合但是用迭代器自己的删除方法操作可以解决。
- 使用普通for循环遍历并删除元素不会存在这个问题。
List
ArrayList、LinekdList :有序,可重复,有索引。
- 有序:存储和取出的元素顺序一致
- 有索引:可以通过索引操作元素
- 可重复:存储的元素可以重复
继承了Collection API;List集合因为支持索引,所以还有独特的索引操作API:
方法名称 | 说明 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
遍历方式除了Collection支持的3中外,还可以使用普通for循环(因为List有索引)。
底层原理:
-
ArrayList:底层是基于数组实现的,第一次创建集合并添加第一个元素的时候,创建一个默认长度为10的数组。
-
LinkedList: 底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API:
方法名称 说明 public void addFirst(E e) 在该列表开头插入指定的元素 public void addLast(E e) 将指定的元素追加到此列表的末尾 public E getFirst() 返回此列表中的第一个元素 public E getLast() 返回此列表中的最后一个元素 public E removeFirst() 从此列表中删除并返回第一个元素 public E removeLast() 从此列表中删除并返回最后一个元素
Set
特点:
-
无序:存取顺序不一致
-
不重复:可以去除重复
-
无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
实现类:
-
HashSet : 无序、不重复、无索引。
- LinkedHashSet:有序、不重复、无索引。
-
TreeSet:排序、不重复、无索引。
Set集合的API基本与Collection的一致。
HashSet
底层采取哈希表存储的数据,对于增删改查数据性能都较好:
- JDK8之前的,底层使用数组+链表组成
- JDK8开始后,底层采用数组+链表+红黑树组成。
当挂在元素下面的数据过多时,查询性能降低。当链表长度超过8的时候,自动转换为红黑树。
哈希值
是JDK根据对象的地址,按照某种规则算出来的int类型的数值。
可通过Object类的API获取:public int hashCode()。
特点:
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的
去重复原理
- 创建一个默认长度16的数组,数组名table
- 根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
- 判断当前位置是否为null,如果是null直接存入
- 如果位置不为null,表示有元素,则调用equals方法比较
- 如果一样,则不存,如果不一样,则存入数组
结论:如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法
LinkedHashSet
特点:有序、不重复、无索引。
这里的有序指的是保证存储和取出的元素顺序一致,实现原理:
底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
TreeSet
特点:不重复、无索引、可排序
可排序
:按照元素的大小默认升序(有小到大)排序。
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。
排序规则:
-
对于数值类型:Integer , Double,官方默认按照大小进行升序排序。
-
对于字符串类型:默认按照首字符的编号升序排序。
-
对于自定义类型如Student对象,TreeSet无法直接排序,需自定排序规则,有两种方法:
- 让自定义的类(如学生类)实现Comparable接口,重写里面的compareTo方法
- TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象
注意:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。
Collections
并不属于集合,是用来操作集合的工具类。
常用API:
方法名称 | 说明 |
---|---|
public static |
给集合对象批量添加元素 |
public static void shuffle(List<?> list) | 打乱List集合元素的顺序 |
public static |
将集合中元素按照默认规则排序 |
public static |
将集合中元素按照指定规则排序 |
Map
Map集合是一种双列集合,每个元素包含两个数据。
每个元素的格式:key=value(键值对元素),因此Map集合也被称为“键值对集合”。
体系
使用最多的Map集合是HashMap。
重点掌握HashMap , LinkedHashMap , TreeMap。其他的后续理解。
实现类特点:
- HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致)
- LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
- TreeMap:元素按照建是排序,不重复,无索引的,值不做要求。
常用API:
方法名称 | 说明 |
---|---|
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
遍历
-
遍历键
先获取Map集合的全部键的Set集合,遍历该Set,然后通过键提取对应值。涉及API:方法名称 说明 Set keySet() 获取所有键的集合 V get(Object key) 根据键获取值 -
遍历键值对:
先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型。然后遍历Set集合,然后提取键及对应值。涉及API:
方法名称 说明 Set<Map.Entry<K,V>> entrySet() 获取所有键值对对象的集合 K getKey() 获得键 V getValue() 获取值 -
Lambda表达式
得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。
方法名称 说明 default void forEach(BiConsumer<? super K, ? super V> action) 结合lambda遍历Map集合 maps.forEach((k , v) -> { System.out.println(k +"----->" + v); });
HashMap
HashMap是Map里面的一个实现类。特点都是由键决定的:无序、不重复、无索引
没有额外需要学习的特有API。
Set系列集合的底层就是Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
LinkedHashMap
特点由键决定:有序、不重复、无索引。
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
TreeMap
由键决定特性:不重复、无索引、可排序
可排序:按照键数据的大小默认升序排序。
注意:TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序。
自定义排序规则的2中方法:
-
类实现Comparable接口,重写比较规则。
-
集合自定义Comparator比较器对象,重写比较规则。
TreeMap跟TreeSet一样底层原理是一样的。
不可变集合
不可变集合:不可被修改的集合。
集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。
在List、Set、Map接口中,都存在of方法(JDK9开始支持),可以创建一个不可变的集合:
方法名称 | 说明 |
---|---|
static |
创建一个具有指定元素的List集合对象 |
static |
创建一个具有指定元素的Set集合对象 |
static <K , V> Map<K,V> of(E…elements) | 创建一个具有指定元素的Map集合对象 |
Stream
在Java 8中,得益于Lambda所带来的函数式编程, 引入了一个全新的Stream流概念,用于简化集合和数组操作的API。
获取流
-
可以使用Collection接口中的默认方法stream()生成流:
名称 说明 default Stream stream() 获取当前集合对象的Stream流 -
数组获取Stream流的方式:
名称 说明 public static Stream stream(T[] array) 获取当前数组的Stream流 public static Stream of(T... values) 获取当前数组/可变数据的Stream流
中间操作方法
名称 | 说明 |
---|---|
Stream |
用于对流中的数据进行过滤。 |
Stream |
获取前几个元素 |
Stream |
跳过前几个元素 |
Stream |
去除流中重复的元素。依赖(hashCode和equals方法) |
static |
合并a和b两个流为一个流 |
注意:
- 中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。
- 在Stream流中无法直接修改集合、数组中的数据
终结操作
名称 | 说明 |
---|---|
void forEach(Consumer action) | 对此流的每个元素执行遍历操作 |
long count() | 返回此流中的元素数 |
注意:终结操作方法,调用完成后流就无法继续使用了,原因是不会返回Stream了。
收集操作
把Stream流操作后的结果数据转回到集合或者数组中去。
收集方法:
名称 | 说明 |
---|---|
R collect(Collector collector) | 开始收集Stream流,指定收集器 |
Collector类由Collectors工具类提供具体实现:
名称 | 说明 |
---|---|
public static |
把元素收集到List集合中 |
public static |
把元素收集到Set集合中 |
public static Collector toMap(Function keyMapper , Function valueMapper) | 把元素收集到Map集合中 |
ArrayList
概述
集合是与数组类似,也是一种容器,用于装数据的。
数组定义完成并启动后,类型确定、长度固定,在个数不能确定,且要进行增删数据操作的时候,数组是不太合适的。
集合的特点:
- 集合的大小不固定,启动后可以动态变化,类型也可以选择不固定
- 集合非常适合做元素个数不确定,且要进行增删操作的业务场景
- 集合还提供了许多丰富、好用的功能,而数组的功能很单一
创建
构造器 | 说明 |
---|---|
public ArrayList() | 创建一个空的集合对象 |
支持泛型:
ArrayList
泛型只能支持引用数据类型,不支持基本数据类型
常用方法
方法名 | 说明 |
---|---|
public boolean add(E e) | 将指定的元素追加到此集合的末尾 |
public void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
public E get(int index) | 返回指定索引处的元素 |
public int size() | 返回集合中的元素的个数 |
public E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
public boolean remove(Object o) | 删除指定的元素,返回删除是否成功 |
public E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
Arrays
数组操作工具类,专门用于操作数组元素的。
常用API:
方法名 | 说明 |
---|---|
public static String toString(类型[] a) | 返回数组的内容(字符串形式) |
public static void sort(类型[] a) | 对数组进行默认升序排序 |
public static |
使用比较器对象自定义排序 |
public static int binarySearch(int[] a, int key) | 二分搜索数组中的数据,存在返回索引,不存在返回-1 |