JDK常用数据结构
Posted IT路人乙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK常用数据结构相关的知识,希望对你有一定的参考价值。
在JDK中使用到的数据结构有数组、链表、栈、队列、树、散列和堆等,这部分主要记录数组、链表、栈和队列,其他的数据结构可以参考下一部分。
数组
数组是可以再内存中连续存储多个元素的结构,在内存中的分配也是连续的,数组中的元素通过数组下标进行访问,数组下标从0开始。JDK中代表作:ArrayList<E>,默认大小为10。代码片段
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
...
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
...
优点
按照索引查询元素速度快
按照索引遍历数组方便
缺点
数组的大小固定后就无法扩容了
数组只能存储一种类型的数据
添加,删除的操作慢,因为要移动其他的元素
适用范围
频繁查询,对存储空间要求不大,很少增加和删除的情况。
链表
单向链表 - 链接方向是单向的,对链表的访问要通过顺序读取从头部开始,每个结点都有指针指向列表中的下一个结点
双向链表 - 每个结点都有两个指针,分别指向直接后继和直接前驱。从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点
循环列表 - 表中最后一个结点的指针域指向头结点,整个链表形成一个环
JDK中代表作:LinkedList<E>,为双向链表。结点代码片段
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
LinkedList<E>源码片段
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
/**
* Constructs an empty list.
*/
public LinkedList() {
}
...
持有链表第一个结点和最后一个结点的指针。
优点
不需要初始化容量,可以任意加减元素
缺点
因为含有大量的指针域,占用空间较大
查找元素需要遍历链表来查找,非常耗时
适用范围
数据量较小,需要频繁增加,删除操作的场景。
堆栈
堆栈是一种运算受限的线性表,限定仅在表尾进行插入和删除操作。一端被称为栈顶,另一端称为栈底。栈的特点是:先进后出,或者说是后进先出,从栈顶放入元素的操作叫入栈,取出元素叫出栈。栈的结构就像一个集装箱,越先放进去的东西越晚才能拿出来,所以,栈常应用于实现递归功能方面的场景。JDK中堆栈的实现代表:Stack<E>,源码片段
...
public class Stack<E> extends Vector<E> {
/**
* Creates an empty Stack.
*/
public Stack() {
}
/**
* Pushes an item onto the top of this stack. This has exactly
* the same effect as:
* <blockquote><pre>
* addElement(item)</pre></blockquote>
*
* @param item the item to be pushed onto this stack.
* @return the <code>item</code> argument.
* @see java.util.Vector#addElement
*/
public E push(E item) {
addElement(item);
return item;
}
/**
* Removes the object at the top of this stack and returns that
* object as the value of this function.
*
* @return The object at the top of this stack (the last item
* of the <tt>Vector</tt> object).
* @throws EmptyStackException if this stack is empty.
*/
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
...
Stack<E>继承了Vector<E>类,不过限制了后者的操作为push入栈和pop出栈。从源码中可看出,push操作是在链表的末端增加元素,pop操作则是在链表末端删除元素并返回。Vector<E>类和ArrayList<E>类相似,使用数组实现;不过不同之处是:a. 前者线程安全,后者线程不安全;b. 前者扩展为原大小的2倍,后者为1.5倍。
队列
和堆栈类似,队列也是一种特殊的线性表。特殊之处在于它只允许表的前端进行删除操作,后端进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列具有先进先出(FIFO)的特性。JDK中实现的队列如下
总的来说,JDK中使用数组和链表实现了有界队列和无界队列,此外,还是实现了不存储数据的中转队列。其中,有界队列有: ArrayBlockingQueue<E>/LinkedBlockingQueue<E>/LinkedBlockingDeque<E>;无界队列有:PriorityBlockingQueue<E>/DelayQueue<E>/LinkedTransferQueue<E>。
适用范围
LinkedBlockingDeque应用场景很少,一般用在“工作窃取”模式下,ArrayBlockingQueue和LinkedBlockingQueue基本就是数组和链表的区别;PriorityBlockingQueue用在需要排序的队列中,DelayQueue可以用来做一些定时任务或者缓存过期的场景;不存储元素的队列SynchronousQueue用来处理一些高效的透传场景。
以上是关于JDK常用数据结构的主要内容,如果未能解决你的问题,请参考以下文章