数据结构 - LinkList,ArrayList的基本原理及使用

84 阅读4分钟

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:
- 散布内存块
- 每个节点额外引用
- 动态分配内存

记住:

  1. LinkedList 是双向链表
  2. 适合频繁插入删除
  3. 不适合随机访问
  4. 注意内存占用

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();
    }
}

记住:

  1. ArrayList 基于数组实现
  2. 支持快速随机访问
  3. 扩容成本较高
  4. 适合查询频繁的场景