Java集合
1、概述
集合类是Java数据结构的实现。Java的集合类是java.util包中的重要内容
2、区别于数组
- 数组:
- 创建时长度固定
- 存放基本数据类型和引用类型
- 集合:
- 长度动态变化的
- 存放引用数据类型
3、分类
单列集合:Collection
双列集合:Map
4、Java集合体系
4.1、迭代器 Iterator
它是Java集合的顶层接口,Iterator 接口提供遍历任何 Collection 的接口。
public class IteratorDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());// 1 2 3
iterator.remove();//用来删除元素
}
System.out.println(list.size());//0
}
}
其他遍历方式:
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
//下标法
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//增强for
for (String s : list) {
System.out.println(s);
}
//foreach
list.forEach(s-> System.out.println(s));
//listIterator
ListIterator<String> listIterator = list.listIterator();
//从前向后遍历
while (listIterator.hasNext()) {
int nextIndex = listIterator.nextIndex();
String next = listIterator.next();
System.out.println(nextIndex + "=" + next);
}
//从后向前遍历
while (listIterator.hasPrevious()) {
int previousIndex = listIterator.previousIndex();
String previous = listIterator.previous();
System.out.println(previousIndex + "=" + previous);
}
}
}
4.2、Collection接口
4.2.1、常见方法
| 方法 | 描述 |
|---|---|
int size(); | 返回元素数量 |
boolean isEmpty(); | 判断是否为空 |
void clear(); | 清空集合元素 |
boolean add(E e); | 添加一个元素 |
boolean addAll(Collection<? extends E> c); | 添加一个集合 |
boolean remove(Object o); | 删除一个元素 |
boolean remove(Object o); | 删除一个元素 |
boolean removeAll(Collection<?> c); | 删除一个集合 |
default boolean removeIf(Predicate<? super E> filter) | 根据条件删除 |
boolean retainAll(Collection<?> c); | 返回交集部分 |
Object[] toArray(); | 返回一个包含此集合中所有元素的数组。 toArray(new Object[0])的功能与toArray()的相同。 |
<T> T[] toArray(T[] a); | 返回一个包含此集合中所有元素的数组。 返回的数组的运行时类型是指定数组的运行时类型。 |
4.2.2、List
4.2.2.1、常见方法
| 方法 | 描述 |
|---|---|
void add(int index, E element); | 在指定索引处添加元素 |
E remove(int index); | 删除指定位置处的元素 |
E get(int index); | 获取指定位置处的元素 |
E set(int index, E element); | 设置指定位置处的元素 |
int indexOf(Object o); | 从前开始查找,返回该元素第一次出现的索引 |
int lastIndexOf(Object o); | 从后开始查找,返回该元素第一次出现的索引 |
default void replaceAll(UnaryOperator<E> operator) | 指定替换方式,替换集合中每个元素为新的值 |
default void sort(Comparator<? super E> c) | 指定排序方式,更改集合中每个元素排列位置 |
| List subList(int fromIndex, int toIndex); | 根据前闭后开,重新获取集合中的一个子集合 |
4.2.2.2、ArrayList、LinkedList、Vector对比
-
相同点:元素的存取是有序的,可以重复,可以存取null,元素有下标
-
不同点:
底层结构 线程安全 集合特点 扩容机制 版本 ArrayList 数组 否 查询快、增删慢 如果有参构造,满后每次1.5倍 如果无参构造,默认0,第一次10,满后每次1.5倍 1.2 LinkedList 双向链表 否 查询慢、增删快 无扩容机制 1.2 Vector 数组 是 查询快、增删慢 如果有参构造,满后每次2倍 如果无参构造,默认10,满后每次2倍 1.0
4.2.2.3、Stack栈
后进先出(LIFO),继承自Vector,也是数组,线程安全的栈。
但作为栈数据类型,不建议使用Vector中与栈无关的方法,尽量只用Stack中的定义的栈相关方法,这样不会破坏栈数据类型。
4.2.2.4、ArrayList源码分析:
核心步骤:
-
创建ArrayList对象的时候,他在底层先创建了一个长度为0的数组。
数组名字:elementDate,定义变量size。
size这个变量有两层含义: ①:元素的个数,也就是集合的长度 ②:下一个元素的存入位置
-
添加元素,添加完毕后,size++
扩容时机一:
- 当存满时候,会创建一个新的数组,新数组的长度,是原来的1.5倍,也就是长度为15.再把所有的元素,全拷贝到新数组中。如果继续添加数据,这个长度为15的数组也满了,那么下次还会继续扩容,还是1.5倍。
扩容时机二:
-
一次性添加多个数据,扩容1.5倍不够,怎么办呀?
如果一次添加多个元素,1.5倍放不下,那么新创建数组的长度以实际为准。
举个例子: 在一开始,如果默认的长度为10的数组已经装满了,在装满的情况下,我一次性要添加100个数据很显然,10扩容1.5倍,变成15,还是不够,
怎么办?
此时新数组的长度,就以实际情况为准,就是110
具体分析过程可以参见视频讲解。
添加一个元素时的扩容:
添加多个元素时的扩容:
4.2.2.5、LinkedList源码分析:
底层是双向链表结构
核心步骤如下:
- 刚开始创建的时候,底层创建了两个变量:一个记录头结点first,一个记录尾结点last,默认为null
- 添加第一个元素时,底层创建一个结点对象,first和last都记录这个结点的地址值
- 添加第二个元素时,底层创建一个结点对象,第一个结点会记录第二个结点的地址值,last会记录新结点的地址值
具体分析过程可以参见视频讲解。
4.2.3、 Set
4.2.3.1、子类对比
-
相同点:元素的存取是无序的,不能重复,可以存取null,元素无下标
-
不同点:
底层结构 线程安全 集合特点 扩容机制 版本 HashSet HashMap (数组+单向链表+红黑树) 否 存取无序 同HashMap 1.2 TreeSet TreeMap (红黑树) 否 支持排序 无扩容机制 1.2 LinkedHashSet LinkedHashMap (数组+双向链表+红黑树) 否 存取有序 同HashMap 1.4 ConcurrentSkipListSet ConcurrentSkipListMap (跳跃表) 是 支持排序 无扩容机制 1.6
4.2.3.2、HashSet添加元素原理
- 底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分了
- 向HashSet中添加元素的过程:
- 当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值判断元素是否存在
- 如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果为true,添加失败;如果为false,那么会保存该元素
- 如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
4.2.3.3、TreeSet实现排序原理
- 底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分了,红黑树(自平衡的排序二叉树)
- TreeSet的有序存储,存储元素时会判断他是否重复,并且自动排序,判断重复元素由
compareTo()方法来实现。因此自定义类要使用TreeSet必须覆写Comparable接口。具体的排序规则可以由我们自己来定
4.2.4、 Queue队列
4.2.4.1、PriorityQueue
- 优先队列,保存队列元素的顺序并不是按照加入的顺序,而是按照队列元素的大小进行排序的,数组实现的二叉树,完全二叉树实现的小顶堆(任意一个非叶子节点的权值,都不大于其左右子节点的权值)。
- 不允许插入null元素。
4.2.4.2、Deque
接口是Queue接口的子接口,它代表一个双端队列
ArrayDeque:
- 数组实现的双端队列,可以在队列两端插入和删除元素,数组队列,先进先出
- 和LinkedList一样也是双向链表
4.3、Map
4.3.1、常见方法
| 方法 | 描述 |
|---|---|
int size(); | 返回元素数量 |
boolean isEmpty(); | 判断是否为空 |
void clear(); | 清空集合元素 |
V put(K key, V value); | 添加一个键值对 |
void putAll(Map<? extends K, ? extends V> m); | 添加集合键值对 |
default V putIfAbsent(K key, V value) | 如果指定键尚未与某个值相关联, 则将其与给定值关联并返回null , 否则返回当前值 |
V remove(Object key); | 删除键所指对象 |
default boolean remove(Object key, Object value) | 删除指定键值对 |
V get(Object key); | 根据键来获取值 |
default V getOrDefault(Object key, V defaultValue) | 根据键来获取值, 如果值为空则返回defaultValue |
efault V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) | 根据键来设置值, 如果值为空则置value,否则算新值 |
default V replace(K key, V value) | 替换指定键值对 |
default boolean replace(K key, V oldValue, V newValue) | 替换指定键值对 |
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) | 函数式替换所有 |
Set<K> keySet(); | 获取所有键集合 |
Collection<V> values(); | 获取所有值集合 |
Set<Map.Entry<K, V>> entrySet(); | 获取键值对集合 |
boolean containsKey(Object key); | 是否包含指定键 |
boolean containsValue(Object value); | 是否包含指定值 |
default void forEach(BiConsumer<? super K, ? super V> action) | 循环遍历键值对 |
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) | 如果该键存在, 则使用函数式重新计算新值 |
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) | 如果该键存在,且值为null, 则使用函数式重新计算新值 |
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) | 如果该键存在,且值不null, 则使用函数式重新计算新值 |
4.3.2、遍历方法
public class MapDemo {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "张三");
map.put(2, "李四");
map.put(3, "王五");
map.put(4, "赵六");
//foreach
map.forEach((key,value)->{
System.out.println(key+"="+value);
});
//entrySet获取
Set<Map.Entry<Integer, String>> entries = map.entrySet();
entries.forEach((Map.Entry<Integer, String> entry) -> {
System.out.println(entry.getKey() + "=" + entry.getValue());
});
//遍历key获取value
Set<Integer> keySets = map.keySet();
keySets.forEach((key) -> {
String value = map.get(key);
System.out.println(key+"="+value);
});
//遍历所有值集合
Collection<String> values = map.values();
values.forEach((value) -> {
System.out.println(value);
});
}
}
4.3.3、子类对比
-
相同点::以键值对的形式添加元素,键不能重复,值可以重复
-
不同点:
底层结构 线程安全 集合特点 扩容机制 版本 Hashtable 数组 是 键和值不可null 存取无序 如果无参构造,加载因子为0.75 默认11,阈值8,大于阈值扩容2倍+1 1.0 HashMap 数组+单向链表+红黑树 否 键和值可以null 存取无序 如果无参构造,加载因子为0.75 默认16,阈值12,大于阈值扩容2倍 何时进行树化?链表大小>=8、数组大小>=64 1.2 LinkedHashMap 继承自HashMap (数组+双向链表+红黑树) 否 键和值可以null 存取有序 同HashMap 1.4 ConcurrentHashMap 数组+链表/红⿊⼆叉树 是,效率比Hashtable高 键和值不可null 存取无序 1.7 TreeMap 红黑树 否 键不可null 值可以null 支持排序 无扩容机制 1.2 ConcurrentSkipListMap 跳跃表 是 键和值不可null 支持排序 无扩容机制 1.6
5、Collections(集合工具类)
常用方法:
| 方法 | 描述 |
|---|---|
public static void reverse(List<?> list) | 反转排列集合元素 |
public static void shuffle(List<?> list) | 随机排序集合元素 |
public static <T extends Comparable<? super T>> void sort(List<T> list) | 默认排序集合元素 |
public static void swap(List<?> list, int i, int j) | 交换指定下标元素 |
public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal) | 替换集合指定元素的值 |
public static <T> void fill(List<? super T> list, T obj) | 填充集合元素为指定值 |
public static int frequency(Collection<?> c, Object o) | 返回指定元素出现次数 |
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) | 根据自然顺序,获取集合中的最大值 |
public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll) | 根据自然顺序,获取集合中的最小值 |
public static <T> List<T> singletonList(T o) | 创建单例集合列表 |
public static <K,V> Map<K,V> singletonMap(K key, V value) | 创建单例集合映射 |
6、数据结构
6.1、栈和队列【记忆】
-
栈结构
先进后出
-
队列结构
先进先出
6.2、数组和链表【记忆】
-
数组结构
查询快、增删慢
-
队列结构
查询慢、增删快
6.6、二叉树【理解】
-
二叉树的特点
- 二叉树中,任意一个节点的度要小于等于2
- 节点: 在树结构中,每一个元素称之为节点
- 度: 每一个节点的子节点数量称之为度
- 二叉树中,任意一个节点的度要小于等于2
-
二叉树结构图
6.7、二叉查找树【理解】
-
二叉查找树的特点
- 二叉查找树,又称二叉排序树或者二叉搜索树
- 每一个节点上最多有两个子节点
- 左子树上所有节点的值都小于根节点的值
- 右子树上所有节点的值都大于根节点的值
-
二叉查找树结构图
-
二叉查找树和二叉树对比结构图
-
二叉查找树添加节点规则
- 小的存左边
- 大的存右边
- 一样的不存
6.8、平衡二叉树【理解】
-
平衡二叉树的特点
- 二叉树左右两个子树的高度差不超过1
- 任意节点的左右两个子树都是一颗平衡二叉树
-
平衡二叉树旋转
-
旋转触发时机
- 当添加一个节点之后,该树不再是一颗平衡二叉树
-
左旋
- 就是将根节点的右侧往左拉,原先的右子节点变成新的父节点,并把多余的左子节点出让,给已经降级的根节点当右子节点
-
右旋
-
就是将根节点的左侧往右拉,左子节点变成了新的父节点,并把多余的右子节点出让,给已经降级根节点当左子节点
-
-
-
平衡二叉树和二叉查找树对比结构图
-
平衡二叉树旋转的四种情况
-
左左
-
左左: 当根节点左子树的左子树有节点插入,导致二叉树不平衡
-
如何旋转: 直接对整体进行右旋即可
-
-
左右
-
左右: 当根节点左子树的右子树有节点插入,导致二叉树不平衡
-
如何旋转: 先在左子树对应的节点位置进行左旋,在对整体进行右旋
-
-
右右
-
右右: 当根节点右子树的右子树有节点插入,导致二叉树不平衡
-
如何旋转: 直接对整体进行左旋即可
-
-
右左
-
右左:当根节点右子树的左子树有节点插入,导致二叉树不平衡
-
如何旋转: 先在右子树对应的节点位置进行右旋,在对整体进行左旋
-
-
6.9、红黑树【理解】
-
红黑树的特点
- 平衡二叉B树
- 每一个节点可以是红或者黑
- 红黑树不是高度平衡的,它的平衡是通过"自己的红黑规则"进行实现的
-
红黑树的红黑规则有哪些
-
每一个节点或是红色的,或者是黑色的
-
根节点必须是黑色
-
如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的
-
如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连 的情况)
-
对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点
-
-
红黑树添加节点的默认颜色
-
添加节点时,默认为红色,效率高
-
-
红黑树添加节点后如何保持红黑规则
- 根节点位置
- 直接变为黑色
- 非根节点位置
- 父节点为黑色
- 不需要任何操作,默认红色即可
- 父节点为红色
- 叔叔节点为红色
- 将"父节点"设为黑色,将"叔叔节点"设为黑色
- 将"祖父节点"设为红色
- 如果"祖父节点"为根节点,则将根节点再次变成黑色
- 叔叔节点为黑色
- 将"父节点"设为黑色
- 将"祖父节点"设为红色
- 以"祖父节点"为支点进行旋转
- 叔叔节点为红色
- 父节点为黑色
- 根节点位置
6.10、哈希值【理解】
-
哈希值简介
是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
-
如何获取哈希值
Object类中的public int hashCode():返回对象的哈希码值
-
哈希值的特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
6.11、哈希表结构【理解】
-
JDK1.8以前
数组 + 链表
-
JDK1.8以后
-
节点个数少于等于8个
数组 + 链表
-
节点个数多于8个
数组 + 红黑树
-
7、不可变集合
7.1、什么是不可变集合
是一个长度不可变,内容也无法修改的集合
7.2、使用场景
如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。
当集合对象被不可信的库调用时,不可变形式是安全的。
简单理解:
不想让别人修改集合中的内容
比如说:
1,斗地主的54张牌,是不能添加,不能删除,不能修改的
2,斗地主的打牌规则:单张,对子,三张,顺子等,也是不能修改的
3,用代码获取的操作系统硬件信息,也是不能被修改的
7.3、不可变集合分类
- 不可变的list集合
- 不可变的set集合
- 不可变的map集合
7.4、不可变的list集合
public class ImmutableDemo1 {
public static void main(String[] args) {
/*
创建不可变的List集合
"张三", "李四", "王五", "赵六"
*/
//一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
List<String> list = List.of("张三", "李四", "王五", "赵六");
System.out.println(list.get(0));
System.out.println(list.get(1));
System.out.println(list.get(2));
System.out.println(list.get(3));
System.out.println("---------------------------");
for (String s : list) {
System.out.println(s);
}
System.out.println("---------------------------");
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("---------------------------");
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
System.out.println("---------------------------");
//list.remove("李四");
//list.add("aaa");
list.set(0,"aaa");
}
}
7.5、不可变的Set集合
public class ImmutableDemo2 {
public static void main(String[] args) {
/*
创建不可变的Set集合
"张三", "李四", "王五", "赵六"
细节:
当我们要获取一个不可变的Set集合时,里面的参数一定要保证唯一性
*/
//一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
Set<String> set = Set.of("张三", "张三", "李四", "王五", "赵六");
for (String s : set) {
System.out.println(s);
}
System.out.println("-----------------------");
Iterator<String> it = set.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("-----------------------");
//set.remove("王五");
}
}
7.6、不可变的Map集合
7.6.1、键值对个数小于等于10
public class ImmutableDemo3 {
public static void main(String[] args) {
/*
创建Map的不可变集合
细节1:
键是不能重复的
细节2:
Map里面的of方法,参数是有上限的,最多只能传递20个参数,10个键值对
细节3:
如果我们要传递多个键值对对象,数量大于10个,在Map接口中还有一个方法
*/
//一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
Map<String, String> map = Map.of("张三", "南京", "张三", "北京", "王五", "上海",
"赵六", "广州", "孙七", "深圳", "周八", "杭州",
"吴九", "宁波", "郑十", "苏州", "刘一", "无锡",
"陈二", "嘉兴");
Set<String> keys = map.keySet();
for (String key : keys) {
String value = map.get(key);
System.out.println(key + "=" + value);
}
System.out.println("--------------------------");
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=" + value);
}
System.out.println("--------------------------");
}
}
7.6.2、键值对个数大于10
public class ImmutableDemo4 {
public static void main(String[] args) {
/*
创建Map的不可变集合,键值对的数量超过10个
*/
//1.创建一个普通的Map集合
HashMap<String, String> hm = new HashMap<>();
hm.put("张三", "南京");
hm.put("李四", "北京");
hm.put("王五", "上海");
hm.put("赵六", "北京");
hm.put("孙七", "深圳");
hm.put("周八", "杭州");
hm.put("吴九", "宁波");
hm.put("郑十", "苏州");
hm.put("刘一", "无锡");
hm.put("陈二", "嘉兴");
hm.put("aaa", "111");
//2.利用上面的数据来获取一个不可变的集合
/*
//获取到所有的键值对对象(Entry对象)
Set<Map.Entry<String, String>> entries = hm.entrySet();
//把entries变成一个数组
Map.Entry[] arr1 = new Map.Entry[0];
//toArray方法在底层会比较集合的长度跟数组的长度两者的大小
//如果集合的长度 > 数组的长度 :数据在数组中放不下,此时会根据实际数据的个数,重新创建数组
//如果集合的长度 <= 数组的长度:数据在数组中放的下,此时不会创建新的数组,而是直接用
Map.Entry[] arr2 = entries.toArray(arr1);
//不可变的map集合
Map map = Map.ofEntries(arr2);
map.put("bbb","222");*/
//Map<Object, Object> map = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));
Map<String, String> map = Map.copyOf(hm);
map.put("bbb","222");
}
}