深入剖析(JDK)ArrayQueue源码
深入剖析(JDK)ArrayQueue源码
前言
在本篇文章当中主要给大家介绍一个比较简单的JDK
为我们提供的容器ArrayQueue
,这个容器主要是用数组实现的一个单向队列,整体的结构相对其他容器来说就比较简单了。
ArrayQueue内部实现
在谈ArrayQueue
的内部实现之前我们先来看一个ArrayQueue
的使用例子:
public void testQueue() {
ArrayQueue queue = new ArrayQueue<>(10);
queue.add(1);
queue.add(2);
queue.add(3);
queue.add(4);
System.out.println(queue);
queue.remove(0);
// 这个参数只能为0 表示删除队列当中第一个元素,也就是队头元素
System.out.println(queue);
queue.remove(0);
System.out.println(queue);
}
// 输出结果
[1, 2, 3, 4]
[2, 3, 4]
[3, 4]
首先
ArrayQueue
内部是由循环数组实现的,可能保证增加和删除数据的时间复杂度都是$O(1)$,不像ArrayList
删除数据的时间复杂度为$O(n)$。在ArrayQueue
内部有两个整型数据head
和tail
,这两个的作用主要是指向队列的头部和尾部,它的初始状态在内存当中的布局如下图所示:文章图片
因为是初始状态
head
和tail
的值都等于0,指向数组当中第一个数据。现在我们向ArrayQueue
内部加入5个数据,那么他的内存布局将如下图所示:文章图片
现在我们删除4个数据,那么上图经过4次删除操作之后,
ArrayQueue
内部数据布局如下:文章图片
在上面的状态下,我们继续加入8个数据,那么布局情况如下:
文章图片
我们知道上图在加入数据的时候不仅将数组后半部分的空间使用完了,而且可以继续使用前半部分没有使用过的空间,也就是说在
ArrayQueue
内部实现了一个循环使用的过程。ArrayQueue源码剖析 构造函数
public ArrayQueue(int capacity) {
this.capacity = capacity + 1;
this.queue = newArray(capacity + 1);
this.head = 0;
this.tail = 0;
}@SuppressWarnings("unchecked")
private T[] newArray(int size) {
return (T[]) new Object[size];
}
上面的构造函数的代码比较容易理解,主要就是根据用户输入的数组空间长度去申请数组,不过他具体在申请数组的时候会多申请一个空间。
add函数
public boolean add(T o) {
queue[tail] = o;
// 循环使用数组
int newtail = (tail + 1) % capacity;
if (newtail == head)
throw new IndexOutOfBoundsException("Queue full");
tail = newtail;
return true;
// we did add something
}
上面的代码也相对比较容易看懂,在上文当中我们已经提到了
ArrayQueue
可以循环将数据加入到数组当中去,这一点在上面的代码当中也有所体现。remove函数
public T remove(int i) {
if (i != 0)
throw new IllegalArgumentException("Can only remove head of queue");
if (head == tail)
throw new IndexOutOfBoundsException("Queue empty");
T removed = queue[head];
queue[head] = null;
head = (head + 1) % capacity;
return removed;
}
从上面的代码当中可以看出,在
remove
函数当中我们必须传递参数0,否则会抛出异常。而在这个函数当中我们只会删除当前head
下标所在位置的数据,然后将head
的值进行循环加1操作。get函数
public T get(int i) {
int size = size();
if (i < 0 || i >= size) {
final String msg = "Index " + i + ", queue size " + size;
throw new IndexOutOfBoundsException(msg);
}
int index = (head + i) % capacity;
return queue[index];
}
get
函数的参数表示得到第i
个数据,这个第i
个数据并不是数组位置的第i
个数据,而是距离head
位置为i
的位置的数据,了解这一点,上面的代码是很容易理解的。resize函数
public void resize(int newcapacity) {
int size = size();
if (newcapacity < size)
throw new IndexOutOfBoundsException("Resizing would lose data");
newcapacity++;
if (newcapacity == this.capacity)
return;
T[] newqueue = newArray(newcapacity);
for (int i = 0;
i < size;
i++)
newqueue[i] = get(i);
this.capacity = newcapacity;
this.queue = newqueue;
this.head = 0;
this.tail = size;
}
在
resize
函数当中首先申请新长度的数组空间,然后将原数组的数据一个一个的拷贝到新的数组当中,注意在这个拷贝的过程当中,重新更新了head
与tail
,而且并不是简单的数组拷贝,因为在之前的操作当中head
可能已经不是了0,因此新的拷贝需要我们一个一个的从就数组拿出来,让后放到新数组当中。下图可以很直观的看出这个过程:文章图片
总结 在本篇文章当中主要给大家介绍了
ArrayQueue
的内部实现过程和原理,并且看了ArrayQueue
的源代码,有图的辅助整个阅读的过程应该是比较清晰的,ArrayQueue
也是一个比较简单的容器,JDK
对他的实现也比较简单。以上就是本文所有的内容了,希望大家有所收获,我是LeHung,我们下期再见!!!(记得点赞收藏哦!)
更多精彩内容合集可访问项目:https://github.com/Chang-LeHu...
【深入剖析(JDK)ArrayQueue源码】关注公众号:一无是处的研究僧,了解更多计算机(Java、Python、计算机系统基础、算法与数据结构)知识。
推荐阅读
- 深入剖析多重背包问题(下篇)
- Spring深入刨析声明式事务注解的源码
- C++后台开发|深入理解 Linux 的 TCP 三次握手
- 深入剖析多重背包问题(上篇)
- SpringBoot深入浅出分析初始化器
- ArrayDeque(JDK双端队列)源码深度剖析
- linux系统tomcat的安装配置(一)含jdk环境变量配置
- Mysql|深入理解Mysql事务隔离级别与锁机制
- Spring|Spring Boot 深入分析AutoConfigurationImportFilter自动化条件配置源码
- C语言深入探索之单链表与typedef的用法