java学习笔记——集合
一、集合概述
1.1 为什么需要集合?
数组的局限性:
长度固定,不能动态改变
只能存储同一种类型的数据
增删操作不方便
集合的优势:
长度可变,自动扩容
提供丰富的操作方法(增删改查、排序等)
支持多种数据结构(链表、树、哈希表等)
1.2 集合的分类
单列集合(Collection):一次存一个元素
双列集合(Map):一次存一对元素(键值对)
二、Collection接口
Collection 常用方法
import java.util.ArrayList; import java.util.Collection; Collection<String> coll = new ArrayList<>(); // 1. 添加元素 coll.add("张三"); coll.add("李四"); coll.add("王五"); System.out.println(coll); // [张三, 李四, 王五] // 2. 删除元素 coll.remove("李四"); // 删除指定元素 System.out.println(coll); // [张三, 王五] // 3. 判断 System.out.println(coll.contains("张三")); // true System.out.println(coll.isEmpty()); // false // 4. 获取长度 System.out.println(coll.size()); // 2 // 5. 清空 coll.clear(); System.out.println(coll.isEmpty()); // true // 6. 转数组 coll.add("A"); coll.add("B"); Object[] arr = coll.toArray(); String[] strArr = coll.toArray(new String[0]);三、迭代器
3.1定义
迭代器是遍历集合的通用工具,不依赖索引。
3.2 基本使用
import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; Collection<String> coll = new ArrayList<>(); coll.add("A"); coll.add("B"); coll.add("C"); // 1. 获取迭代器 Iterator<String> it = coll.iterator(); // 2. 遍历 while (it.hasNext()) { // 判断是否有下一个元素 String s = it.next(); // 获取下一个元素 System.out.println(s); }3.3 迭代过程原理
3.4 迭代中删除元素
// 使用迭代器的 remove() 方法,不能用集合的 remove() Iterator<String> it = coll.iterator(); while (it.hasNext()) { String s = it.next(); if ("B".equals(s)) { it.remove(); // 正确!用迭代器的remove // coll.remove("B"); // 错误!会抛出ConcurrentModificationException } }3.5 迭代器源码分析
// ArrayList 内部类 Itr 简化版 private class Itr implements Iterator<E> { int cursor; // 下一个要返回元素的索引 int expectedModCount = modCount; // 期望的修改次数 public boolean hasNext() { return cursor != size; // cursor不等于size说明还有元素 } public E next() { checkForComodification(); // 检查并发修改 int i = cursor; E element = elementData[i]; cursor = i + 1; return element; } // 检查是否在迭代中使用了集合的增删方法 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }ps:
为什么集合的remove会报错?
集合有个 modCount 记录修改次数
迭代器记录 expectedModCount
集合的remove会修改 modCount,导致两者不一致 → 抛异常
迭代器的remove会同步更新两者,所以安全
四、数据结构基础
4.1 常见数据结构
4.2 数组 vs 链表
数组:
链表:
五、ArrayList集合
5.1 基本使用
import java.util.ArrayList; ArrayList<String> list = new ArrayList<>(); // 增 list.add("张三"); list.add("李四"); list.add(1, "王五"); // 在索引1处插入 System.out.println(list); // [张三, 王五, 李四] // 删 list.remove(1); // 按索引删除 list.remove("李四"); // 按元素删除 // 改 list.set(0, "张三丰"); // 修改指定索引的元素 // 查 String s = list.get(0); // 按索引查询 int index = list.indexOf("张三丰"); // 查找元素索引 boolean has = list.contains("张三丰"); // 是否包含5.2 遍历方式
ArrayList<String> list = new ArrayList<>(); list.add("A"); list.add("B"); list.add("C"); // 方式1:普通for循环 for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } // 方式2:增强for循环 for (String s : list) { System.out.println(s); } // 方式3:迭代器 Iterator<String> it = list.iterator(); while (it.hasNext()) { System.out.println(it.next()); } // 方式4:forEach(JDK8+) list.forEach(s -> System.out.println(s));5.3 ArrayList源码分析
// ArrayList 底层是 Object[] 数组 public class ArrayList<E> { private Object[] elementData; // 存储元素的数组 private int size; // 实际元素个数 // 空参构造:初始为空数组 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // {} } // add 方法 public boolean add(E e) { ensureCapacityInternal(size + 1); // 检查容量,不够就扩容 elementData[size++] = e; // 添加元素,size+1 return true; } // 扩容机制 private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容 elementData = Arrays.copyOf(elementData, newCapacity); } }核心要点:
底层结构:Object[] 数组
初始容量:JDK7+ 空参构造初始为空数组,第一次add时扩容为10
扩容机制:每次扩容为原来的 1.5倍
查询快O(1):通过索引直接定位
增删慢O(n):需要移动元素
六、LinkedList集合
6.1 基本使用
import java.util.LinkedList; LinkedList<String> list = new LinkedList<>(); // 基本操作同ArrayList list.add("A"); list.add("B"); list.addFirst("开头"); // 头部添加(LinkedList特有) list.addLast("结尾"); // 尾部添加(LinkedList特有) // 获取 String first = list.getFirst(); String last = list.getLast(); // 删除 list.removeFirst(); list.removeLast(); // 模拟栈和队列 list.push("入栈"); // 压栈 String pop = list.pop(); // 弹栈 list.offer("入队"); // 入队 String poll = list.poll(); // 出队6.2 LinkedList特有方法
6.3 LinkedList源码分析
// LinkedList 底层是双向链表 public class LinkedList<E> { private Node<E> first; // 头节点 private Node<E> last; // 尾节点 private int size; // 元素个数 // 内部类:链表节点 private static class Node<E> { E item; // 存储的数据 Node<E> next; // 下一个节点 Node<E> prev; // 上一个节点 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } // add方法(尾部添加) public boolean add(E e) { linkLast(e); return true; } void linkLast(E e) { Node<E> l = last; Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; } }核心要点:
底层结构:双向链表
查询慢O(n):需要从头/尾遍历
增删快O(1):只需修改前后节点的指针
实现了 Deque 接口,可以当栈或队列使用
6.4 ArrayList vs LinkedList
七、增强for循环
7.1 语法
for (元素类型 变量名 : 数组或集合) { // 使用变量名 }7.2 基本使用
// 遍历数组 int[] arr = {1, 2, 3, 4, 5}; for (int num : arr) { System.out.println(num); } // 遍历集合 ArrayList<String> list = new ArrayList<>(); list.add("A"); list.add("B"); for (String s : list) { System.out.println(s); }7.3 注意事项
// 增强for不能修改原数组/集合 int[] arr = {1, 2, 3}; for (int num : arr) { num = 100; // 只改了临时变量num,不影响arr } System.out.println(arr[0]); // 1(没变) // 要修改元素,用普通for for (int i = 0; i < arr.length; i++) { arr[i] = 100; // 直接修改数组元素 }7.4 增强for底层原理
增强for遍历集合时,底层是迭代器:
// 增强for for (String s : list) { System.out.println(s); } // 等价于 Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); System.out.println(s); }