数据结构 - Deque及ArrayDeque

241 阅读9分钟

Deque的基本内容

一、Deque的基本概念 🎭

graph LR
    A[头部操作] --> B[Deque]
    C[尾部操作] --> B
    B --> D[双向链表实现]
    B --> E[数组实现]
    
 

二、基本实现 💻

/**
 * Deque的基本实现(以双向链表为例)
 */
class SimpleDeque<T> {
    // 节点定义
    private class Node<T>(
        var value: T,
        var prev: Node<T>? = null,
        var next: Node<T>? = null
    )
    
    private var head: Node<T>? = null
    private var tail: Node<T>? = null
    private var size = 0
    
    /**
     * 1. 头部操作
     */
    fun addFirst(item: T) {
        val newNode = Node(item)
        if (isEmpty()) {
            head = newNode
            tail = newNode
        } else {
            newNode.next = head
            head?.prev = newNode
            head = newNode
        }
        size++
    }
    
    fun removeFirst(): T? {
        if (isEmpty()) return null
        val value = head?.value
        head = head?.next
        head?.prev = null
        size--
        if (isEmpty()) tail = null
        return value
    }
    
    /**
     * 2. 尾部操作
     */
    fun addLast(item: T) {
        val newNode = Node(item)
        if (isEmpty()) {
            head = newNode
            tail = newNode
        } else {
            newNode.prev = tail
            tail?.next = newNode
            tail = newNode
        }
        size++
    }
    
    fun removeLast(): T? {
        if (isEmpty()) return null
        val value = tail?.value
        tail = tail?.prev
        tail?.next = null
        size--
        if (isEmpty()) head = null
        return value
    }
    
    fun isEmpty() = size == 0
}

三、生动示例:餐厅排队系统 🏪

/**
 * 餐厅排队系统
 */
class RestaurantQueue {
    private val customerQueue = ArrayDeque<Customer>()
    
    /**
     * 1. VIP客人(从队首加入)
     */
    fun addVipCustomer(customer: Customer) {
        customerQueue.addFirst(customer)
        println("VIP客人${customer.name}从队首加入")
    }
    
    /**
     * 2. 普通客人(从队尾加入)
     */
    fun addNormalCustomer(customer: Customer) {
        customerQueue.addLast(customer)
        println("普通客人${customer.name}从队尾加入")
    }
    
    /**
     * 3. 紧急离开(从队首离开)
     */
    fun leaveFromFront(): Customer? {
        return customerQueue.pollFirst()?.also {
            println("客人${it.name}从队首离开")
        }
    }
    
    /**
     * 4. 正常离开(从队尾离开)
     */
    fun leaveFromEnd(): Customer? {
        return customerQueue.pollLast()?.also {
            println("客人${it.name}从队尾离开")
        }
    }
}

四、实际应用场景 🌟

/**
 * 1. 浏览器历史记录
 */
class BrowserHistory {
    private val history = ArrayDeque<String>()
    
    // 添加新页面
    fun visitPage(url: String) {
        history.addLast(url)
    }
    
    // 后退
    fun goBack(): String? {
        return history.pollLast()
    }
    
    // 前进
    fun goForward(): String? {
        return history.peekFirst()
    }
}

/**
 * 2. 撤销重做系统
 */
class UndoRedoSystem {
    private val undoStack = ArrayDeque<Action>()
    private val redoStack = ArrayDeque<Action>()
    
    // 执行操作
    fun doAction(action: Action) {
        action.execute()
        undoStack.addLast(action)
        redoStack.clear()
    }
    
    // 撤销
    fun undo() {
        undoStack.pollLast()?.let { action ->
            action.undo()
            redoStack.addLast(action)
        }
    }
    
    // 重做
    fun redo() {
        redoStack.pollLast()?.let { action ->
            action.execute()
            undoStack.addLast(action)
        }
    }
}

五、性能特点 ⚡

/**
 * Deque性能分析
 */
class DequePerformance {
    /**
     * 1. 时间复杂度
     */
    fun timeComplexity() = """
        双向链表实现:
        - 头部操作:O(1)
        - 尾部操作:O(1)
        - 随机访问:O(n)
        
        数组实现:
        - 头部操作:O(1)(平均)
        - 尾部操作:O(1)(平均)
        - 随机访问:O(1)
        - 扩容操作:O(n)
    """.trimIndent()
    
    /**
     * 2. 空间复杂度
     */
    fun spaceComplexity() = """
        双向链表实现:
        - 每个元素需要额外的前后指针空间
        - 空间利用率相对较低
        
        数组实现:
        - 连续内存空间
        - 可能存在空间浪费
        - 需要预分配空间
    """.trimIndent()
}

六、使用建议 💡

/**
 * 使用建议
 */
class DequeUsageTips {
    fun tips() = listOf(
        // 1. 选择实现
        "需要频繁随机访问时选择ArrayDeque",
        "内存敏感时选择LinkedList实现",
        
        // 2. 容量考虑
        "预估容量大小,避免频繁扩容",
        "注意内存使用限制",
        
        // 3. 操作建议
        "优先使用专用方法而不是通用List方法",
        "注意空队列的处理",
        "考虑线程安全需求"
    )
}

七、常见误区 ⚠️

/**
 * 常见误区
 */
class CommonMistakes {
    fun mistakes() = """
    1. 误区一:认为Deque就是队列
       - Deque是双端队列,两端都可以操作
       - 可以用作栈或队列
    
    2. 误区二:忽视实现差异
       - 不同实现性能特点不同
       - 需要根据场景选择
    
    3. 误区三:过度使用
       - 简单场景可能Queue或Stack更合适
       - 要根据实际需求选择
    """.trimIndent()
}

总结一下:

  1. Deque特点:

    • 双端操作
    • 灵活性强
    • 实现方式多样
  2. 主要用途:

    • 双向队列
    • 栈结构
    • 滑动窗口
  3. 使用建议:

    • 根据场景选择实现
    • 注意性能特点
    • 合理使用API

ArrayDeque的底层实现

让我来解释 ArrayDeque 的底层实现原理:

// ArrayDeque 的基本结构
public class ArrayDeque<E> {
    // 存储元素的数组,长度必须是 2 的幂
    transient Object[] elements;
    
    // 队列头部元素的索引
    transient int head;
    
    // 队列尾部下一个元素的索引
    transient int tail;
}

🎯 核心特点

  1. 循环数组实现
假设数组大小为8[_, _, A, B, C, D, _, _]
       ↑        ↑
      head    tail

数组索引:0 1 2 3 4 5 6 7
实际元素:_ _ A B C D _ _
  1. 添加元素示例
// 初始状态
[_, _, A, B, C, D, _, _]
       ↑        ↑
      head    tail

// 添加元素 E
[_, _, A, B, C, D, E, _]
       ↑           ↑
      head       tail

// 添加元素 F
[_, _, A, B, C, D, E, F]
       ↑              ↑
      head          tail

// 继续添加 G,数组前面还有空间
[G, _, A, B, C, D, E, F]
       ↑              ↑
      head          tail
  1. 删除元素示例
// 初始状态
[_, _, A, B, C, D, _, _]
       ↑        ↑
      head    tail

// 删除头部元素 A
[_, _, _, B, C, D, _, _]
          ↑     ↑
         head  tail

🔍 关键实现细节

  1. 容量必须是2的幂
// 这样可以用位运算代替取模运算
index & (elements.length - 1) // 等价于 index % elements.length
  1. 扩容机制
// 当数组满时,容量翻倍
int newCapacity = elements.length << 1; // 左移1位,相当于乘2
  1. 主要操作
// 添加到队尾
public void addLast(E e) {
    elements[tail] = e;
    // 环形数组,如果到末尾需要回到开头
    tail = (tail + 1) & (elements.length - 1);
}

// 添加到队首
public void addFirst(E e) {
    head = (head - 1) & (elements.length - 1);
    elements[head] = e;
}

// 从队首删除
public E pollFirst() {
    E result = elements[head];
    elements[head] = null;
    head = (head + 1) & (elements.length - 1);
    return result;
}

🎬 生活类比: 想象一个圆形自助餐传送带:

  • 传送带是圆的(循环数组)
  • 有进餐口(head)和出餐口(tail)
  • 可以从两端放餐点或取餐点
  • 当餐点太多时,换更大的传送带(扩容)

📝 优点

  1. 双端操作都是 O(1) 时间复杂度
  2. 自动扩容
  3. 比 LinkedList 更节省内存
  4. 访问效率高(连续内存)

🚫 局限性

  1. 不支持 null 元素
  2. 非线程安全
  3. 容量必须是 2 的幂

这就是 ArrayDeque 的核心实现原理。它通过巧妙的循环数组设计,实现了高效的双端队列操作。

详细操作

ArrayDeque 的完整核心方法实现:

public class ArrayDeque<E> {
    // 存储元素的数组
    transient Object[] elements;
    // 头部索引
    transient int head;
    // 尾部索引
    transient int tail;
    // 最小初始容量
    private static final int MIN_INITIAL_CAPACITY = 8;

    // 构造方法
    public ArrayDeque() {
        elements = new Object[16];  // 默认容量16
    }

    // 添加元素到队尾
    public void addLast(E e) {
        if (e == null)
            throw new NullPointerException();
        elements[tail] = e;
        if ((tail = (tail + 1) & (elements.length - 1)) == head)
            doubleCapacity();  // 如果队列满了就扩容
    }

    // 添加元素到队首
    public void addFirst(E e) {
        if (e == null)
            throw new NullPointerException();
        head = (head - 1) & (elements.length - 1);
        elements[head] = e;
        if (head == tail)
            doubleCapacity();
    }

    // 从队首删除并返回元素
    public E pollFirst() {
        int h = head;
        @SuppressWarnings("unchecked")
        E result = (E) elements[h];
        if (result == null)  // 队列为空
            return null;
        elements[h] = null;  // 清除引用
        head = (h + 1) & (elements.length - 1);
        return result;
    }

    // 从队尾删除并返回元素
    public E pollLast() {
        int t = (tail - 1) & (elements.length - 1);
        @SuppressWarnings("unchecked")
        E result = (E) elements[t];
        if (result == null)  // 队列为空
            return null;
        elements[t] = null;
        tail = t;
        return result;
    }

    // 获取队首元素但不删除
    public E peekFirst() {
        @SuppressWarnings("unchecked")
        E result = (E) elements[head];
        return result;
    }

    // 获取队尾元素但不删除
    public E peekLast() {
        @SuppressWarnings("unchecked")
        E result = (E) elements[(tail - 1) & (elements.length - 1)];
        return result;
    }

    // 判断是否为空
    public boolean isEmpty() {
        return head == tail;
    }

    // 获取元素个数
    public int size() {
        return (tail - head) & (elements.length - 1);
    }

    // 清空队列
    public void clear() {
        int h = head;
        int t = tail;
        if (h != t) {
            head = tail = 0;
            int i = h;
            int mask = elements.length - 1;
            do {
                elements[i] = null;
                i = (i + 1) & mask;
            } while (i != t);
        }
    }

    // 扩容方法
    private void doubleCapacity() {
        int p = head;
        int n = elements.length;
        int r = n - p; // head右边的元素个数
        int newCapacity = n << 1; // 新容量为原来的2倍
        if (newCapacity < 0)
            throw new IllegalStateException("Sorry, deque too big");
        
        Object[] a = new Object[newCapacity];
        // 复制head右边的元素
        System.arraycopy(elements, p, a, 0, r);
        // 复制head左边的元素
        System.arraycopy(elements, 0, a, r, p);
        elements = a;
        head = 0;
        tail = n;
    }

    // 迭代器实现
    public Iterator<E> iterator() {
        return new DeqIterator();
    }

    private class DeqIterator implements Iterator<E> {
        private int cursor = head;
        private int fence = tail;
        private int lastRet = -1;

        public boolean hasNext() {
            return cursor != fence;
        }

        public E next() {
            if (cursor == fence)
                throw new NoSuchElementException();
            @SuppressWarnings("unchecked")
            E result = (E) elements[cursor];
            lastRet = cursor;
            cursor = (cursor + 1) & (elements.length - 1);
            return result;
        }
    }
}

🔍 主要方法分类

  1. 基本操作

    • addFirst/addLast:两端添加元素
    • pollFirst/pollLast:两端移除元素
    • peekFirst/peekLast:查看两端元素
  2. 状态查询

    • isEmpty:是否为空
    • size:元素个数
    • clear:清空队列
  3. 内部实现

    • doubleCapacity:扩容
    • iterator:迭代器实现

🎯 关键实现特点

  1. 循环数组
[D, E, F, A, B, C]
       ↑     ↑
      tail  head
  1. 位运算优化
// 使用位运算代替取模
index & (elements.length - 1)  // 等价于 index % elements.length
  1. 自动扩容

    • 当 head 追上 tail 时进行扩容
    • 新容量为原来的 2 倍
  2. null 处理

    • 不允许存储 null 元素
    • 移除元素后将位置设为 null 避免内存泄漏

这样的实现保证了:

  • 两端操作都是 O(1) 时间复杂度
  • 内存使用效率高
  • 自动扩容机制
  • 支持迭代操作

Deque 在 Android 和高并发中的应用

一、Android 中的应用 📱

/**
 * 1. 事件总线实现
 */
class EventBus {
    private val eventQueue = ConcurrentLinkedDeque<Event>()
    
    // 发送事件
    fun postEvent(event: Event) {
        eventQueue.offerLast(event)
        processEvents()
    }
    
    // 处理事件
    private fun processEvents() {
        while (!eventQueue.isEmpty()) {
            eventQueue.pollFirst()?.let { event ->
                dispatchEvent(event)
            }
        }
    }
}

/**
 * 2. 页面导航历史
 */
class NavigationHistory {
    private val backStack = ArrayDeque<Fragment>()
    
    // 打开新页面
    fun navigate(fragment: Fragment) {
        backStack.addLast(fragment)
        showFragment(fragment)
    }
    
    // 返回上一页
    fun goBack(): Boolean {
        if (backStack.size <= 1) return false
        backStack.removeLast()
        val lastFragment = backStack.last()
        showFragment(lastFragment)
        return true
    }
}

/**
 * 3. 图片加载队列
 */
class ImageLoadQueue {
    private val loadQueue = LinkedBlockingDeque<ImageTask>()
    
    // 添加加载任务
    fun addImageTask(task: ImageTask) {
        loadQueue.offerLast(task)
    }
    
    // 优先加载任务
    fun addPriorityTask(task: ImageTask) {
        loadQueue.offerFirst(task)
    }
}

二、高并发应用 🚀

/**
 * 1. 生产者-消费者模式
 */
class ProducerConsumer {
    private val queue = ConcurrentLinkedDeque<Task>()
    
    // 生产者
    class Producer(private val queue: ConcurrentLinkedDeque<Task>) : Runnable {
        override fun run() {
            while (true) {
                val task = createTask()
                queue.offerLast(task)
            }
        }
    }
    
    // 消费者
    class Consumer(private val queue: ConcurrentLinkedDeque<Task>) : Runnable {
        override fun run() {
            while (true) {
                queue.pollFirst()?.let { task ->
                    processTask(task)
                }
            }
        }
    }
}

/**
 * 2. 任务调度系统
 */
class TaskScheduler {
    private val highPriorityQueue = ConcurrentLinkedDeque<Task>()
    private val normalPriorityQueue = ConcurrentLinkedDeque<Task>()
    
    fun scheduleTask(task: Task) {
        if (task.isHighPriority) {
            highPriorityQueue.offerFirst(task)
        } else {
            normalPriorityQueue.offerLast(task)
        }
    }
    
    fun processNextTask() {
        // 优先处理高优先级任务
        highPriorityQueue.pollFirst()?.let { task ->
            executeTask(task)
            return
        }
        
        // 处理普通任务
        normalPriorityQueue.pollFirst()?.let { task ->
            executeTask(task)
        }
    }
}

/**
 * 3. 线程池工作队列
 */
class CustomThreadPool {
    private val workQueue = LinkedBlockingDeque<Runnable>()
    private val workers = mutableListOf<Worker>()
    
    inner class Worker : Thread() {
        override fun run() {
            while (!isInterrupted) {
                val task = workQueue.takeFirst() // 阻塞等待任务
                try {
                    task.run()
                } catch (e: Exception) {
                    // 处理异常
                }
            }
        }
    }
}

三、实际应用场景 🌟

/**
 * 1. Android消息队列
 */
class MessageQueue {
    private val messageDeque = ConcurrentLinkedDeque<Message>()
    
    fun enqueueMessage(msg: Message, when: Long) {
        // 根据时间戳插入合适位置
        var p = messageDeque.peekFirst()
        while (p != null && p.`when` <= when) {
            p = messageDeque.peekFirst()
        }
        messageDeque.offerFirst(msg)
    }
}

/**
 * 2. 异步任务处理
 */
class AsyncTaskQueue {
    private val taskQueue = LinkedBlockingDeque<AsyncTask<*,*,*>>()
    
    fun execute(task: AsyncTask<*,*,*>) {
        taskQueue.offerLast(task)
        processNextTask()
    }
    
    private fun processNextTask() {
        taskQueue.pollFirst()?.execute()
    }
}

/**
 * 3. 缓存管理
 */
class LruCache<K, V>(private val maxSize: Int) {
    private val map = LinkedHashMap<K, V>()
    private val accessQueue = ArrayDeque<K>()
    
    fun put(key: K, value: V) {
        if (accessQueue.size >= maxSize) {
            // 移除最久未使用的项
            val oldestKey = accessQueue.removeFirst()
            map.remove(oldestKey)
        }
        map[key] = value
        accessQueue.addLast(key)
    }
}

四、性能优化建议 ⚡

/**
 * 性能优化建议
 */
class PerformanceOptimization {
    fun tips() = """
    1. 选择合适的实现:
       - ConcurrentLinkedDeque: 并发场景
       - ArrayDeque: 单线程场景
       - LinkedBlockingDeque: 需要阻塞操作时
    
    2. 容量控制:
       - 设置合理的初始容量
       - 及时清理无用数据
       - 避免无限增长
    
    3. 并发处理:
       - 使用线程安全的实现
       - 避免过度同步
       - 合理使用锁机制
    """.trimIndent()
}

五、最佳实践 💡

/**
 * 最佳实践建议
 */
class BestPractices {
    fun recommendations() = listOf(
        // 1. Android场景
        "使用合适的Deque实现处理生命周期事件",
        "实现可撤销操作的历史记录",
        "管理异步任务队列",
        
        // 2. 并发场景
        "使用ConcurrentLinkedDeque避免同步开销",
        "合理设置队列边界",
        "实现优先级调度",
        
        // 3. 性能优化
        "预分配合适容量",
        "及时释放资源",
        "避免频繁扩容"
    )
}

总结一下:

  1. Android应用:

    • 事件处理
    • 页面导航
    • 任务队列
    • 缓存管理
  2. 高并发应用:

    • 生产者-消费者模式
    • 任务调度系统
    • 线程池工作队列
    • 消息队列
  3. 使用建议:

    • 选择合适的实现
    • 注意并发安全
    • 控制容量大小
    • 及时释放资源