首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >集合系列 Queue(十一):ArrayDeque

集合系列 Queue(十一):ArrayDeque

作者头像
陈树义
发布2019-08-29 16:42:06
5010
发布2019-08-29 16:42:06
举报
文章被收录于专栏:陈树义陈树义

从名字我们可以看出,其实一个双向队列实现,而且底层采用数组实现。

public class ArrayDeque<E> extends AbstractCollection<E>
                           implements Deque<E>, Cloneable, Serializable

从定义可以看出,其实现了 Deque 接口。

原理

为了深入理解 ArrayDeque 的原理,我们将从类成员变量、构造方法、核心方法两个方面逐一介绍。

类成员变量

// 数据数组
transient Object[] elements;
// 头结点
transient int head;
// 尾节点
transient int tail; 

从类成员变量我们就可以知道,其底层确实使用数组存储。

构造方法

ArrayDeque 一共有 3 个构造方法:

public ArrayDeque() {
    elements = new Object[16];
} 

public ArrayDeque(int numElements) {
    allocateElements(numElements);
} 

public ArrayDeque(Collection<? extends E> c) {
    allocateElements(c.size());
    addAll(c);
}

从第一个构造方法可以看到,其构造方法直接指定了 ArrayDeque 的初始大小为 16。

核心方法

对于双向队列来说,其关键的方法是:offer、poll、offerFirst、offerLast、pollFirst、pollLast。但其实这些方法的内容都类似,所以我们只分析 offer 和 poll 方法。

offer
public boolean offer(E e) {
    return offerLast(e);
}
    
public boolean offerLast(E e) {
    addLast(e);
    return true;
}
    
public void addLast(E e) {
    if (e == null)
        throw new NullPointerException();
    elements[tail] = e;
    // 当 tail 和 head 相遇时,表示队列已满,需要扩容
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)
        doubleCapacity();
}

这里比较难懂的地方是这个判断:

if ( (tail = (tail + 1) & (elements.length - 1)) == head)
    doubleCapacity();

因为 ArrayDeque 初始容量是 16,而每次扩容都是扩为原来的两倍,所以 ArrayDeque 的容量总是 2 的幂次方。所以上面的判断其实在队列未满时,相当于将 tail 进行加一操作。

if ( (tail = (tail + 1)) == head)
    doubleCapacity();

而做这样一个与操作的目的就是在 tail 到达数组末尾时可以自动切换为 0。我们可以假设此数组大小为 16,而此时 tail 指向了 15,即末尾节点。那么此时执行 offer 操作,我们在计算 (tail + 1) & (elements.length - 1) 就会如下图所示:

10000   // tail + 1 = 15 + 1 = 16
01111   // element.length - 1 = 16 -1 =15
00000   // 结果为0

计算出的结果为 0,也就是说 tail 指针指向了 0 这个位置。

poll
public E poll() {
    return pollFirst();
}
    
public E pollFirst() {
    int h = head;
    @SuppressWarnings("unchecked")
    E result = (E) elements[h];
    // Element is null if deque empty
    if (result == null)
        return null;
    elements[h] = null;     // Must null out slot
    // 等价于 head = h + 1
    // 当处于队列末尾时,会切到队列头
    head = (h + 1) & (elements.length - 1);
    return result;
}

poll 方法也与 offer 方法类似,在最终对 head 节点加一时也用了同样的方法。

总结

看完 ArrayDeque 类的实现,我们不由得会想起 LinkedList 的实现,因为它们两个都是双向队列的实现,但是一个采用数组实现,一个采用链表实现。那么它们有什么异同呢?

通过查询一些资料发现,其实它们两个在功能和效率上并没有太大不同。如果你需要用到双向嘟列,那么 ArrayDeque 相对于 LinkedList 要更好。因为 ArrayDeque 相对于 LinkedList 直接采用数组存储,而 LinkedList 则需要采用节点存储。所以 LinkedList 相对于 ArrayDeque 需要消耗更多内存。

本身数组与链表的差异在于查询和修改的差异,但是对于队列来说,其都是在头和尾进行操作。所以数组与链表的差异在队列身上没有任何体现。而查阅 ArrayDeque 和 LinkedList 的发布时间,我们会发现 ArrayDeque 发布于 JDK 1.6,而 LinkedList 发布于 JDK 1.2。所以从这一点来看,我们有理由相信 ArrayDeque 其实是 LinkedList 的优化版本。

如果你对 LinkedList 和 ArrayDeque 的差异感兴趣,建议阅读这篇文章:Java: ArrayDeque vs. LinkedList

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-08-28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 原理
    • 类成员变量
      • 构造方法
        • 核心方法
          • offer
          • poll
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档