死磕 java集合之ArrayDeque源码分析

问题

(1)什么是双端队列?

(2)ArrayDeque是怎么实现双端队列的?

(3)ArrayDeque是线程安全的吗?

(4)ArrayDeque是有界的吗?

我自己是一个从事了6年的Java全栈工程师,最近整理了一套适合2019年学习的Java\大数据资料,从基础的Java、大数据面向对象到进阶的框架知识都有整理哦,可以来我的主页免费领取哦

简介

双端队列是一种特殊的队列,它的两端都可以进出元素,故而得名双端队列。

ArrayDeque是一种以数组方式实现的双端队列,它是非线程安全的。

继承体系

死磕 java集合之ArrayDeque源码分析_第1张图片

通过继承体系可以看,ArrayDeque实现了Deque接口,Deque接口继承自Queue接口,它是对Queue的一种增强。

publicinterfaceDequeextendsQueue{// 添加元素到队列头voidaddFirst(E e);// 添加元素到队列尾voidaddLast(E e);// 添加元素到队列头booleanofferFirst(E e);// 添加元素到队列尾booleanofferLast(E e);// 从队列头移除元素EremoveFirst();// 从队列尾移除元素EremoveLast();// 从队列头移除元素EpollFirst();// 从队列尾移除元素EpollLast();// 查看队列头元素EgetFirst();// 查看队列尾元素EgetLast();// 查看队列头元素EpeekFirst();// 查看队列尾元素EpeekLast();// 从队列头向后遍历移除指定元素booleanremoveFirstOccurrence(Object o);// 从队列尾向前遍历移除指定元素booleanremoveLastOccurrence(Object o);// *** 队列中的方法 ***// 添加元素,等于addLast(e)booleanadd(E e);// 添加元素,等于offerLast(e)booleanoffer(E e);// 移除元素,等于removeFirst()Eremove();// 移除元素,等于pollFirst()Epoll();// 查看元素,等于getFirst()Eelement();// 查看元素,等于peekFirst()Epeek();// *** 栈方法 ***// 入栈,等于addFirst(e)voidpush(E e);// 出栈,等于removeFirst()Epop();// *** Collection中的方法 ***// 删除指定元素,等于removeFirstOccurrence(o)booleanremove(Object o);// 检查是否包含某个元素booleancontains(Object o);// 元素个数publicintsize();// 迭代器Iteratoriterator();// 反向迭代器IteratordescendingIterator();}

Deque中新增了以下几类方法:

(1)*First,表示从队列头操作元素;

(2)*Last,表示从队列尾操作元素;

(3)push(e),pop(),以栈的方式操作元素的方法;

源码分析

主要属性

// 存储元素的数组transientObject[] elements;// non-private to simplify nested class access// 队列头位置transientinthead;// 队列尾位置transientinttail;// 最小初始容量privatestaticfinalintMIN_INITIAL_CAPACITY =8;

从属性我们可以看到,ArrayDeque使用数组存储元素,并使用头尾指针标识队列的头和尾,其最小容量是8。

主要构造方法

// 默认构造方法,初始容量为16publicArrayDeque(){    elements =newObject[16];}// 指定元素个数初始化publicArrayDeque(intnumElements){    allocateElements(numElements);}// 将集合c中的元素初始化到数组中publicArrayDeque(Collection c){    allocateElements(c.size());    addAll(c);}// 初始化数组privatevoidallocateElements(intnumElements){    elements =newObject[calculateSize(numElements)];}// 计算容量,这段代码的逻辑是算出大于numElements的最接近的2的n次方且不小于8// 比如,3算出来是8,9算出来是16,33算出来是64privatestaticintcalculateSize(intnumElements){intinitialCapacity = MIN_INITIAL_CAPACITY;// Find the best power of two to hold elements.// Tests "<=" because arrays aren't kept full.if(numElements >= initialCapacity) {        initialCapacity = numElements;        initialCapacity |= (initialCapacity >>>1);        initialCapacity |= (initialCapacity >>>2);        initialCapacity |= (initialCapacity >>>4);        initialCapacity |= (initialCapacity >>>8);        initialCapacity |= (initialCapacity >>>16);        initialCapacity++;if(initialCapacity <0)// Too many elements, must back offinitialCapacity >>>=1;// Good luck allocating 2 ^ 30 elements}returninitialCapacity;}

通过构造方法,我们知道默认初始容量是16,最小容量是8。

入队

入队有很多方法,我们这里主要分析两个,addFirst(e)和addLast(e)。

// 从队列头入队publicvoidaddFirst(E e){// 不允许null元素if(e ==null)thrownewNullPointerException();// 将head指针减1并与数组长度减1取模// 这是为了防止数组到头了边界溢出// 如果到头了就从尾再向前// 相当于循环利用数组elements[head = (head -1) & (elements.length -1)] = e;// 如果头尾挨在一起了,就扩容// 扩容规则也很简单,直接两倍if(head == tail)        doubleCapacity();}// 从队列尾入队publicvoidaddLast(E e){// 不允许null元素if(e ==null)thrownewNullPointerException();// 在尾指针的位置放入元素// 可以看到tail指针指向的是队列最后一个元素的下一个位置elements[tail] = e;// tail指针加1,如果到数组尾了就从头开始if( (tail = (tail +1) & (elements.length -1)) == head)        doubleCapacity();}

(1)入队有两种方式,从队列头或者从队列尾;

(2)如果容量不够了,直接扩大为两倍;

(3)通过取模的方式让头尾指针在数组范围内循环;

(4)x & (len - 1) = x % len,使用&的方式更快;

扩容

privatevoiddoubleCapacity() {asserthead == tail;// 头指针的位置intp = head;// 旧数组长度intn = elements.length;// 头指针离数组尾的距离intr = n - p;// number of elements to the right of p// 新长度为旧长度的两倍intnewCapacity = n <<1;// 判断是否溢出if(newCapacity <0)thrownewIllegalStateException("Sorry, deque too big");// 新建新数组Object[] a =newObject[newCapacity];// 将旧数组head之后的元素拷贝到新数组中System.arraycopy(elements, p, a,0, r);// 将旧数组下标0到head之间的元素拷贝到新数组中System.arraycopy(elements,0, a, r, p);// 赋值为新数组elements = a;// head指向0,tail指向旧数组长度表示的位置head =0;    tail = n;}

扩容这里迁移元素可能有点绕,请看下面这张图来理解。

死磕 java集合之ArrayDeque源码分析_第2张图片

出队

出队同样有很多方法,我们主要看两个,pollFirst()和pollLast()。

// 从队列头出队publicE pollFirst() {    int h = head;@SuppressWarnings("unchecked")// 取队列头元素E result = (E) elements[h];// 如果队列为空,就返回nullif(result ==null)returnnull;// 将队列头置为空elements[h] =null;// Must null out slot// 队列头指针右移一位head = (h +1) & (elements.length -1);// 返回取得的元素returnresult;}// 从队列尾出队publicE pollLast() {// 尾指针左移一位int t = (tail -1) & (elements.length -1);@SuppressWarnings("unchecked")// 取当前尾指针处元素E result = (E) elements[t];// 如果队列为空返回nullif(result ==null)returnnull;// 将当前尾指针处置为空elements[t] =null;// tail指向新的尾指针处tail = t;// 返回取得的元素returnresult;}

(1)出队有两种方式,从队列头或者从队列尾;

(2)通过取模的方式让头尾指针在数组范围内循环;

(3)出队之后没有缩容哈哈^^

前面我们介绍Deque的时候说过,Deque可以直接作为栈来使用,那么ArrayDeque是怎么实现的呢?

publicvoidpush(E e){    addFirst(e);}publicEpop(){returnremoveFirst();}

是不是很简单,入栈出栈只要都操作队列头就可以了。

总结

(1)ArrayDeque是采用数组方式实现的双端队列;

(2)ArrayDeque的出队入队是通过头尾指针循环利用数组实现的;

(3)ArrayDeque容量不足时是会扩容的,每次扩容容量增加一倍;

(4)ArrayDeque可以直接作为栈使用;

彩蛋

双端队列与双重队列?

双端队列(Deque)是指队列的两端都可以进出元素的队列,里面存储的是实实在在的元素。

双重队列(Dual Queue)是指一种队列有两种用途,里面的节点分为数据节点和非数据节点,它是LinkedTransferQueue使用的数据结构。

我自己是一个从事了6年的Java全栈工程师,最近整理了一套适合2019年学习的Java\大数据资料,从基础的Java、大数据面向对象到进阶的框架知识都有整理哦,可以来我的主页免费领取哦

你可能感兴趣的:(死磕 java集合之ArrayDeque源码分析)