Java集合
1.集合概述
1.1.基本概念
集合可以存储任意类型的对象,并且长度可变
集合对象可以是任意的数据类型,并且长度可变
1.2.集合分类
单列集合Collection | 双列集合Map |
---|---|
单列集合根接口,用于存储一系列符合某种规则的元素 | 双列集合根接口,用于存储具有键(Key )、值(Value )映射关系的元素 |
Collection 集合有两个重要的子接口,分别是List和Set | Map 集合中每个元素都包含一对键值,并且Key唯一,在使用Map 集合时通过指定的Key找到对应的· |
List 集合的特点是元素有序、可重复。该接口的主要实现类有ArrayList 和LinkedList | Map 接口的主要实现类有HashMap 和TreeMap |
Set 集合的特点是元素无序并且不可重复。该接口的主要实现类有HashSet 和TreeSet |
2.Collection接口
方法声明 | 功能描述 |
---|---|
boolean add(Object o) | 向集合中添加一个元素 |
boolean addAll(Collection c) | 将指定集合c中的所有元素添加到该集合中 |
void clear() | 删除该集合中的所有元素 |
boolean remove(Object o) | 删除该集合中指定的元素 |
boolean removeAll(Collection c) | 删除该集合中包含指定集合c中的所有元素 |
boolean isEmpty() | 判断该集合是否为空 |
boolean contains(Object o) | 判断该集合中是否包含某个元素 |
boolean containsAll(Collection c) | 判断该集合中是否包含指定集合c中的所有元素 |
Iterator iterator() | 返回在该集合的元素上进行迭代的迭代器(Iterator),用于遍历该集合所有元素 |
int size() | 获取该集合元素个数 |
Stream | 将集合源转换为有序元素的流对象(JDK 8新方法) |
3.List接口
3.1. List接口简介
3.1.1.定义
- List接口继承自Collection接口,是单列集合的一个重要分支,习惯性的会将实现了List接口的对象称为List集合
3.1.2.特点
- List集合中允许出现重复元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引(类似于数组中的元素角标)来访问集合中的元素
- 元素有序,存入顺序和取出顺序一致
3.1.3.常用方法
方法声明 | 功能描述 |
---|---|
void add(int index,Object element) | 将元素element插入在List集合的指定索引位置 |
boolean addAll(int index,Collection c) | 将集合c包含的所有元素插入到List集合的指定索引位置 |
Object get(int index) | 返回集合索引index处的元素 |
Object remove(int index) | 删除index索引处的元素 |
Object set(int index, Object element) | 将索引index处元素替换成element元素,并将替换后的元素返回 |
int indexOf(Object o) | 返回对象o在List集合中首次出现的位置索引 |
int lastIndexOf(Object o) | 返回对象o在List集合中最后一次出现的位置索引 |
List subList(int fromIndex, int toIndex) | 返回从索引fromIndex(包括)到 toIndex(不包括)处所有元素集合组成的子集合 |
Object[] toArray() | 将集合元素转换为数组 |
default void sort(Comparator<? super E> c) | 根据指定的比较器规则对集合元素排序(JDK 8新方法) |
3.2. ArrayList类
3.2.1.特点
- ArrayList是List接口的一个实现类,它是程序中最常见的一种集合
- ArrayList内部的数据存储结构是数组形式
- 不适合做大量的增删操作
- 遍历和查找元素时显得非常高效
3.2.2.语法
1 | ArrayList list = new ArrayList(); |
3.3. LinkedList类
3.3.1.特点
- 内部包含有两个Node类型的first和last属性的双向循环链表结构
- 遍历和查找效率较低
- 增删操作表现出很高的效率
3.3.2.底层原理
左图为新增元素,图中的元素1和元素2在集合中彼此为前后关系,在它们之间新增一个元素时,只需要让元素1记住它后面的元素是新元素,让元素2记住它前面的元素为新元素就可以了
右图为删除元素,要想删除元素1和元素2之间的元素3,只需要让元素1与元素2变成前后关系就可以了
3.3.3.特有方法
方法声明 | 功能描述 |
---|---|
void add(int index, E element) | 在此列表中指定的位置插入指定的元素。 |
void addFirst(Object o) | 将指定元素插入集合的开头 |
void addLast(Object o) | 将指定元素添加到集合的结尾 |
Object getFirst() | 返回集合的第一个元素 |
Object getLast() | 返回集合的最后一个元素 |
Object removeFirst() | 移除并返回集合的第一个元素 |
Object removeLast() | 移除并返回集合的最后一个元素 |
boolean offer(Object o) | 将指定元素添加到集合的结尾 |
boolean offerFirst(Object o) | 将指定元素添加到集合的开头 |
boolean offerLast(Object o) | 将指定元素添加到集合的结尾 |
Object peek() | 获取集合的第一个元素 |
Object peekFirst() | 获取集合的第一个元素 |
Object peekLast() | 获取集合的最后一个元素 |
Object poll() | 移除并返回集合的第一个元素 |
Object pollFirst() | 移除并返回集合的第一个元素 |
Object pollLast() | 移除并返回集合的最后一个元素 |
void push(Object o) | 将指定元素添加到集合的开头 |
Object pop() | 移除并返回集合的第一个元素 |
3.3.4.语法
1 | LinkedList link = new LinkedList(); |
4.Collection集合遍历
4.1. Iterator遍历集合
4.1.1.工作原理
Iterator遍历集合时,内部采用指针的方式来跟踪集合中的元素。在调用next()方法之前,索引位于第一个元素之前,不指向任何元素
第一次调用next()方法后,索引会向后移动一位,指向第一个元素并将该元素返回
再次调用next()方法时,索引会指向第二个元素并将该元素返回
以此类推,直到hasNext()方法返回false,表示到达了集合的末尾终止对元素的遍历
4.1.2.实现
1 | int[] arr = new int[10]; |
4.1.3.注意
Iterator
迭代器对集合中的元素进行迭代时,如果调用了集合对象的remove()方法删除元素,会出现ConcurrentModificationException
异常。
4.2. foreach遍历集合
1 | /** |
- 注意:
foreach
循环遍历集合和数组时,只能访问集合中的元素,不能对其中的元素进行修改
4.3. JDK8 —- foreach遍历集合
1 | list.forEach(obj -> System.out.println(obj)); |
4.4.forEachRemaining
- JDK 8中还针对Iterator迭代器对象提供了一个forEachRemaining(Consumer action)方法来进行遍历,该方法同样需要一个函数式接口
1 | Iterator it = list.iterator(); |
5.Set接口
5.1. Set接口简介
5.1.1.特点
- Set接口和List接口一样,同样继承自Collection接口
- Set接口中的元素无序,并且都会以某种规则保证存入的元素不出现重复
5.1.2.分类
分类 | 简介 |
---|---|
HashSet | 根据对象的哈希值来确定元素在集合中的存储的位置,因此具有良好的存取和查找性能 |
TreeSet | 以二叉树的方式来存储元素,它可以实现对集合中的元素进行排序 |
5.2. HashSet类
5.2.1.特点
- HashSet是Set接口的一个实现类,它所存储的元素不可重复,并且无序
- 当向HashSet集合中添加一个元素时,首先会调用该元素的hashCode()方法来确定元素的存储位置,然后再调用元素对象的equals()方法来确保该位置没有重复元素
5.2.2.存储原理
- 在Java中,一些基本数据包装类、String类等都已经默认重写了
hashCode()
和equals()
方法 - 开发者向
HashSet
集合中添加自定义的数据类型,如Student
类时,必须增加重写的hashCode()
和equals()
方法,才能保证数据的唯一性。
5.3. TreeSet类
5.3.1.特点
TreeSet
是Set
接口的另一个实现类,它内部采用平衡二叉树来存储元素,来保证TreeSet
集合中没有重复的元素,并且可以对元素进行排序- 二叉树就是每个节点最多有两个子节点的有序树,每个节点及其子节点组成的树称为子树,左侧的节点称为“左子树”,右侧的节点称为“右子树”,其中左子树上的元素小于它的根结点,而右子树上的元素大于它的根结点
5.3.2.存储原理
5.3.3.特有方法
方法声明 | 功能描述 |
---|---|
Object first() | 返回TreeSet集合的首个元素 |
Object last() | 返回TreeSet集合的最后一个元素 |
Object lower(Object o) | 返回TreeSet集合中小于给定元素的最大元素,如果没有返回null |
Object floor(Object o) | 返回TreeSet集合中小于或等于给定元素的最大元素,如果没有返回null |
Object higher(Object o) | 返回TreeSet集合中大于给定元素的最小元素,如果没有返回null |
Object ceiling(Object o) | 返回TreeSet集合中大于或等于给定元素的最小元素,如果没有返回null |
Object pollFirst() | 移除并返回集合的第一个元素 |
Object pollLast() | 移除并返回集合的最后一个元素 |
5.3.4.排序
5.3.4.1.基本概念
- 向TreeSet集合添加元素时,都会调用compareTo()方法进行比较排序,该方法是Comparable接口中定义的,因此要想对集合中的元素进行排序,就必须实现
Comparable
接口 - Java中大部分的类都实现了
Comparable
接口,并默认实现了接口中的CompareTo()
方法,如Integer
、Double
和String等
5.3.4.2.分类
- 自然排序:要求存储的元素类必须实现
Comparable
接口,并重写compareTo()
方法 - 定制排序:要求自定义一个比较器,该比较器必须实现
Comparator
接口,并重写compare()
方法,然后将该比较器作为参数传入集合的有参构造
5.3.4.2.主要区别
自然排序 | 定制排序 |
---|---|
适合元素类本身未实现Comparable 接口,无法进行比较 | 元素类本身实现Comparable 接口 |
适合元素类实现的Comparable接口排序规则无法满足用户需求 | 依赖compareTo() 方法的实现 |
会额外定义一个实现Comparator 接口的比较器 | 实现Comparable 接口排序规则比较单一,不利于后续改进 |
5.3.4.2.实现
- 自然排序
1 | public class Person implements Comparable<Object>{ |
1 | import java.util.TreeSet; |
- 定制排序
1 | import java.util.Comparator; |
1 | import java.util.TreeSet; |
6.Map接口
6.1. Map接口简介
6.1.1.特点
- 双列集合,它的每个元素都包含一个键对象
Key
和值对象Value
,键和值对象之间存在一种对应关系,称为映射 Map
中的映射关系是一对一的,一个键对象Key
对应唯一一个值对象Value
,其中键对象Key和值对象Value可以是任意数据类型,并且键对象Key不允许重复,这样在访问Map集合中的元素时,只要指定了Key,就能找到对应的Value
6.1.2.常用方法
方法声明 | 功能描述 |
---|---|
void put(Object key, Object value) | 向Map集合中添加指定键值映射的元素 |
int size() | 返回Map集合键值对映射的个数 |
Object get(Object key) | 返回指定键所映射的值,如果此映射不包含该键的映射关系,则返回null |
boolean containsKey(Object key) | 查看Map集合中是否存在指定的键对象key |
boolean containsValue(Object value) | 查看Map集合中是否存在指定的值对象value |
Object remove(Object key) | 删除并返回Map集合中指定键对象Key的键值映射元素 |
void clear() | 清空整个Map集合中的键值映射元素 |
Set keySet() | 以Set集合的形式返回Map集合中所有的键对象Key |
Collection values() | 以Collection集合的形式返回Map集合中所有的值对象Value |
Set<Map.Entry<Key,Value>> entrySet() | 将Map集合转换为存储元素类型为Map的Set集合 |
Object getOrDefault(Object key, Object defaultValue) | 返回Map集合指定键所映射的值,如果不存在则返回默认值defaultValue(JDK 8新方法) |
void forEach(BiConsumer action) | 通过传入一个函数式接口对Map集合元素进行遍历(JDK 8新方法) |
Object putIfAbsent(Object key, Object value) | 向Map集合中添加指定键值映射的元素,如果集合中已存在该键值映射元素,则不再添加而是返回已存在的值对象Value(JDK 8新方法) |
boolean remove(Object key, Object value) | 删除Map集合中键值映射同时匹配的元素(JDK 8新方法) |
boolean replace(Object key, Object value) | 将Map集合中指定键对象Key所映射的值修改为value(JDK 8新方法) |
6.2. HashMap类
6.2.1.特点
- HashMap集合是Map接口的一个实现类,它用于存储键值映射关系,该集合的键和值允许为空,但键不能重复,且集合中的元素是无序的
- HashMap底层是由哈希表结构组成的,其实就是“数组+链表”的组合体,数组是
HashMap
的主体结构,链表则主要是为了解决哈希值冲突而存在的分支结构。正因为这样特殊的存储结构,HashMap
集合对于元素的增、删、改、查操作表现出的效率都比较高
6.2.2.内部结构
- 在哈希表结构中,主体结构为图中水平方向的数组结构,其长度称为HashMap集合的容量(capacity)
- 数组结构垂直对应的是链表结构,链表结构称为一个桶(bucket),每个桶的位置在集合中都有对应的桶值,用于快速定位集合元素添加、查找时的位置
6.2.3.存储原理
6.2.4.注意
使用HashMap集合时,如果通过键对象k定位到的桶位置不含链表结构,那么对于查找、添加等操作很快;如果定位到的桶位置包含链表结构,对于添加操作,其时间复杂度依然不大,因为最新的元素会插入链表头部,只需要简单改变引用链即可;而对于查找操作来讲,此时就需要遍历链表,然后通过键对象k的equals(k)方法逐一查找比对。
所以,从性能方面考虑,HashMap中的链表出现越少,性能才会越好,这就要求HashMap集合中的桶越多越好。
HashMap根据实际情况,内部实现了动态地分配桶数量的策略。
通过new HashMap()方法创建HashMap时,会默认集合容量capacity大小为16,加载因子loadFactor为0.75(HashMap桶多少权衡策略的经验值),此时该集合桶的阀值就为12(容量capacity与加载因子loadFactor的乘积),如果向HashMap集合中不断添加完全不同的键值对<k,v>,当超过12个存储元素时,HashMap集合就会默认新增加一倍桶的数量(也就是集合的容量),此时集合容量就变为32。
6.2.4.LinkedHashMap
HashMap
集合并不保证集合元素存入和取出的顺序- 如果想让这两个顺序一致,可以使用
LinkedHashMap
类,它是HashMap的子类。和LinkedList
一样也使用双向链表来维护内部元素的关系,使LinkedHashMap
元素迭代的顺序与存入的顺序一致 - 一般情况下,用的最多的是
HashMap
,在Map
中插入、删除和定位元素,HashMap
是最好的选择。但如果需要输出的顺序和输入的相同,那么用LinkedHashMap
可以实现,它还可以按读取顺序来排列
6.3. Map集合遍历
6.3.1.Iterator迭代器
- 遍历思路:先将
Map
集合转换为Iterator
接口对象,然后进行遍历。由于Map集合中元素是由键值对组成的,所以使用Iterator
接口遍历Map
集合时,会有两种将Map
集合转换为Iterator
接口对象再进行遍历的方法 - 遍历方法:
keySet()
方法和entrySet()
方法
6.3.1.1.keySet()方法
- 先将Map集合中所有键对象转换为Set单列集合,接着将包含键对象的Set集合转换为Iterator接口对象,然后遍历Map集合中所有的键,再根据键获取相应的值。
1 | Set keySet = map.keySet(); // 获取键的集合 |
6.3.1.2.entrySet()方法
- 将原有Map集合中的键值对作为一个整体返回为Set集合,接着将包含键值对对象的Set集合转换为Iterator接口对象,然后获取集合中的所有的键值对映射关系,再从映射关系中取出键和值
1 | Set entrySet = map.entrySet(); |
6.3.2.JDK8提供的forEach
- JDK 8中,根据
Lambda
表达式特性新增了一个forEach(BiConsumer action)
方法来遍历Map
集合,该方法所需要的参数也是一个函数式接口,因此可以使用Lambda
表达式的书写形式来进行集合遍历
1 | map.forEach((key,value) -> System.out.println(key + ":" + value)); |
6.3.3.值遍历—values()方法
- 在
Map
集合中,除了以上介绍的两种主要的遍历方式外,还提供了一个values()
方法,通过这个方法可以直接获取Map中存储所有值的Collection
集合
1 | Collection values = map.values(); // 获取Map集合中value值集合对象 |
6.4. TreeMap集合
- 介绍:
TreeMap
集合是Map
接口的另一个实现类,在TreeMap
内部是通过二叉树的原理来保证键的唯一性,这与TreeSet
集合存储的原理一样,因此TreeMap
中所有的键是按照某种顺序排列的 - 说明:为了实现
TreeMap
元素排序,可以参考TreeSet
集合排序方式,使用自然排序和定制排序
1 | import java.util.Map; |
comparator
方式
1 | import java.util.*; |
1 | public class Main { |
1 | /** |
6.5. Properties集合
- 介绍:
Map
接口还有一个实现类Hashtable
,它和HashMap
十分相似,其中一个主要区别在于Hashtable
是线程安全的 - 说明:
Hashtable
类有一个子类Properties
,Properties
主要用来存储字符串类型的键和值,在实际开发中,经常使用**Properties
集合类来存取应用的配置项**
1 | import java.io.FileInputStream; |
1 | /** |
7.泛型
7.1.why
- 集合中可以存储任意类型的对象元素,但是当把一个对象存入集合后,集合会“忘记”这个对象的类型,将该对象从集合中取出时,这个对象的编译类型就统一变成了Object类型
- 在程序中无法确定一个集合中的元素到底是什么类型,那么在取出元素时,如果进行强制类型转换就很容易出错
7.2.语法
1 | ArrayList<参数化类型> list = new ArrayList<参数化类型>(); |
8.常用工具类
8.1. Collections工具类
8.1.1.添加、排序常用方法
方法声明 | 功能描述 |
---|---|
static | 将所有指定元素添加到指定集合c中 |
static void reverse(List list) | 反转指定List集合中元素的顺序 |
static void shuffle(List list) | 对List集合中的元素进行随机排序 |
static void sort(List list) | 根据元素的自然顺序对List集合中的元素进行排序 |
static void swap(List list,int i,int j) | 将指定List集合中角标i处元素和j处元素进行交换 |
1 | improt java.util.ArrayList; |
1 | /** |
8.1.2.查找、替换常用方法
方法声明 | 功能描述 |
---|---|
static int binarySearch(List list,Object key) | 使用二分法搜索指定对象在List集合中的索引,查找的List集合中的元素必须是有序的 |
static Object max(Collection col) | 根据元素的自然顺序,返回给定集合中最大的元素 |
static Object min(Collection col) | 根据元素的自然顺序,返回给定集合中最小的元素 |
static boolean replaceAll(List list,Object oldVal,Object newVal) | 用一个新值newVal替换List集合中所有的旧值oldVal |
1 | improt java.util.ArrayList; |
1 | /** |
8.2. Arrays工具类
sort()
排序
1 | int[] arr = {9, 8, 3, 5, 2}; |
binarySearch(Object[] obj, Object key)
用二分法查找obj
中的key
1 | int[] arr = {9, 8, 3, 5, 2}; |
copyOfRange(int[] original, int from, int to)
复制数组的指定范围
1 | int[] arr = {9, 8, 3, 5, 2} |
fill(Object[] a, Object value)
用value
把数组填充
1 | int[] arr = {9, 8, 3, 5, 2}; |
asList()
把Array转换为List
1 | Integer[] array = {9, 8, 3, 5, 2}; |
stream()
创建stream
流对象
1 | Integer[] array = {9, 8, 3, 5, 2}; |
9.JDK8 —- 聚合操作
9.1. 聚合操作简介
9.2. 创建Stream流对象
- 所有的
Collections
集合都可以使用stream()
静态方法获取Stream
流对象 Stream
接口的of()
静态方法可以获取基本类型包装类数组、引用类型数组和单个元素的Stream
流对象Arrays
工具类的stream()
静态方法也可以获取数组元素的Stream
流对象
1 | import java.util.*; |
- 注意:在进行聚合操作时,只是改变了
Stream
流对象中的数据,并不会改变原始集合或数组中的源数据
9.3. Stream流的常用方法
方法声明 | 功能描述 |
---|---|
Stream | 将指定流对象中的元素进行过滤,并返回一个子流对象 |
Stream | 将流中的元素按规则映射到另一个流中 |
Stream | 删除流中重复的元素 |
Stream | 将流中的元素按自然顺序排序 |
Stream | 截取流中元素的长度 |
Stream | 丢弃流中前n个元素 |
static | 将两个流对象合并为一个流 |
long count() | 统计流中元素的个数 |
R collect(Collector<? super T, A, R> collector) | 将流中的元素收集到一个容器中(如集合) |
Object[] toArray() | 将流中的元素收集到一个数组中 |
void forEach(Consumer<? super T> action) | 将流中的元素进行遍历 |
forEach()
遍历
1 | // 不保证元素的遍历过程在流中是被有序执行的 |
filter()
过滤
1 | // 将一个Stream流中的元素进行筛选转换成另一个子集流 |
map()
映射
1 | // 将流对象中的元素通过特定的规则进行修改然后映射为另一个流对象 |
limit()
截取
1 | // 用于对流对象中的元素进行截取操作 |
collect()
收集
1 | // |
1 | import java.util.List; |
1 | /** |
9.4. Parallel Stream (并行流)
9.4.1.串并流对比
- 串行流(Serial Stream):将源数据转换为一个流对象,然后在单线程下执行聚合操作的流(也就是单一管道流)
- 并行流(Parallel Stream):将源数据分为多个子流对象进行多线程操作(也就是多个管道流),然后将处理的结果再汇总为一个流对象
9.4.2.并行流创建
- 通过
Collection
集合接口的parallelStream()
方法直接将集合类型的源数据转变为Stream
并行流 - 通过
BaseStream
接口的parallel()
方法将Stream
串行流转变为Stream
并行流
1 | import java.util.*; |
1 | /** |
9.4.3.注意
创建
Stream
流对象时,除非有特别声明,否则默认创建的都是串行流使用
Stream
并行流在一定程度上可以提升程序的执行效率,但是在多线程执行就会出现线程安全这个大问题,所以为了能够在聚合操作中使用Stream
并行流,前提是要执行操作的源数据在并行执行过程中不会被修改