ArrayDeque源码解读

ArrayDeque

ArrayDeque和LinkedList是Deque的两个通用实现,在使用Queue时,由于Queue只是一个接口,因此创建Queue时也会使用ArrayDeque

为了实现在数组两端进行操作元素的需求,因此ArrayDeque使用循环数组作为底层数据结构,同时,ArrayDeque中定义了head和tail两个指针指向头和尾

因为是循环数组,所以head可能比tail大,head指向第一个有效元素,tail指向尾部第一个可以插入元素的空位。

插入元素

因为是循环数组,扩容比较棘手,因此扩容操作是在插入操作完成之后进行的。

tail永远执行一个空位置,所以插入操作永远是有空余位置的。

下标越界直接取余就行。

public void addFirst(E e) {
    if (e == null)//不允许放入null
        throw new NullPointerException();
    elements[head = (head - 1) & (elements.length - 1)] = e;//2.下标是否越界
    if (head == tail)//1.空间是否够用
        doubleCapacity();//扩容
}

扩容操作

扩容操作的逻辑是申请一个更大的数组(两倍),然后使用System.arraycopy进行数据拷贝,拷贝的时候拷贝到新数组的头部。

private void doubleCapacity() {
    assert head == tail;
    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 = (E[])a;
    head = 0;
    tail = n;
}

删除操作

删除只需要挪动下标,然后原来位置置null(方便GC回收)

public E pollFirst() {
    int h = head;
    E result = elements[head];
    if (result == null)
        return null;
    elements[h] = null;//方便GC回收
    head = (head + 1) & (elements.length - 1);//下标越界处理
    return result;
}

快速失败

ArrayDeque不是快速失败的集合,也不是安全失败的集合

ArrayDeque的迭代器是弱一致性的,不是快速失败的。它允许在迭代期间对集合进行修改而不会抛出异常,但这种修改可能不会被迭代器所反映出来。

快速失败和安全失败的区别

快速失败:使用modCount记录结构版本,发现接口修改直接抛出ConcurrentModificationException

安全失败:创建快照副本,保证迭代器迭代的时候不会受到结构修改的影响。

为什么不设计成一个快速失败的数据结构

  • ArrayDeque 的主要使用场景是队列或栈操作,这些操作通常是频繁的插入、删除、读取等基本操作。如果为 ArrayDeque 添加快速失败机制,会引入额外的性能开销,而这些开销在大多数场景下是不必要的。

  • ArrayDeque 设计的主要目的是为单线程环境下的双端队列提供高效的实现。在这种场景下,快速失败的需求并不强烈,因为典型的使用模式是顺序操作,而不是并发访问。

posted @ 2024-08-22 16:20  枫叶藏在眼眸  阅读(26)  评论(0)    收藏  举报