Java基础学习(三)-集合学习
在这里推荐一下韩顺平老师的 Java 系列教程,讲得真的非常细腻和详细。
一、集合体系图
二、Collection
接口及其常用方法
2.1、Collection 接口声明
- Collection 实现子类可以存放多个元素,每个元素可以是 Object
- 有些 Collection 的实现类可以存放重复的元素,有些不可以
- 有些 Collection 的实现类是有序的,有些不是有序的
- Collection 接口没有直接的实现子类,但其子接口
Set
和List
有一系列实现类
2.2、Collection 常用方法
1、方法一览
2、add(E)
用于添加单个元素
3、remove(Object)
用于删除指定元素
4、contains(Object)
判断元素是否存在
5、size()
获取集合中元素个数
6、isEmpty()
判断集合是否为空
7、clear()
清空集合对象
8、addAll(Collection<? extends E>)
添加多个元素
9、containsAll(Collection<?>)
判断多个元素是否都存在
10、removeAll(Collection<?>)
移除多个元素
11、retainAll(Collection<?>)
用于保留两个集合对象中都有的元素。
2.3、迭代器遍历
1、Iterator
接口及 iterator
方法
Collection 集合中有一个
iterator
方法,这个方法用于返回一个Iterator
对象
Iterator
是Collection
接口的父接口,用于遍历 Collection 集合中的元素。- 所有实现了 Collection 接口的集合类都有一个
iterator
方法,用于返回一个迭代器 Iterator
仅用于遍历集合,本身不存放对象。
迭代器的执行原理
- 使用
Iterator iterator = coll.iterator()
获取集合对象coll
的迭代器。 - 使用
hasNext()
判断是否还有下一个元素 next()
方法的作用有:- 指针下移
- 将下移后指针位置上的元素返回
迭代器一开始的指针指向第一个元素前一格。
2、Collection
接口遍历元素方式一 - 使用 Iterator
- 创建一个
ArrayList
对象,然后使用迭代器进行遍历
1 | List<String> list = new ArrayList<>(); |
结果
3、注意
在调用
iterator.next()
方法前必须使用iterator.hasNext()
方法进行检测。若不调用,且下一条记录无效,此时直接调用
iterator.next()
会直接抛出NoSuchElementException
异常。如果希望再次遍历,那么需要重置迭代器,使用以下的代码重置迭代器
1 | iterator = list.iterator(); |
4、Collection
接口遍历元素方式二 - 使用增强 for 循环
1 | List<String> list = new ArrayList<>(); |
增强 for 循环底层仍然使用了迭代器
5、Collection
接口遍历元素方式三 - 使用 stream 流
- 方法一
1 | list.forEach(System.out::println); |
- 方法二
1 | list.stream().forEach(System.out::println); |
三、List
3.1、概述
1、基本介绍
List 接口是
Collection
接口的子接口
- List 集合类中元素 有序 ,即添加顺序与取出顺序一致,且可以重复
- List 集合中每个元素都有其对应的顺序索引,即支持索引
- List 容器中的元素都对应一个整数型序号记载其在容器中的为止,可以根据序号存取容器中的元素
ArrayList
允许加入多个 null- JDK API 中 List 接口的常用实现类有
ArrayList
LinkedList
Vector
2、方法一览
3.2、方法介绍
1、void add(int index, E element)
在 index 位置插入元素 element
2、boolean addAll(int index, Collection<? extends E> coll)
从 index 位置开始,将
coll
集合中的元素加入到该集合中
3、Object get(int index)
获取指定 index 位置的元素
4、int indexOf(E ele)
返回
ele
在集合中首次出现的位置
5、int lastIndexOf(E ele)
返回
ele
在集合中最后一次出现的位置
6、E remove(int index)
移除并返回指定位置的元素
7、E set(int index, E ele)
将 index 位置的元素设置为
ele
,相当于替换
8、List subList(int fromIndex, int toIndex)
返回从
fromIndex
到toIndex
间的子集合
3.3、ArrayList
源码分析
1、底层存储结构
ArrayList
底层维护了一个 Object 类型的数组elementData
,ArrayList
底层存储结构为 动态数组
2、构造函数
ArrayList
中存在三个构造函数,分别为无参构造函数、给定初始容量的构造函数和给定集合的构造函数。
- 无参构造函数
使用无参构造函数构造
ArrayList
对象时,会构造一个空数组
1 | /** |
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
1 | /** |
- 传入初始容量的构造函数
- 对用户传入的初始容量
initialCapacity
进行判断,若传入参数大于0,则直接开辟一个长度为initialCapacity
的 Object 数组 - 如果用户传入的初始容量为 0 ,那么直接初始化数组为一个空数组 {}
- 如果用户传入的初始容量小于0,则抛出一个参数不合法异常
1 | /** |
- 传入集合对象的构造函数
- 将传入的集合转换为一个数组,然后赋给当前
ArrayList
的底层数组 - 对数组长度进行判断,如果长度不为0,那么使用
Arrays
工具类拷贝数组 - 如果数组长度为0,那么直接初始化数组为空数组 {}
1 | public ArrayList(Collection<? extends E> c) { |
EMPTY_ELEMENTDATA
1 | /** |
3、add 方法
如果用户使用无参构造器构造
ArrayList
对象,那么会初始化底层数组elementData
为一个空数组,在第一次往ArrayList
对象中添加数组时,会扩容elementData
长度为10,以后的扩容中,会扩容elementData
长度为 1.5 倍
1 | public void ensureCapacity(int minCapacity) { |
ArrayList
使用位运算获取扩容大小
- 在添加元素前,先调用
ensureCapacityInternal
方法确认添加后的容量够不够
1 | public boolean add(E e) { |
ensureCapacityInternal
方法内部调用了ensureCapacityInternal
方法
1 | private void ensureCapacityInternal(int minCapacity) { |
- 在
ensureExplicitCapacity
方法中,判断添加后的 size 是否大于当前ArrayList
底层数组elementData
长度,如果大于,那么调用grow
方法进行扩容。
1 | private void ensureExplicitCapacity(int minCapacity) { |
在确保数组容量足够后,在将待添加元素放在数组最后一个位置,同时令当前数组元素个数
size
+1,然后返回 truerangeCheck
该方法用于判断数组下标是否越界
1 | private void rangeCheck(int index) { |
3.4、Vector
1、介绍
Vector 也是 List 接口的一个实现子类,它继承自
AbstractList
,实现了 List 接口
Vector 底层也是一个对象数组,由于 Vector 类的操作方法都带有
synchronized
,所以它是线程安全的,但开发中多线程情况下不推荐使用 Vector,使用CopyOnWriteArrayList
- 底层数组
- 操作方法(局部)
1 | public synchronized void addElement(E obj) { |
2、构造函数
- 无参构造函数
如果调用无参构造函数,那么默认构造大小为 10 的数组,同时扩容按两倍扩容
3.5、LinkedList
1、介绍
LinkedList
底层实现了双向链表和双端队列特点- 可以添加任意元素,且元素可以重复
- 线程不安全,没有实现同步
2、底层结构
LinkedList
底层维护了一个双向链表LinkedList
中维护了两个属性 first 和 last 分别指向首节点和尾节点
1 | /** |
- 每个节点中又维护了
prev
、next
、item
三个属性,其中通过prev
指向前一个节点,next
指向后一个节点。
1 | private static class Node<E> { |
- 所以
LinkedList
中元素的添加和删除不是通过数组完成的,相对来说效率较高
3、addFirst(E e)
将传入元素 e 置为当前双向链表的头元素
1 | public void addFirst(E e) { |
查看
linkFirst
方法
1 | private void linkFirst(E e) { |
- 将当前链表的首元素置为变量 f
- 然后以传入的元素 e 构造一个 Node 节点对象,其中新节点的前指针为 null,后指针为原来链表的首元素 f
- 如果 f 为空,证明原来的链表为空,此时直接将最后一个元素 last 置为新节点即可
- 否则就令 f 的前驱指针指向新节点,然后令元素个数和修改次数都+1,并退出方法
3.6、List 集合选择
1、ArrayList
和 LinkedList
比较
底层结构 | 增删效率 | 改查效率 | |
---|---|---|---|
ArrayList | |||
LinkedList |
如何选择
ArrayList
和LinkedList
:
- 如果改查操作多,那么选择
ArrayList
- 如果增删操作多,那么选择
LinkedList
- 一般来说,在程序中查询较多,因此大部分情况下使用
ArrayList
四、Set
4.1、概述
- 无序(添加和取出的顺序不一致),没有索引
- 不允许重复元素,最多包含一个 null
- JDK API 中 Set 接口重要的实现类有:
HashSet
TreeSet
和 List 接口一样,Set 接口也是 Collection 接口的子接口,所以常用方法和 Collection 接口一样。Set 接口同样可以使用迭代器或者增强 for 循环遍历,但不能使用索引方式获取
4.2、HashSet
HashSet
实现了 Set 接口,底层是一个HashMap
,可以存放 null,但只能存放一个 null,不能有重复的元素/对象,在多线程高并发条件下应该使用CopyOnWriteArraySet
4.3、TreeSet
1、介绍
TreeSet
是一个有序的集合,它的作用是提供有序的Set集合。它继承了AbstractSet
抽象类,实现了NavigableSet<E>
,Cloneable
,Serializable
接口。TreeSet
是基于TreeMap
实现的,TreeSet
的元素支持2种排序方式:自然排序或者根据提供的Comparator
进行排序。如果使用
TreeSet
存储自定义的类,那么可以通过两个方法来构造排序规则
- 自然排序
- 令我们自定义的类实现
Comparable
接口,然后覆写compareTo
方法,这个方法接收一个 Object 对象,我们需要在该方法中先将传入的 Object 对象强转为我们自定义类的对象,然后自定义比较大小规则
1 |
|
- 这个时候我们可以在
TreeSet
对象中添加此类对象
1 | TreeSet<Student> students = new TreeSet<>(); |
- 查看结果,可以看到根据我们的自定义规则,即按成绩大小排序了
1 | System.out.println("TreeSet中的情况为:"); |
- 在构造
TreeSet
对象时传入一个Comparator
比较器对象,自定义比较大小规则,可以使用Lambda
表达式,这里仍然使用成绩比较学生优先级
- 使用和前面相反的排序规则
1 | TreeSet<Student> students = new TreeSet<>((s1,s2) -> s2.getScore() - s1.getScore()); |
- 添加学生对象
1 | Student student1 = new Student(99,"芜湖1"); |
去除 Student 实体类关于
Comparable
接口的相关信息遍历,查看结果
2、注意
当我们使用自定义类作为
TreeSet
的泛型时,我们必须保证我们自定义的类实现了Comparable
方法,或者在TreeSet
构造函数中传入一个Comparator
实现类,否则会报ClassCastException
异常
五、Map
5.1、概述
- Collection 和 Map 并列存在,用于保存具有映射关系的数据,即Key-Value键值对
- Map 中的 Key 和 Value 可以是任意引用类型的数据。
- Map 中的 Key 不可以重复,而 Value 可以重复
- 常用 String 对象作为 Map 中的 Key
- Key 和 Value 之间存在一个单向一对一关系,通过指定 Key 总能找到对应 Value
5.2、Map 常用方法
1、put
添加键值对
2、remove
根据键删除键值对映射关系
3、get
根据键获取值
4、size
获取键值对个数
5、isEmpty
判断元素个数是否为0
6、clear
清空 Map 对象
7、containsKey
判断键是否存在
8、entrySet
获取所有关系:K-V
9、values
获取所有的值
10、keySet
获取所有键
5.3、Map 接口的遍历方法
1、准备数据
1 | Map<String, String> map = new HashMap<>(); |
2、先取出所有 Key ,然后通过 Key 取出对应 Value
1 | //1 先取出所有key,然后通过key获取value |
结果
3、获取 Entry 集合,然后遍历 Entry 集合,逐个取出 Entry 对象的键和值
1 | //2 获取entrySet,然后遍历 Entry 集合,逐个取出 Entry 对象的键和值 |
结果
4、使用迭代器遍历 map 集合的 keySet
,然后根据键获取值
1 | //3 迭代器 |
结果
5、使用迭代器遍历 map 集合的 entrySet
,然后输出键值对信息
1 | //4 迭代器 + entrySet |
结果
6、直接获取值集合
1 | //5 通过values获取值集合 |
结果
5.4、Map 实现类 - Properties
1、基本介绍
- Properties 类继承自
HashTable
类并实现了Map
接口,也是使用一种键值对的形式保存数据 - Properties 还可以用于从
xxx.properties
配置文件中加载数据到 Properties 类对象,并进行读取和修改
2、常用方法
setProperty
添加/修改配置
1 | Properties properties = new Properties(); |
结果如下:
修改配置
1 | //2 修改 |
结果
- remove
移除配置
1 | properties.remove("password"); |
getProperty
获取配置信息
3、使用
使用
Properties
类获取配置文件中的配置
- 在项目文件夹下创建一个
db.properties
配置文件
1 | com.mysql.cj.jdbc.Driver = |
- 创建一个文件输入流,指定读入的文件路径
- 使用 Properties 的 load 方法读取文件输入流的配置信息
1 | Properties properties = new Properties(); |
查看结果
5.5、TreeMap
1、介绍
和
HashMap
与HashSet
的关系一样,TreeSet
底层同样是TreeMap
add 方法,可以看到每次添加的值同样是一个 静态常量 PRESENT
静态常量定义如下
2、使用
和
TreeSet
一样,TreeMap
同样有一个接收Comparator
实现类的构造函数,在使用自定义类作为TreeMap
的键时,我们可以令自定义类实现Comparable
接口,或在构造TreeMap
时传入一个Comparator
实现类.
六、Collections
工具类
6.1、Collections
工具类介绍
Collections
工具类是一个操作Set
、List
和Map
等集合的工具类。Collections
提供了一系列静态方法对集合进行排序、查询和修改等操作。- 以下方法均为 static 静态方法
1、reverse(List)
反转 List 中元素的顺序
2、shuffle(List)
对 List 集合中的元素进行随机排序
3、sort(List)
根据元素自然顺序对指定 List 集合中的元素进行 升序排序
4、sort(List, Comparator)
根据指定的 Comparator 产生的顺序对指定 List 集合中的元素进行 升序排序
5、swap(List, int, int)
对指定 List 集合中的
i
处元素和j
处元素进行交换,两个下标需要合理
6、synchronizedCollection(Collection)
将线程不安全的集合对象转换为线程安全的集合对象,
synchronizedList
、synchronizedSet
和synchronizedMap
分别接收线程不安全的 List 、 Set 和 Map 实现类,然后返回一个线程安全的集合类(不推荐)
6.2、查找、替换
1、Object max/min(Collection)
按照元素的自然顺序,返回给定集合中的最大/小元素
2、Object max/min(Collection, Comparator)
按照比较器指定的顺序,返回给定集合中的最大/小元素
3、int frequency(Collection,Object)
返回指定集合中指定元素的出现次数
4、void copy(List dest,List src)
将
src
中的内容复制到dest
中
5、boolean replaceAll(List list, Object oldVal, Object newVal)
用新值替换 List 对象中的旧值
6.3、Apache CollectionUtils
1、介绍
这里讲的
CollectionUtils
是在apache
下,而不是spring
下的集合工具类,它可以让代码更加简洁和安全,在MAVEN
中的坐标为:
1 | <dependency> |
2、常用方法
CollectionUtils.addIgnoreNull(personList,null);
除非元素为null,否则向集合添加元素
CollectionUtils.collate(Iterable<? extends O> a, Iterable<? extends O> b)
将两个已排序的集合a和b合并为一个已排序的列表,以便保留元素的自然顺序
CollectionUtils.collate(Iterable<? extends O> a, Iterable<? extends O> b, Comparator<? super O> c)
将两个已排序的集合a和b合并到一个已排序的列表中,以便保留根据Comparator c的元素顺序。
CollectionUtils.containsAny(Collection<?> coll1, T... coll2)
返回该个集合中是否含有至少有一个元素
CollectionUtils.emptyIfNull(Collection<T> collection)
如果参数是null,则返回不可变的空集合,否则返回参数本身。
CollectionUtils.isEmpty(Collection<?> coll)
安全检查指定的集合是否为空
CollectionUtils.isNotEmpty(Collection<?> coll)
安全检查指定的集合是否不为空。
CollectionUtils.reverseArray(Object[] array)
反转给定数组的顺序。
CollectionUtils.subtract(Iterable<? extends O> a, Iterable<? extends O> b)
差集
CollectionUtils.union(Iterable<? extends O> a, Iterable<? extends O> b)
并集
CollectionUtils.intersection(Collection a, Collection b)
交集
boolean isEqualCollection(Collection a, Collection b)
判断两个集合是否相等