Java集合框架--LinkedList
2018-12-22 本文已影响3人
莴苣
collection框架的接口继承树
Queue接口:
存放有优先顺序的一组元素,除了Collection接口外的操作外,还有自己的额外操作,插入,取出,判断,这些方法中的每一种都存在两种形式:一次抛掷异常,或者返回特殊值(null or false)。在大多数实现中,插入操作不能失败。
Deque接口:
一种支持两端插入和移除的线性集合 Deque 是“双端队列”的缩写。大多数双端队列实现对可以包含的元素的数量没有固定限制,但是这个接口支持容量受限的双端队列以及没有固定大小限制的双端队列。
LinkedList实现类
transient int size = 0;
/**
* 头指针
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* 尾指针
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
//增加方法
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* 在末尾插入一个元素
*/
void linkLast(E e) {
final Node<E> l = last; //获取当前最后一个节点
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null) //集合中还没有元素
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
public E get(int index) {
checkElementIndex(index); //检查是否超下标
return node(index).item;
}
/**
* 返回指定元素索引处的(非空)节点。
*/
Node<E> node(int index) {
// assert isElementIndex(index);
//下面的操作展现了双端队列的一个优化操作
//判断下标是在前半部分还是后半部分。
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
/**
* Unlinks non-null node x.
*/
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
// 被删除的节点是头节点
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
// 被删除的节点是尾节点
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
LinkedList内部实现的是一个双向链表的数据结构,维护了first,last节点。对比ArrayList的get()方法,一般情况效率比较慢,通过传过来的index根据size判断是在前部分,还是后半部分,然后从前,或者从后开始查找。
对于add() 操作来说,ArrayList有扩容的可能,一般情况下LinkedList没有。
对于插入操作add(index,e)
//这是ArrayList的插入操作,index右边的会右移操作,效率就比LinkedList慢了吧
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
遍历的问题
- 如果通过for循环下标的方式去遍历LinkedList,这种做法是非常低效率的,因为每一次,都会从first,或者last节点去查找结点。
2.foreach
3.迭代器 会记录lastReturned节点
4.pollFirst() 会调用unlinkFirst()
5.pollLast() 会调用unlinkLast()
6.removeFirst() 会调用unlinkFirst()
7.removeLast() 会调用unlinkLast()
使用removeFist()或removeLast()效率最高。但用它们遍历时,会删除原始数据;若单纯只读取,而不删除,LinkedList遍历时建议使用For-each或者迭代器的方式。千万不要通过随机访问去遍历LinkedList!