本文纲要
List集合概述与基本使用
1.1List接口特点
1.2 项目结构
1.3 基本遍历方式List特有方法
2.1add(int index, E element)
2.2remove(int index)与remove(Object o)
2.3set(int index, E element)
2.4get(int index)- 常见数据结构:栈和队列
3.1 栈(Stack)
3.2 队列(Queue) - 数据结构:数组与链表
4.1 数组
4.2 链表 ArrayList底层原理与源码分析
5.1ArrayList底层数据结构
5.2 空参构造及首次添加元素
5.3 扩容机制
5.4 查询与遍历LinkedList基本使用LinkedList特有方法LinkedList源码解析
List集合概述与基本使用
1 )List接口特点
List是单列集合Collection的子接口,代表一个有序、有索引、可重复的元素序列。
- 有序:指的是存取顺序一致,即按什么顺序存入,就按什么顺序取出。
- 有索引:可以通过整数索引(从0开始)精确操作每一个元素,包括获取、修改、删除。
- 可重复:允许存储重复的元素。
常见的List实现类包括ArrayList和LinkedList,它们都实现了List接口的全部方法。由于List继承了Collection,所以Collection中的通用方法(如add、remove、contains等)在List中同样适用。
2 ) 项目结构
本文涉及的示例代码位于如下包结构中,后续讲解将围绕这些类展开:
mycollection/
└── src/
└── com/
└── wb/
└── mylistdemo1/
├── MyListDemo1.java
├── MyListDemo2.java
├── MyLinkedListDemo3.java
└── MyLinkedListDemo4.java
3 ) 基本遍历方式
下面通过 MyListDemo1演示List结合泛型的创建、添加元素以及两种常见的遍历方式:迭代器和增强for循环。
// MyListDemo1.java
package com.wb.mylistdemo1;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class MyListDemo1 {
public static void main(String[] args) {
// 创建List集合,使用多态,实际是ArrayList
List<String> list = new ArrayList<>();
// 添加元素
list.add("a");
list.add("b");
list.add("c");
list.add("d");
// 方式一:迭代器遍历
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
System.out.println("---------------------");
// 方式二:增强for遍历
for (String s : list) {
System.out.println(s);
}
}
}
输出结果均为 a b c d,顺序与插入时一致。
List特有方法
List 接口中定义了一系列与索引相关的方法,这是Set集合所不具备的。包括:
void add(int index, E element)— 在指定位置插入元素E remove(int index)— 删除指定索引的元素并返回被删除的元素E set(int index, E element)— 修改指定索引的元素,返回被修改的元素E get(int index)— 返回指定索引处的元素
此外,List中存在两个重载的remove方法,需要注意区分:
boolean remove(Object o)— 删除指定的元素,返回是否删除成功E remove(int index)— 删除指定索引的元素,返回被删除的元素
下面的示例将它们拆分为独立的方法进行展示。
1 ) add(int index, E element)
// 方法 extract from MyListDemo2
private static void method1(List<String> list) {
// 在索引0处插入"qqq"
list.add(0, "qqq");
System.out.println(list);
}
注意事项:原来该位置及其后的元素会依次后移一个索引。
2 ) remove(int index) 与 remove(Object o)
private static void method2(List<String> list) {
// 按索引删除
String removed = list.remove(0); // 删除0索引的元素,返回被删除的元素
System.out.println(removed);
System.out.println(list);
// 按元素对象删除
boolean success = list.remove("bbb"); // 删除指定的元素"bbb",返回是否成功
System.out.println("删除bbb成功:" + success);
System.out.println(list);
}
两个remove方法的参数类型不同,调用时根据参数类型自动匹配。当传入int时调用索引删除,传入Object时调用元素删除。
3 ) set(int index, E element)
private static void method3(List<String> list) {
// 将索引0处的元素修改为"qqq"
String oldValue = list.set(0, "qqq");
System.out.println("被替换的元素:" + oldValue);
System.out.println(list);
}
注意:被替换的元素在集合中不再存在,返回的是旧值。
4 ) get(int index)
private static void method4(List<String> list) {
// 获取0索引的元素
String s = list.get(0);
System.out.println(s);
}
get方法常与普通for循环结合,遍历整个集合:
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
整合后的MyListDemo2完整代码:
package com.wb.mylistdemo1;
import java.util.ArrayList;
import java.util.List;
public class MyListDemo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
// 逐一演示各个方法(取消注释即可运行对应方法)
method1(list);
method2(list);
method3(list);
method4(list);
}
private static void method1(List<String> list) {
System.out.println("--- add(index, element) ---");
list.add(0, "qqq");
System.out.println(list);
}
private static void method2(List<String> list) {
System.out.println("--- remove(index) & remove(object) ---");
// 按索引删除
String removedByIndex = list.remove(0);
System.out.println("删除索引0的元素: " + removedByIndex);
System.out.println(list);
// 按对象删除
boolean removedByObject = list.remove("bbb");
System.out.println("删除元素bbb是否成功: " + removedByObject);
System.out.println(list);
}
private static void method3(List<String> list) {
System.out.println("--- set(index, element) ---");
String old = list.set(0, "qqq");
System.out.println("被替换的元素: " + old);
System.out.println(list);
}
private static void method4(List<String> list) {
System.out.println("--- get(index) ---");
String first = list.get(0);
System.out.println(first);
}
}
常见数据结构:栈和队列
数据结构是计算机存储和组织数据的方式,不同的数据结构直接影响程序的运行和存储效率。
下面介绍两种经典模型:栈与队列。
1 ) 栈(Stack)
栈是一种先进后出(FILO,First In Last Out)的结构。数据从一端(栈顶)进入,称为压栈/进栈,也从同一端出去,称为弹栈/出栈。
类比:手枪弹夹,先压入的子弹最后打出。
2 ) 队列(Queue)
队列是一种先进先出(FIFO,First In First Out)的结构。数据从后端进入(入队列),从前端离开(出队列)。
类比:排队购票,后来的人排在队尾,队首的人先完成离开。
数据结构:数组与链表
1 ) 数组
数组在内存中是一块连续的空间,通过基地址加索引可以快速定位任意元素,因此查询速度快。但增删元素时需要移动大量数据,效率较低。
- 删除元素:删除位置后的所有元素依次前移
- 插入元素:插入位置后的所有元素依次后移
因此,数组是一种 查询快、增删慢 的模型。
2 ) 链表
链表由一个个节点(Node)组成,每个节点独立存储,通过记录相邻节点的地址值形成链状结构。
- 单向链表:每个节点记录自己的值和下一个节点的地址。
- 双向链表:每个节点记录自己的值、前一个节点的地址、下一个节点的地址。既能从前向后查找,也能从后向前查找,查询效率更高。
链表在增删时只需修改相邻节点的指向,无需移动大量数据,因此 增删快;但查询时必须从头(或尾)开始遍历,因此 查询慢(相对数组)。
ArrayList底层原理与源码分析
1 ) ArrayList底层数据结构
ArrayList底层是一个动态数组,名为 elementData,类型为 Object[]。其特点为查询快、增删慢。同时维护一个 size 变量,表示当前元素个数,也指向下一次添加操作的索引位置。
2 ) 空参构造及首次添加元素
使用空参构造创建ArrayList时,底层会创建一个长度为0的数组:
// 源码片段(简化理解)
private static final Object[] DEFAULTCAPACITYEMPTYELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITYEMPTYELEMENTDATA;
}
当第一次调用 add(E e) 时,会触发扩容,创建一个默认容量为 10 的新数组,并将元素存入 size 指向的位置,随后 size 自增为1
3 ) 扩容机制
当数组已满(size == elementData.length)时,继续添加元素会触发扩容,流程如下:
- 计算新容量:
newCapacity = oldCapacity + (oldCapacity >> 1),即扩容至原来的1.5倍。 - 使用
Arrays.copyOf将原数组的元素拷贝到新数组中。 - 在
size位置插入新元素,size自增。
4 )查询与遍历
get(int index) 方法直接返回 elementData[index],前提是检查 index 是否小于 size,否则抛出异常。size() 方法返回 size 变量的值,因此普通for循环遍历时直接从0遍历到 size-1即可。
LinkedList基本使用
LinkedList 底层是双向链表,实现了List和Deque接口。它的特点是增删快、查询慢(与ArrayList相反)。由于实现了List接口,ArrayList 中使用的遍历方法在这里完全通用。
// MyLinkedListDemo3.java
package com.wb.mylistdemo1;
import java.util.Iterator;
import java.util.LinkedList;
public class MyLinkedListDemo3 {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
// 普通for遍历
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("-------------------------");
// 迭代器遍历
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
System.out.println("--------------------------");
// 增强for遍历
for (String s : list) {
System.out.println(s);
}
}
}
LinkedList特有方法
LinkedList 除实现List接口的方法外,还提供了大量针对头尾操作的特有方法:
| 方法 | 描述 |
|---|---|
| addFirst(E e) | 在列表开头插入指定元素 |
| addLast(E e) | 在列表末尾追加指定元素 |
| getFirst() | 返回此列表的第一个元素 |
| getLast() | 返回此列表的最后一个元素 |
| removeFirst() | 删除并返回第一个元素 |
| removeLast() | 删除并返回最后一个元素 |
示例代码:
// MyLinkedListDemo4.java
package com.wb.mylistdemo1;
import java.util.LinkedList;
public class MyLinkedListDemo4 {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
// 分别演示四组方法,取消注释即可运行
// method1(list); // addFirst
// method2(list); // addLast
// method3(list); // getFirst & getLast
// method4(list); // removeFirst & removeLast
}
private static void method1(LinkedList<String> list) {
// 在开头插入元素
list.addFirst("qqq");
System.out.println(list); // [qqq, aaa, bbb, ccc]
}
private static void method2(LinkedList<String> list) {
// 在末尾追加元素
list.addLast("www");
System.out.println(list); // [aaa, bbb, ccc, www]
}
private static void method3(LinkedList<String> list) {
// 获取第一个和最后一个元素
String first = list.getFirst();
String last = list.getLast();
System.out.println(first); // aaa
System.out.println(last); // ccc
}
private static void method4(LinkedList<String> list) {
// 移除第一个和最后一个元素
String first = list.removeFirst();
String last = list.removeLast();
System.out.println(first); // aaa
System.out.println(last); // ccc
System.out.println(list); // [bbb]
}
}
注意:getFirst / getLast 在集合为空时会抛出 NoSuchElementException;而 removeFirst / removeLast 同样如此。实际开发中可使用 pollFirst / pollLast 方法,它们在集合为空时返回 null 而非抛异常。
LinkedList源码解析
LinkedList 底层基于双向链表实现,由一个个 Node(节点)对象连接而成。每个节点包含三个部分:
item:存储本节点的数据next:指向下一个节点的引用(地址值)prev:指向前一个节点的引用(地址值)
LinkedList 本身维护了两个核心成员变量:
Node first:指向链表的头节点Node last:指向链表的尾节点
1 ) 空参构造
// LinkedList 内部
transient Node<E> first;
transient Node<E> last;
public LinkedList() {
// 没有任何赋值,first 和 last 均为 null
}
此时,链表为空,first == null 且 last == null
2 ) 添加元素(add方法)
调用 add(E e) 默认将元素追加到链表末尾,内部实际调用 linkLast(e)。下面通过添加 "aaa"、"bbb"、"ccc" 三个元素的过程来剖析源码。
添加第一个元素 “aaa”
- 当前
last == null,l = null - 创建新节点
newNode = new Node<>(l, "aaa", null),此时prev = null,item = "aaa",next = null,
last = newNode,尾指针指向新节点 - 判断
l == null成立,因此first = newNode,头指针也指向该节点
此时链表状态:
添加第二个元素 “bbb”
- 当前
last指向"aaa"节点,l = "aaa"节点 - 创建新节点
newNode = new Node<>(l, "bbb", null),prev指向"aaa"节点
last = newNode,尾指针后移 l != null,执行l.next = newNode,将原来尾节点的next域指向新节点
此时链表状态:
添加第三个元素 “ccc”
- 当前
last指向"bbb"节点,l = "bbb"节点 - 创建新节点
newNode = new Node<>(l, "ccc", null) last = newNodel.next = newNode
最终链表结构:
核心源码片段(linkLast):
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode; // 首次添加,头结点也指向新节点
else
l.next = newNode; // 将原尾节点的next指向新节点
size++;
}
3 ) 获取元素(get方法)
get(int index) 方法根据索引查找元素,内部调用 node(int index)。
node(int index) 方法利用二分思维,判断索引离头还是尾更近,以提升效率:
- 如果
index < (size >> 1),从first开始向后遍历 - 否则从
last开始向前遍历
Node<E> node(int index) {
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
例如查询索引为2的元素(在长度为5的链表中),因为 2 < (5 >> 1) (即2.5)成立,会选择从 first 向后遍历3步。若查询索引为3,则从 last 向前遍历。
4 ) 删除元素(remove方法)
LinkedList 既支持按索引删除,也支持按元素删除,还提供了 removeFirst() / removeLast() 等特有方法。删除的核心操作是修改前后节点的指针,并将当前节点的 item、prev、next 置 null 以便GC回收。
以删除第一个元素为例:
- 获取
first节点f - 获取下一个节点
next = f.next - 将
first指向next - 若
next == null,说明链表已空,last也置null;否则next.prev = null size--并返回被删除的item
private E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
return element;
}
5 ) 对比 ArrayList
ArrayList:底层数组,查询快(O(1)),增删慢(需要移动元素)
LinkedList:底层双向链表,增删快(只需修改指针),查询慢(需要遍历)
开发者应根据实际场景选择合适的数据结构。
总结
至此,关于 Java 集合中 List 接口及其典型实现类的介绍已全部完成。
通过理解数据结构(数组、链表、栈、队列)以及阅读底层源码,能够更好地掌握集合的特性,为后续高级开发打下扎实的基础。

1260

被折叠的 条评论
为什么被折叠?



