一,集合的体系结构
1.1 集合介绍
为什么需要集合?
- 数组的局限性:长度固定,无法动态扩容;只能存储同一类型数据;缺少现成的操作方法(如排序、查找)
- 集合的优势:动态扩容(自动调整大小);可存储对象(包括不同类型);提供丰富的操作方法(增删改查、排序等)
集合主要分为两类:
-
Collection单列集合
单列集合:在添加数据的时候每次只能添加一个据的集合
-
Map双列集合
双列集合:在添加数据的时候每次可以添加两个数据的集合
示意图如下:

1.2 Collection体系
Collection 集合体系如下图:

-
List 系列集合:添加的元素是有序的,可重复的,有索引的
有序:存放和拿取的顺序一致
可重复:集合中存储得元素可重复
有索引:可以通过索引获取集合中的每一个元素
-
Set 系列集合:添加的元素是无序,不重复,没有索引的
无序:存放和拿取得顺序不一致
不重复:集合中得元素是不可重复
没有索引:不能通过索引获取集合中的每一个元素
二,Collection
Collection 是单列集合的最终父级接口,它的功能是全部单列集合都可以继承使用的, 即 Collection 接口存放的方法都是所有单列集合中共有的方法
Collection 常用方法
| 方法名 | 说明 |
|---|---|
| public boolean add(E e) | 把给定的对象添加到当前集合中 |
| public void clear() | 清空集合中的所有元素 |
| public boolean remove(E e) | 把给定的对象在当前集合中删除,删除失败返回false |
| public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
| public boolean isEmpty() | 判断当前集合是否为空 |
| public int size() | 返回集合中元素的个数/集合的长度 |
注意要点:
-
Collection 是接口,不能创建这玩意的对象而是创建它的实现类的对象
-
add 方法中
如果我们往List系列添加元素那么返回值永远为 true
如果我们往Set系列添加元素并且这个元素集合中已经存在,那么会返回 false
-
contains方法在底层是依赖equals方法判断对象是否一致的
如果我们是自己定义的对象并且我们没有在这个对象中重写 equals 方法,那么默认使用 Object 类中的 equals 方法进行判断, 而 Object 方法中的 equals 方法是判断地址值的。
解决方法:在自定义的类中重写 equals 方法即可
import java.util.ArrayList; import java.util.Collection; public class Main { public static void main(String[] args){ Collection<User> arr=new ArrayList<>(); User u1=new User(1,2); User u2=new User(3,4); arr.add(u1); //如果没有在User类中重写equals方法那么调用的就是Object类中的equals就返回false System.out.println(arr.contains(u2)); } }
三,Collection系列集合的遍历方式
之所以用新的遍历方式是因为在 Collection 系列集合中存在 Set 系列集合,Set 系列集合的特点有无索引!故使用新的遍历方式。
3.1 迭代器遍历
迭代器最大的特点:不依赖索引
迭代器在 Java 中的类是Iterator,迭代器是集合专用的遍历方式
-
Collection 集合获取迭代器
ArrayList<String> list=new ArrayList<>(); Iterator<String> it=list.iterator(); -
Iterator中常用的方法方法名 说明 boolean hasNext() 判断当前位置是否有元素,有元素返回true,没有返回false E next() 获取当前位置的元素,并且迭代器对象移动到下一个位置 void remove() 删除当前迭代器指向的元素 -
举例
ArrayList<String> list=new ArrayList<>(); list.add("a"); list.add("aa"); list.add("aaa"); list.add("aaaa"); Iterator<String> it=list.iterator();//获取当前集合的Iterator对象,需要注意迭代器的E和集合的E要一致 while(it.hasNext()){//hasNext是判断当前位置(初始为0)是否有元素!! System.out.println(it.next());//next是获取当前位置的元素的内容!并将迭代器移动到下一个(0->1) }
底层就链表,迭代器可以看做 c 语言的指针。
注意事项:
-
如果迭代器已经获取完了最后一个元素,我们再次指向 next 方法,会报错
NoSunchElementException -
迭代器遍历完,指针不会复位!
如果还想再次遍历只能再次获取迭代器对象(因为底层是单向链表)
-
循环中只能使用一个 next 方法
-
迭代遍历的时候,不能用集合的方式进行增加或删除!
-
3.2 增强for遍历
介绍:
- 增强for的底层就是迭代器,为了简化迭代器的书写!
- 它是在JDK5之后出现的,其内部原理就是一个Iterator迭代器
- 所有的单列集合和数组才能使用增强for进行遍历
格式:
for(元素类型 变量名 : 数组或集合){
}
for(String s:list){
sout(s);
}
//s其实就是一个第三方变量,在循环的过程中依次表示集合中的每一个数据
//idea可以使用 集合名.for来快捷键生成
3.3 Lambda表达式遍历
得益于 JDK 8 开始的新技术 Lambda 表达式,提供了一种更简单,更直接的遍历集合的方式。
| 方法名 | 说明 |
|---|---|
| default void forEach(Consumer<? super T> action): | 结合lambda遍历集合 |
ArrayList<String> list=new ArrayList<>();
list.add("a");
list.add("aa");
list.add("aaa");
list.add("aaaa");
list.forEach(new Consumer<String>(){//Consumer是一个接口,并且是函数式接口!
@Override
//这里的s就依次表示集合中的每个数据
public void accept(String s){
System.out.println(s);//这里的方法体就代表我们要对集合进行的操作
}
});
//写Lambda表达式的时候如果不太熟可以先写匿名内部类,然后删减多余部分即可!
list.forEach(s->System.out.println(s));//简化写法
forEatch 方法的底层:遍历集合依次得到每一个元素,每得到一个集合中的元素就会把数据传递给我们自己写的 accept 方法
四,List系列的集合
介绍:
- List系列的集合是Collection单列集合中的一种,故Collection系列的所有方法List系列都可以使用
- List集合因为有索引,所有多了很多对索引操作的特有的方法!
| 方法名 | 说明 |
|---|---|
| void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
| E remove(int index) | 删除指定索引的元素,返回被删除的元素 |
| E set(int index,E element) | 修改指定索引的元素,返回被修改的元素 |
| E get(int index) | 返回指定索引处的元素 |
细节:
-
add 方法:添加后,原来索引上的元素会依次往后移!
-
remove 方法:存在删除索引和删除指定元素的方法一个是重载 Collection 的,一个是 Collection 的方法
list.remove(1)此时是删除 1 索引上的元素!而不是元素 1原因:在调用方法的时候,如果方法出现了重载现象,优先调用实参和形参一致的那个方法!
解决方法:手动装箱
public class Main { public static void main(String[] args){ List<Integer> list=new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); list.remove(1);//删除的是1索引内容 System.out.println(list); Integer i=Integer.valueOf(1);//手动装箱 list.remove(i);//删除的是元素1 System.out.println(list); } }
4.1 List集合的五种遍历方式
List 是继承于 Collection 接口,故 List 也存在 Collection 的三种遍历方式,又 List 存在索引,故 List 也有自己特有的两种遍历方式
- 迭代器遍历
- 列表迭代器遍历
- 增强for遍历
- Lambda表达式遍历
- 普通for循环(因为List存在索引)
五种遍历方式的举例:
public class Main {
public static void main(String[] args){
List<Integer> list=new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
//1.迭代器
Iterator<String> it=list.iterator();
while(it.hasNext()){
String str=it.next();
System.out.println(str);
}
//2.增强for
for(String s:list){
System.out.println(s);
}
//3.Lambda表达式
list.forEach(s->System.out.println(s));
//4.普通for
for(int i=0;i<list.size();i++){
String s=list.get(i);
System.out.println(s);
}
}
}
列表迭代器:是迭代器的子接口(迭代器的方法它都可以使用)
-
获取列表迭代器
List<String> arr=new ArrayList<>(); ListIterator<Stirng> it=arr.listIterator(); -
常用方法:
| 方法名 | 说明 |
|---|---|
| boolean hasNext() | 判断当前位置是否有元素,有元素返回true,没有返回false |
| E next() | 获取当前位置的元素,并且迭代器对象移动到下一个位置 |
| void remove() | 删除当前迭代器指向的元素 |
| void set(E e) | 修改当前迭代器指向的元素 |
| void add(E e) | 给指针指向的元素添加元素在下一个位置上! |
public class Main {
public static void main(String[] args){
List<String> list=new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
String i=it.next();
if("bbb".equals(i)){
it.add("qqq");
}
}
System.out.println(list);
}
}
总结:
- 迭代器遍历:在遍历的过程中需要删除索引,使用迭代器遍历
- 列表迭代器:在遍历的过程中需要添加元素,使用列表迭代器
- 增强for遍历,Lambda表达式:只是想遍历,就用这个
- 普通for:如果遍历的时候需要操作索引,使用普通for遍历
4.2 ArrayList集合
4.2.1 介绍
定义:ArrayList 是 List 接口的动态数组实现,可以存储任意类型的对象(通过泛型指定)。
核心特性:
- 元素有序(按插入顺序排列)
- 元素可重复
- 动态扩容(自动调整数组大小)
- 非线程安全(多线程环境下需同步处理)
创建方法
ArrayList<String> list=new ArrayListM<>();
常用方法:ArrayList 的常用方法就是 Collection 的常用方法 +List 的常用方法
ArrayList<String> list1=new ArrayListM<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
ArrayList<String> list2=new ArrayListM<>();
list2.addAll(list1);//直接将list1集合的全部内容添加到list2中
4.2.2 底层实现原理
数据结构
-
基于 Object 数组 实现(源码中的
transient Object[] elementData) -
默认初始容量为 10(
DEFAULT_CAPACITY = 10) -
当数组容量不足时,自动触发扩容机制
扩容机制
-
扩容公式:新容量 = 旧容量 * 1.5(源码中的
int newCapacity = oldCapacity + (oldCapacity >> 1))- 如果还是不够,就取所需的最小容量
-
扩容步骤:
- 当添加元素时发现数组已满
- 创建一个新数组(大小为原数组的 1.5 倍)
- 将旧数组元素复制到新数组
- 新元素添加到新数组末尾
-
样板代码
ArrayList<Integer> list = new ArrayList<>(10); // 初始容量10 // 一次性添加1000个元素 list.addAll(Collections.nCopies(1000, 1)); // 扩容过程: // 1. 当前容量10,需要1010 // 2. 计算 newCapacity = 10 + (10 >> 1) = 15 // 3. 15 < 1010,所以 newCapacity = minCapacity = 1010 // 直接扩容到1010,而不是15!
性能特点
-
查询快:通过索引直接访问数组元素(时间复杂度 O(1))
-
增删慢:
- 尾部插入快(O(1))
- 中间插入/删除需移动元素(O(n))
4.2.3 使用场景
| 场景 | 说明 |
|---|---|
| 频繁查询 | 适合使用索引快速访问元素(如数据展示) |
| 尾部增删 | 尾部操作效率高(如日志记录) |
| 元素有序存储 | 需要保持插入顺序时使用 |
| 不涉及线程安全 | 单线程环境下优先选择(相比 Vector 性能更好) |
4.2.4 常见问题与注意事项
-
并发修改异常
-
问题:在遍历集合时修改元素会抛出
ConcurrentModificationExceptionArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C")); for (String s : list) { if (s.equals("B")) { list.remove(s); // 抛出异常! } }原因
// 增强for循环的内部实现 Iterator<String> it = list.iterator(); // 获取迭代器 while(it.hasNext()) { String s = it.next(); // 这里会检查modCount! if(s.equals("B")) { list.remove(s); // 直接操作原集合,修改了modCount } } // 下次调用it.next()时,发现modCount变了,抛出异常 -
解决方案:
-
使用迭代器的
remove()方法ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C")); Iterator<String> it = list.iterator(); while(it.hasNext()) { String s = it.next(); if(s.equals("B")) { it.remove(); // 关键:使用迭代器的remove方法 } } System.out.println(list); // [A, C] -
使用
CopyOnWriteArrayList(并发场景)import java.util.concurrent.CopyOnWriteArrayList; List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("A", "B", "C")); for(String s : list) { // 可以安全遍历和删除 if(s.equals("B")) { list.remove(s); // CopyOnWriteArrayList允许这样操作 } } System.out.println(list); // [A, C] -
使用 fori 循环倒序遍历
ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C")); for(int i = list.size() - 1; i >= 0; i--) { if(list.get(i).equals("B")) { list.remove(i); // 从后往前删,不会影响索引 } } // 或者正序但要调整索引 for(int i = 0; i < list.size(); i++) { if(list.get(i).equals("B")) { list.remove(i); i--; // 索引回退 } } -
使用 removeIf()
ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C")); list.removeIf(s -> s.equals("B")); // 一行搞定! System.out.println(list); // [A, C]
-
-
-
优化建议
- 预分配容量:如果已知数据量大小,初始化时指定容量避免频繁扩容
- 避免中间插入:大量中间插入操作应改用
LinkedList
-
与数组的转换
-
ArrayList → Array:
String[] arr = list.toArray(new String[0]); -
Array → ArrayList:
ArrayList<String> list = new ArrayList<>(Arrays.asList(arr));
-
-
线程安全问题
-
默认非线程安全:多线程环境下需同步处理
-
解决方案:
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
-
4.3 LinkedList集合
4.3.1 介绍
定义:LinkedList 是 List 接口的双向链表实现,同时实现了 Deque 接口,可用作队列或双端队列。
核心特性:
- 元素有序(按插入顺序排列)
- 元素可重复
- 无需扩容(动态添加节点)
- 非线程安全(多线程环境下需同步处理)
LinkedList 创建方法
LinkedList<String> list=new LinkedList<>();
LinkedList 独有的方法
| 方法名 | 说明 |
|---|---|
| public void addFirst(E e) | 在该列表的开头插入指定元素 |
| public void addLast(E e) | 将指定元素插入该列表的末尾 |
| public E getFirst() | 返回此列表的第一个元素 |
| public E getLast() | 返回此列表的最后一个元素 |
| public E removeFirst() | 从该列表中删除第一个元素并返回该元素 |
| public E removeLast() | 从该列表中删除最后一个元素并返回该元素 |
4.3.2 底层实现原理
数据结构
- 基于 双向链表 实现(源码中的
Node<E>内部类) - 每个节点包含:
E item:存储元素Node<E> next:指向下一个节点Node<E> prev:指向前一个节点
- 维护
first和last指针,分别指向链表的头和尾
操作原理
- 插入/删除:只需修改相邻节点的引用(时间复杂度 O(1))
- 随机访问:需要从头或尾遍历链表(时间复杂度 O(n))
性能特点
- 增删快:头尾操作效率极高(如实现栈、队列时)
- 查询慢:无法直接通过索引定位元素
4.3.3 使用场景
| 场景 | 说明 |
|---|---|
| 频繁头尾操作 | 实现栈、队列、双端队列的理想选择 |
| 中间插入/删除 | 相比 ArrayList 更高效(无需移动元素) |
| 不确定数据量 | 无需担心扩容问题 |
4.3.4 常见问题与注意事项
-
线程安全问题
默认非线程安全:多线程操作需手动同步
解决方案:List<String> syncList = Collections.synchronizedList(new LinkedList<>()); -
内存开销
每个节点需要存储两个引用(prev/next),内存占用比 ArrayList 高约 3 倍
-
遍历性能
- 避免通过索引遍历(如
list.get(i)),时间复杂度为 O(n^2) - 推荐使用迭代器,时间复杂度为 O(n)
- 避免通过索引遍历(如
五,Set系列集合
介绍
-
Set 系列集合添加的元素是无序,不重复,无索引:
无序:存和取得顺序不一致,即:存入 1,2,3 可能取出 2,3,1
不重复:集合内元素不重复
无索引:没有带索引的方法,不能用普通 for 循环遍历,无法使用索引获取元素
-
Set 集合的实现类:
HashSet:无序,不重复,无索引
LinkedHashSet:有序,不重复,无索引
TreeSet:可排序,不重复,无索引
-
Set 接口注意事项
-
Set 接口是基础 Collection 接口的,故基本方法和 Collection 一致
-
Set 集合可以用 Collection 的三种遍历方式进行遍历
-
Set 集合的 add 方法如果第二次添加了相同的元素结果会返回 false
-
在 Java 中,Set 集合不提供直接的方法来修改元素的内容。Set 是一种不允许重复元素的集合,它的设计目的是用于存储和管理不重复的对象。因此,如果你想修改 Set 集合中的元素内容,你需要先删除旧的元素,然后再添加新的元素。
5.1 HashSet
HashSet 的特点:无序,不可重复,无索引,允许一个 null。
HashSet 底层采用哈希表存储数据,哈希表是一种对于增删改查数据性能都比较好的数据结构
HashSet 底层:
- JDK8之前:数组+链表
- JDK8开始:数组+链表+红黑树
数据存储是无序的:数据存入位置公式:int index=(数组长度-1) & 哈希值
哈希值:
- 根据hashCode方法计算出的int类型的整数
- 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
- 一般情况下会重写hashCode方法,利用对象内部的属性值计算哈希值
哈希值特点:
- 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
- 如果重写了hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
- 在小部分情况下,不同属性值或不同地址值计算出的哈希值也可能一样(哈希碰撞)
HashSet 添加元素的情况

-
扩容时机:当数组内元素个数为:
当前数组长度*默认加载因子即16*0.75=12的时候扩容为原数组的两倍 -
当链表长度 >8 且数组长度 >64,链表就会转化为红黑树

-
注意点:如果 HashSet 存储的自定义对象,必须重写hashCode和equals方法!
重写 hashCode 目的是想根据属性值去计算哈希值
重写 equals 目的是想比较对象内部属性
Object 类中的 hashCode 和 equals 都是通过地址值进行计算和比较的一般不用
-
HashSet 存取顺序不一致:因为是根据哈希值存入的,通过数组 0 索引开始查,每个数据的哈希值不一致,不可能按照顺序存入
-
HashSet 为何不用索引:因为哈希表用上了链表和红黑树,无法定义同一个数组下哪一个链表或红黑树节点为 0 索引,故不用
-
HashSet 利用什么机制去重的:利用 hashCode 得到哈希值从而确定当前元素在数组中的位置,利用 equals 比较对象内部的属性值是否相同。这就是为何自定义对象的时候需要重写方法
-
线程安全问题
-
非线程安全:多线程操作可能导致数据不一致
-
解决方案:
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>()); Set<String> safeSet = ConcurrentHashMap.newKeySet();
-
5.2 LinkedHashSet
介绍
- LinkedHashSet是HashSet的子类,具有全部的HashSet的方法,不需要多记其API只需记住特性即可
- 特性:有序,不重复,无索引,允许一个null
- 性能平衡:
- 查找效率接近
HashSet(平均 O(1)) - 插入/删除效率略低于
HashSet(需维护链表)
- 查找效率接近
- 允许 null 值:可存储一个
null元素 - 非线程安全:多线程环境下需同步处理
- LinkedHashSet会保证数据的存入和取出的顺序一致!
- 原理:底层数据结构依旧是哈希表,只是每个元素又额外多了一个双链表机制记录存储的顺序。

8 索引添加的是第一个元素,0 索引是添加的最后一个元素,两个单独的指针代表头节点和尾节点
当要求数据去重且存取有序的时候才使用 LinkedHashSet。要不然默认使用 HashSet,因为 LinkedHashSet 效率较低
5.3 TreeSet
介绍
-
TreeSet 是 Set 接口的子类,具有全部的 Set 接口的方法,不需要多记其 API 只需记住特性即可
-
特性:
- 元素唯一性:不允许重复元素(依赖
compareTo()或compare()判断重复)- 当
compareTo()或compare()返回 0 时,视为重复元素,拒绝插入 - 不允许NULL因为(
compareTo()或compare()无法处理null)
- 当
- 有序性:元素自动排序(默认升序,可通过
Comparator自定义) - 无索引:不能通过索引访问元素
- 非线程安全:多线程环境下需手动同步
- 元素唯一性:不允许重复元素(依赖
-
TreeSet 底层是基于红黑树的数据结构实现排序的,增删改查的性能都比较好
因为 TreeSet 底层是红黑树,故不需要重写 equals 方法和 hashCode 方法在自定义类中,但是要指定排序规则
-
TreeSet 集合默认的规则
-
对于数值类型,Integer,Double,默认按照从小到大排序
-
对于字符,按照字符在 ASCII 码表中的数字升序排序
-
对于字符串:按照字典序
-
对于自定义的类型,需要手动添加排序规则!
TreeSet 的两种比较方式
-
默认排序 / 自然排序:javabean 类实现
Comparable接口指定比较规则public class Student implements Comparable<Student>{//实现接口 int age; String name; @Override//重写接口中的方法 public int compareTo(Student o) { //指定排序规则 //只看年龄,按照年龄的升序排序 return this.getAge()-o.getAge(); //this表示当前要添加的元素,o表示在已经在红黑树中存在的元素 //返回值: //1.负数:认为要添加的元素是小的,存左边 //2.正数:认为要添加的元素是大的,存右边 //3.0:认为要添加的元素已经存在,舍弃 } } -
比较器排序:创建 TreeSet 对象的时候,传递比较器
Comparator指定规则使用原则:默认使用第一种,如果第一种无法满足需求才使用第二种
例:存入四个字符串 "c","ab",”df“,”qwer",按照长度排序,长度一致按照首字母排序
import java.util.*; public class Main { public static void main(String[] args) { TreeSet<String> ts=new TreeSet<>(new Comparator<String>() { @Override //o1当前要添加的元素,o2已经在红黑树存在的元素 //返回规则一样 public int compare(String o1, String o2) { int i=o1.length()-o2.length(); i= i== 0 ? o1.compareTo(o2):i;//比较长度,如果长度一致使用默认排序规则compareTo,否则以长度为准 return i; } }); //这个Comparator接口是函数式接口,可以用Lambda表达式简化 TreeSet<String> ts=new TreeSet<>((o1,o2)->{ int i=o1.length()-o2.length(); i= i== 0 ? o1.compareTo(o2):i; return i; }); ts.add("c"); ts.add("ab"); ts.add("df"); ts.add("qwer"); System.out.println(ts); } }如果两种方式都存在,方式二优先使用
-
六,Map
6.1 Map简述
双列集合特点:
-
双列集合一次需要存一对数据,分为键 (key) 和值(value)
-
键不能重复,值可以重复
-
键和值是一一对应的,每一个键只能找到自己对于的值
-
键 + 值这个整体,叫键值对或键值对对象,在 Java 中叫Entry 对象
-
Map 集合体系如下图:

Map 系列集合常用 API
-
Map 是双列集合的顶层接口,它的功能是全部双列集合都可以继承使用的
方法名 说明 V put(K key,V value) 添加元素 V remove(Object key) 根据键删除键值对元素 void clear() 移除所有键值对元素 boolean containsKey(Object key) 判断集合是否包含指定的键 boolean containsValue(Object value) 判断集合是否包含指定的值 boolean isEmpty() 判断集合是否为空 int size() 集合的长度,即集合中键值对的个数 -
put 方法的细节:
在添加数据的时候,如果键不存在,那么直接把键值对对象添加到 map 集合中,返回 null
在添加数据的时候,如果键是存在的,那么会把原有的数据覆盖,并把被覆盖的值进行返回
6.2 Map系列集合遍历方式
6.2.1 键找值方式遍历
import java.util.*;
public class Main {
public static void main(String[] args) {
Map<String,String> map=new HashMap<>();
map.put("a","A");
map.put("b","B");
map.put("c","C");
map.put("d","D");
//获取所有的键,把键放入单列集合中
Set<String> keys = map.keySet();//keySet方法就是获取所有的key并返回Set集合
for (String key : keys) {//Set集合可以使用的三种遍历方式,Lambda,增强for,迭代器iterator
String value = map.get(key);//Map系列集合的get方法,通过key获取value
System.out.println(key+"="+value);
}
//可以简化,不需要keys这个变量
for (String key : map.keySet()) {
String value = map.get(key);
System.out.println(key+"="+value);
}
}
}
6.2.2 键值对方式遍历
import java.util.*;
public class Main {
public static void main(String[] args) {
Map<String,String> map=new HashMap<>();
map.put("a","A");
map.put("b","B");
map.put("c","C");
map.put("d","D");
//通过entrySet方法获取键值对对象
//Entry是Map接口中的内部接口
//可以通过外部接口.内部接口方式使用内部接口,也可也通过import java.util.Map.Entry方式导入
//这样就直接Entry<K,V>即可
Set<Map.Entry<String, String>> entries = map.entrySet();
//通过entries这个单列集合获取键值对对象内部的k和v
//因为是set集合,可以使用set集合的三种遍历方式,这里暂时采用Lambda方式,因为最简单
entries.forEach(s-> System.out.println(s.getKey()+"="+s.getValue()));
//getKey是键值对对象的方法,获取key
//getValue是键值对对象的方法,获取value
}
}
6.2.3 Lambda表达式方式遍历
使用 Map 集合中下列方法遍历元素
| 方法名 | 说明 |
|---|---|
| default void forEach(BiConsumer<? suoer K,? super V> action) | 结合Lambda表达式遍历Map集合 |
import java.math.BigDecimal;
import java.util.*;
import java.util.function.BiConsumer;
public class Main {
public static void main(String[] args) {
Map<String,String> map=new HashMap<>();
map.put("a","A");
map.put("b","B");
map.put("c","C");
map.put("d","D");
//这里写内部类形式方便理解
map.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String key, String value) {
System.out.println(key+"="+value);
}
});
//简化为Lambda表达式
map.forEach((key,value)->System.out.println(key+"="+value));
}
}
forEatch 方法的底层:就是用第二种方式进行遍历,依次得到每一个键和值,再调用 accept 方法
6.1 HashMap
-
HashMap 特点:
-
HashMap 是 Map 里面的一个实现类
-
没有额外需要学习的特有方法,直接使用 Map 中的方法即可
-
特点都是由键决定的:无序,不重复,无索引(这里都是关于键的特性,值无要求)
-
HashMap 和 HashSet 底层原理是一模一样的,都是采用哈希表结构
存入的方式和 HashSet 也一样,只不过 HashMap 是根据键进判断的
-
如果键存储的是自定义对象,需要重写 hashCode 和 equals 方法
-
如果值存储的是自定义对象,不需要重写
-
依赖 hashCode 和 equals 方法保证键的唯一
-
-
HashSetvsHashMapHashSet内部直接封装了一个HashMap,元素作为HashMap的键(Key),而值(Value)统一为一个固定占位对象(如PRESENT = new Object())。- 底层均基于**哈希表(数组 + 链表/红黑树)**实现,哈希冲突处理逻辑完全一致。
6.2 LinkedHashMap
- LinkedHashMap特点:
- 由键绝对:有序,不重复,无索引。
- 原理:和LinkedHashSet一致。。。
- 方法和Map方法一样,不需要额外记。。
6.3 TreeMap
-
TreeMap 和 TreeSet 一样都是红黑树结构
-
由键决定:可排序,不重复,无索引
可排序:对键进行排序
-
这玩意和 TreeSet 唯一的区别是多了个 Value 其他用法和 TreeSet 一致,只需看 TreeSet 即可
-
默认按照键的从小到大排序,也可自定义排序规则
排序规则实现方式和 TreeSet 一样
-
在自定义类中实现 Comparable 接口,指定比较规则
-
创建集合的时候传递 Comparator 比较器对象,指定比较规则
-
-
TreeSetvsTreeMap:TreeSet内部封装了一个TreeMap,元素作为键,值同样为固定占位对象。
-
底层均基于 ** 红黑树(自平衡二叉搜索树)** 实现,排序和查找逻辑一致。
七,可变参数
//计算n个数据的和
//jdk5之前写法
int[] arr={1,2,3,4,5,6,7,8,9,10};
int sum=getSum(arr);
int get Sum(int[] arr){
int sum=0;
for(int i:arr){
sum=sum+i
}
return i;
}
//jdk5开始的写法:
//可变参数:方法的形参的个数是可变的
//格式:数据类型...形参名
//int...nums
//举例
int sum=getSum(1,2,3,4,5,6,7,8,9,10);
int get Sum(int...nums){
int sum=0;
for(int i:nums){
sum=sum+i
}
return i;
}
//底层:可变参数就是一个数组,是Java帮助我们创建好的数组,只需把nums当作数组调用即可
可变参数的小细节:
-
在方法的形参中,最多只能定义一个可变参数
int get Sum(int...nums,int...a){//错误写法,无法判断前面参数应该接收多少个参数 } -
在方法当中,如果除了可变参数以外的形参数,那么可变参数必须在最后!
int get Sum(int...nums,int a){//错误写法,无法判断前面参数应该接收多少个参数 } int get Sum(int nums,int...a){//正确写法 }
八,Collections类
-
Collections 是
java.util.Collections的类,是集合的工具类 -
常用方法:
方法名 说明 public static boolean addAll(Collection c,T...elements) 批量添加元素 public static void shuffle(List<?> list) 打乱List集合元素的顺序 public static void sort(List list) 排序 public static void sort(List,Comparator c) 根据指定规则排序 public static int binarySearch(List list,T key) 以二分查找算法查找元素(元素必须有序) public static int fill(List list,T obj) 使用指定元素填充集合 public static void max/min(Collection coll) 根据默认的自然排序获取最大/最小值 public static void swap(List<?> list,int i,int j) 交换指定索引的元素 -
小细节:
假设 list 集合里面为空,啥也没有,用 fill 里面是啥也填充不到的,因为不知道要填充多少。
即要填充的集合里面必须有值才能填充(即将里面值全部替换成填充值 )
例题:
//实现概率抽取集合元素 //随机抽取集合中元素且,男占70%女占30% import java.lang.reflect.Array; import java.util.*; public class Main { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); Collections.addAll(list, 1, 1, 1, 1, 1, 1, 1);//1代表男生占比,6个即百分之60 Collections.addAll(list, 0, 0, 0, 0);//0代表女生占比,4代表百分之40 Collections.shuffle(list);//打乱 Random r = new Random(); int index = r.nextInt(list.size());//这样获取到男生占比60%女生占比40% int nums = list.get(index); System.out.println(nums); ArrayList<String> boy = new ArrayList<>(); ArrayList<String> girl = new ArrayList<>(); Collections.addAll(boy, "男1", "男2", "男3", "男4", "男5", "男6"); Collections.addAll(girl, "女1", "女2", "女3", "女4"); if (nums == 1) { int boyIndex = r.nextInt(boy.size()); System.out.println(boy.get(boyIndex)); } else if (nums == 0) { int girIndex = r.nextInt(girl.size()); System.out.println(girl.get(girIndex)); } } }
九,不可变集合
使用场景:
- 如果某个数据不能被修改,就放入到不可变集合中
- 当集合对象被不信任的库调用的时候,不可变形式是安全的。
简单理解:不想让外界修改集合中的内容
格式
//1.创建不可变的List集合
List<T> list=List.of(Element...元素);
//2.创建不可变的Set集合
Set<T> set=Set.of(Element...元素);
//3.创建不可变的Map集合
Map<K,V> map=Map.of(k1,v1,k2,v2,k3,v3....);
细节:
-
不可变集合一旦创建就无法修改,只能查询
-
Set 类型的不可变集合里面的元素不能重复!
-
Map 类型的集合默认把第一个参数当作 K,第二个参数当作 V 以此类推
-
Map 集合的键不能重复
-
Map 里面的 of 方法有上限,最多传递 20 个参数。
因为其他两个是用可变参数传递的
Map 集合是通过方法重载的方式写了一堆 of 方法
解决方法:传递 Entry 键值对对象即可
//创建一个普通map集合 HashMap<String,String> hm=new HashMap<>(); hm.put("a","A"); hm.put("b","B"); hm.put("c","C"); hm.put("d","D"); //2.利用上面的数据获取一个不可变集合 //获取entry对象 Set<Map.Entry<String,String>> entries=hm.entrySet(); //把entries变成数组 //使用方法 toArray(指定类型) Map.Entry[] arr = entries.toArray(new Map.Entry[0]); //这里的new Map.Entry[0]就是创建一个Map.Entry数组,长度为0 //toArray底层细节:toArray底层会比较集合长度和数组长度大小, //如果集合长度>数组长度,即数据在数组中放不下,此时会根据实际数据个数重新创建数组 //如果集合长度<=数组长度,即数据在数组中放得下,直接使用这个数组 Map map=Map.ofEntries(arr);//将键值对对象传入不可变Map集合 //上述方法太难记了可使用Map集合的copyOf方法构建一个不可变map集合(jdk10才有这个方法) Map map=Map.copyOf(hm);//这个底层就是上述代码