LinkedList的基本原理
🌟 LinkedList 基本结构
public class LinkedList<E> {
// 双向链表节点
private static class Node<E> {
E item; // 数据
Node<E> next; // 下一个节点
Node<E> prev; // 上一个节点
}
// 链表属性
transient int size = 0;
transient Node<E> first; // 头节点
transient Node<E> last; // 尾节点
}
想象成:
火车车厢连接:
🚂 <-> 🚃 <-> 🚃 <-> 🚃
头 节点 节点 尾
🚂 添加元素过程
// 在尾部添加元素
public boolean add(E e) {
// 创建新节点
Node<E> newNode = new Node<>(last, e, null);
if (last == null) {
// 空链表
first = newNode;
} else {
// 连接新节点
last.next = newNode;
}
last = newNode;
size++;
}
形象理解:
添加新车厢:
1. 原始: 🚂 <-> 🚃 <-> 🚃
2. 新增: 🚂 <-> 🚃 <-> 🚃 <-> 🆕
🔍 查找元素过程
// 查找元素
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
// 找到指定位置的节点
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;
}
}
形象理解:
查找第3个车厢:
🚂 -> 🚃 -> 🎯 -> 🚃
1 2 3 4
🎯 删除元素过程
// 删除节点
public E remove(int index) {
// 1. 找到节点
Node<E> x = node(index);
// 2. 保存数据
E element = x.item;
// 3. 断开连接
Node<E> next = x.next;
Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
}
size--;
return element;
}
形象理解:
删除中间车厢:
1. 原始: 🚂 <-> 🚃 <-> ❌ <-> 🚃
2. 删除: 🚂 <-> 🚃 <-> 🚃
💫 迭代器实现
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
// 向前遍历
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
}
形象理解:
列车检查员走过每节车厢:
🚶♂️
🚂 -> 🚃 -> 🚃 -> 🚃
🔄 与 ArrayList 对比
ArrayList:
📦 📦 📦 📦 📦
连续的数组存储
LinkedList:
🔗 🔗 🔗 🔗 🔗
节点之间互相链接
性能对比:
1. 随机访问:
ArrayList: O(1) ⚡
LinkedList: O(n) 🐌
2. 插入删除:
ArrayList: O(n) 🐌
LinkedList: O(1) ⚡
⚡ 性能优化技巧
class OptimizedUsage {
// 1. 批量操作优化
void batchOperation() {
LinkedList<String> list = new LinkedList<>();
// 添加到末尾更快
list.addLast("item");
// 使用迭代器删除
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
if (condition) {
iter.remove(); // 优于list.remove()
}
}
}
// 2. 位置选择优化
void positionOptimize() {
// 根据索引选择最优遍历方向
if (index < size/2) {
// 从头遍历
fromFirst();
} else {
// 从尾遍历
fromLast();
}
}
}
🎯 实际应用场景
// 1. LRU缓存实现
class LRUCache<K,V> {
private LinkedList<Entry<K,V>> list = new LinkedList<>();
private Map<K, Entry<K,V>> map = new HashMap<>();
public void put(K key, V value) {
// 移除旧值
if (map.containsKey(key)) {
list.remove(map.get(key));
}
// 添加新值到头部
Entry<K,V> entry = new Entry<>(key, value);
list.addFirst(entry);
map.put(key, entry);
}
}
// 2. 撤销重做功能
class UndoRedoManager {
private LinkedList<Command> undoStack = new LinkedList<>();
private LinkedList<Command> redoStack = new LinkedList<>();
public void execute(Command cmd) {
cmd.execute();
undoStack.push(cmd);
redoStack.clear();
}
}
📊 内存分析
内存占用对比:
ArrayList:
- 连续内存块
- 额外空间少
- 扩容成本高
LinkedList:
- 散布内存块
- 每个节点额外引用
- 动态分配内存
记住:
- LinkedList 是双向链表
- 适合频繁插入删除
- 不适合随机访问
- 注意内存占用
ArrayList的基本原理
🌟 ArrayList 基本结构
public class ArrayList<E> {
// 底层数组
transient Object[] elementData;
// 实际元素个数
private int size;
// 默认容量
private static final int DEFAULT_CAPACITY = 10;
}
想象成:
超市货架:
📦📦📦📦📦___ (10个格子,已使用5个)
已用空间 | 预留空间
🚀 扩容机制
// 添加元素时的扩容
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 扩容1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 复制数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
形象理解:
货架扩建过程:
1. 原货架:📦📦📦📦📦 (已满)
2. 搭建新货架:📦📦📦📦📦_____
3. 转移商品:📦📦📦📦📦_____ (1.5倍大小)
⚡ 添加元素
public boolean add(E e) {
// 确保容量足够
ensureCapacityInternal(size + 1);
// 直接放入数组
elementData[size++] = e;
return true;
}
// 指定位置添加
public void add(int index, E element) {
// 检查索引
rangeCheckForAdd(index);
// 确保容量
ensureCapacityInternal(size + 1);
// 移动元素
System.arraycopy(elementData, index,
elementData, index + 1,
size - index);
// 插入新元素
elementData[index] = element;
size++;
}
形象理解:
1. 尾部添加:
📦📦📦📦📦➡️
简单放入末尾
2. 中间插入:
📦📦➡️📦📦📦
需要移动后面的元素
🔍 删除元素
public E remove(int index) {
// 检查索引
rangeCheck(index);
// 记录旧值
E oldValue = elementData(index);
// 计算要移动的元素数量
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1,
elementData, index,
numMoved);
// 清除引用
elementData[--size] = null;
return oldValue;
}
形象理解:
删除中间元素:
1. 原始:📦📦❌📦📦
2. 移动:📦📦📦📦_
3. 结果:减少一个位置
💫 遍历方式
// 1. 普通for循环
for (int i = 0; i < list.size(); i++) {
// 直接索引访问,最快
}
// 2. 增强for循环
for (Element e : list) {
// 底层使用迭代器
}
// 3. 迭代器
Iterator<Element> it = list.iterator();
while (it.hasNext()) {
// 迭代器遍历
}
形象理解:
不同的遍历方式就像:
1. 直接数货架号 📦1️⃣ 📦2️⃣ 📦3️⃣
2. 一个个看过去 📦➡️📦➡️📦
3. 用扫描器扫描 🔍📦 🔍📦 🔍📦
⚡ 性能优化
class ArrayListOptimizer {
// 1. 容量预分配
void optimizeCapacity() {
// 知道大小时预分配
ArrayList<String> list = new ArrayList<>(1000);
// 批量添加时一次性扩容
list.ensureCapacity(expectedSize);
}
// 2. 删除优化
void optimizeRemove() {
// 从后向前删除
for (int i = list.size() - 1; i >= 0; i--) {
if (condition) {
list.remove(i);
}
}
}
}
📊 内存模型
ArrayList内存布局:
┌─────────────────────────┐
│ Object[] elementData │
├─────────────────────────┤
│ [0] 📦 实际元素 │
│ [1] 📦 实际元素 │
│ [2] 📦 实际元素 │
│ [3] _ 预留空间 │
│ [4] _ 预留空间 │
└─────────────────────────┘
🎯 使用场景
// 1. 数据缓存
class DataCache {
private ArrayList<Data> cache;
public DataCache(int capacity) {
// 预分配容量
cache = new ArrayList<>(capacity);
}
}
// 2. 批量操作
class BatchProcessor {
void process(List<Task> tasks) {
// 随机访问效率高
for (int i = 0; i < tasks.size(); i++) {
processTask(tasks.get(i));
}
}
}
⚠️ 注意事项
class ArrayListPrecautions {
// 1. 避免频繁扩容
void avoidFrequentGrow() {
// 预估大小
ArrayList<Data> list = new ArrayList<>(estimateSize());
}
// 2. 及时清理
void cleanup() {
// 清理不用的元素
list.clear();
// 释放多余空间
list.trimToSize();
}
}
记住:
- ArrayList 基于数组实现
- 支持快速随机访问
- 扩容成本较高
- 适合查询频繁的场景