Java零基础手把手系列:ArrayDeque简洁却不简单入门

本文介绍了Java中的ArrayDeque,一种双端队列实现,可以用于实现Stack和Queue。文章详细讲解了ArrayDeque的主要方法、用法、实现原理,包括成员变量、容量计算以及作为Stack和Queue的工作机制。ArrayDeque以其高效的常数时间复杂度操作和非线程安全特性,成为快速队列实现的选择。

 

Java零基础手把手系列:Queue最简入门之后,今天一起学习其中双端队列Deque的一种实现:ArrayDeque

可以添加博主VX:crazy042438讨论

目录

1. ArrayDeque简介

2. 主要方法

3. 主要用法

    3.1 使用ArrayDeque实现Stack

    3.2 使用ArrayDeque实现Queue

4.实现原理简析

    4.1 主要成员

    4.2 特别的容量计算

        4.2.1 容量是如何计算的

        4.2.2 容量计算图解

        4.3 用作Stack(栈)

        4.4 用作Queue(队列)

5. ArrayDeque特点总结

1. ArrayDeque简介

ArrayDeque是一种特殊的自增长数组:允许往它的头部和尾部插入或者删除数据,它是Java内置的实现类(继承自Deque)。

既然头部/尾部都可以添加/删除数据,所以ArrayDeque可以用来实现数据结构中的Stack(后进先出LIFO)和队列(先进先出FIFO)。

虽然Java里面实现Stack和Queue有很多替换类,但是文档里面说:

这就非常有意思了,我们一起来看看。

2. 主要方法

ArrayDeque的方法(增加删除等)都提供两类操作:一类调用之后返回状态(如true),一类调用不成功会抛出异常,如(NullPointerException),主要的方法如下:

功能方法返回状态方法抛出异常
往头部插入offerFirst(e)true/异常addFirst(e)NullPointerException
从头部删除pollFirst()null/元素removeFirst()NoSuchElementException
从头部获取peekFirst()null/元素getFirst()NoSuchElementException
往尾部插入offerLast(e)true/异常addLast(e)NullPointerException
从尾部删除pollLast()null/元素removeLast()NoSuchElementException
从尾部获取peekLast()null/元素getLast()NoSuchElementException

注:offerFirst其实是利用addFirst实现的

3. 主要用法

下面通过几个简单的例子,来看看ArrayDeque的基本使用方法。

3.1 使用ArrayDeque实现Stack

使用内置的两个方法可以轻松实现Stack的功能:

  • push:本质是调用addFirst,来看一下其中源码:

/**
  * Pushes an element onto the stack represented by this deque.  In other
  * words, inserts the element at the front of this deque.
  *
  * <p>This method is equivalent to {@link #addFirst}.
  *
  * @param e the element to push
  * @throws NullPointerException if the specified element is null
  */
 public void push(E e) {
     addFirst(e);
 }
  • pop:本质是调用removeFirst(源码就略过)

当需要往Stack里面添加元素当时候,调用push方法,需要出栈当时候,调用pop方法,我们来看一个例子:

public  void testStack(){
    Deque<String> stack = new ArrayDeque<>();
    stack.push("first");
    stack.push("second");
    //输出second
    System.out.println(stack.pop());
    //输出first
    System.out.println(stack.pop());

}

3.2 使用ArrayDeque实现Queue

同样地,使用其中内置的两个方法,可以轻松实现Queue的功能:

  • offer:本质是调用offerLast实现往队尾插入元素

  • poll:本质是调用pollFirst从头部删除元素

来看一个简单的例子:

public void testQueue(){
    Deque<String> queue = new ArrayDeque<>();
    queue.offer("first");
    queue.offer("second");

    //输出first
    System.out.println(queue.poll());
    //输出second
    System.out.println(queue.poll());
}

4. 实现原理简析

知道了它的简单使用办法,下面从其主要成员、实现Stack以及Queue的逻辑来说明其内部实现的原理

4.1 主要成员

ArrayDeque有三个最主要的成员变量

//基于数组实现
transient Object[] elements;
//头指针
transient int head;   
//尾指针
transient int tail;    

这里基本可以猜到:ArrayDeque是以数组为基础(elements),辅助两个指针(头指针head和尾指针tail)来实现基本操作,也就是插入/删除的时候移动头/尾指针,如果遇到头尾重合,则扩容数组,如下图所示:

 

4.2 特别的容量计算

先说结论,其容量一定是2的n次方,有兴趣可以读下面2个小节,也可以忽略。

4.2.1 容量是如何计算的

ArrayDeque的源码里面有一个函数allocateElements用于分配数组elements的大小,如下:

private void allocateElements(int numElements) {
    int initialCapacity = 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 off
            initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
    }
    elements = new Object[initialCapacity];
}

一看这个很懵逼,先用通俗易懂的语言总结一下:
a)如果传入数组容量小于8,则数组容量就等于8
b)如果传入数组容量(就是numElements)大于8,则寻找大于numElements的min( 2^n )那个数,比如传入9,那么大于9的min(2^5, 2^4, 2^3, 2^2, 2^1)=16,传入16,则大于16的min( 2^5, 2^4, 2^3…)=32
c)min( 2^n )表示:最小的2的n次方
d)所以数组elements的大小一定是2的n次方,也就只能是8,16,32,64,128等数字的取值

4.2.2 容量计算图解

如果想知道其原理,首先需要明白>>>是表示无符号数右移,比如数字5,二进制为0000 0101:

5 = 0000 0101 >>> 1 结果为:0000 0010 = 2
5 = 0000 0101 >>> 2 结果为:0000 0001 = 1

有了这个基础,来看allocateElements函数的图解:

解释一下:

a) 右移目的:所有原来数字二进制位上全部变为1

b)initialCapacity++的目的:所有为1的变为0,并且进位一个1,比如:

 

0000 0111 + 1 = 0000 1000

c) 判断小于0:怕加1之后溢出,所以干脆取最大的值吧,32位的话就是2^30次方

4.3 用作Stack(栈)

push和pop 3.1小节说了其本质是调用addFirst和removeFirst,先看操作图解:

如图示:当调用push往栈里面添加一个元素的时候,是head指针移动,且head的移动方向是从后往前移动(想想为什么呢?),即addFirst里面关键的实现:

head = (head - 1) & (elements.length - 1)

当调用pop推出栈顶元素的时候,先让栈顶引用为null,然后head前移一个位置,即removeFirst/pollFirst里面的关键实现:

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

4.4 用作Queue(队列)

offer和poll 3.2小节说了其本质是调用offerLast和pollFirst,先看操作图解:

如图示:当调用offer往队里面添加一个元素的时候,是tail指针移动,且tail移动的方向是从前往后(head这个时候是指向队首),即addLast里面的关键:

tail = (tail + 1) & (elements.length - 1)

当调用poll推出栈顶元素的时候,先让head指向的引用为null,然后head前移一个位置,即removeFirst/pollFirst里面的关键实现:

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

5. ArrayDeque特点总结

根据上面的一些简单的实现原理,在辅助一些源码的实现,ArrayDeque有几个特殊的地方,这里总结如下:

  • 它不是线程安全的

  • null元素是不行的

  • 大多数的操作时间复杂度为常数(可通过序号定位)

  • 所以速度快于synchronized Stack

  • 所以作为队列使用,比LinkedList快

  • 自动扩容的时候,element的size是直接double

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值