目录
LinkedList的数据结构从JDK1.7开始采用双向链表结构,在JDK1.6之前使用的数据结构是双向循环链表
LinkedList的数据结构从JDK1.7开始采用双向链表结构,在JDK1.6之前使用的数据结构是双向循环链表
LinkedList从1.7开始,底层采用的是双向链表数据结构。
双向链表由多个节点Node<E>构成,每个节点由三部分构成,分别是前驱节点(prev)、元素值(item)、后继节点(next),节点之间是游离状态存在的。
下面咱们来看源码:
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;
}
}
这个Node<E>类是LinkedList<E>的内部类,private修饰只服务于LinkedList。其中有成员变量E item元素值,前驱节点Node<E> prev,后继节点Node<E> next。其中的全参构造,参数依次是前驱节点、元素值、后继节点 这样就构成了一个节点。
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;
这是LinkedList<E>类的三个成员变量,首先呢第一个size是你LinkedList集合的长度是多少初始值为0,第二个first变量的意思是指向第一个节点(头节点)的指针,第三个last的意思是指向最后一个节点(尾节点)的指针。默认null。
add(E e)末尾添加元素
LinkedList增加元素的内部实现 add(E e)
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
*/
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++;
}
add(E e)其中内部调用linkLast(E e)方法 在最后尾节点链元素 来看看源码是怎么玩的吧:
首先我们假设集合中是没有元素的,即LinkedList集合的三个成员变量均为初始值:size=0,Node类型的first和last为null。然后方法中定义局部变量Node<E> l = last;l引用指向咱们的尾节点,此时为null。接着new了一个Node创建一个节点,上面有介绍这个类,它的全参构造参数依次是前驱节点、元素值、后继节点。Node<E> newNode 新节点在末尾添加,所以它的前驱节点指向l也就是last尾节点,元素值为e,后继节点为null(因为把它添加在最后一个)。下行代码last = newNode;就是将LinkedList集合的成员变量last(尾节点)指向这个要添加在最后的元素newNode。接着,判断l是否为null,我们目前l为null,进入first也指向这个新添加的元素。(因为我们目前做的是集合中没有节点,添加一个节点,所以first和last都指向这个新添加的元素)。然后size结合长度加一,modCount++这个集合的操作次数加一。
下面来上一种流程图:
我们可知道,目前LinkedList集合刚创建出来没有节点,然后我们调用其add(E e)方法(内部将元素添加在链表最后)添加一个元素后,其中的成员变量指向:first头节点与last尾节点都指向这个新添加的节点,由于只有一个节点所以这个节点的前驱节点与后继节点都为null。
做得有点丑,哈哈!希望各位大佬看见了给点意见吧!
add(int index,E e):向指定位置插入新元素
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
首先调用checkPositionIndex(index)这个方法,内部再调用isPositionIndex(index)。说白了就是先判断传进去的index是否越界了,这个指定位置应当是 index >= 0 && index <= size;
然后判断index是否等于size,如等于就是在链表最后添加,所以调用linkLast(element);同上面add(E e)末尾添加元素一个道理。
如果index不等于size,便调用linkBefore(element, node(index));传参有个node(index)方法:
就是查找元素,链表查找元素:遵循对半查找,即查找的节点下标小于长度/2,从头节点开始挨个查找,若查找的节点下标大于长度/2,从尾结点开始逆向挨个查找!看源码↓↓↓
其中x.next表示的是后继节点的指向,x.prev表示的是前驱节点的指向。
/**
* Returns the (non-null) Node at the specified element index.
*/
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;
}
}
将index对应的节点求出传入linkBefore(E e,Node<E> succ)方法,第一个参数是你要插入的元素,第二个参数是你要插入的那个位置的原始元素,也就是上面找到的index对应的。
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
如:假设集合中有4个元素,现在向下标为2的位置插入元素add(2,e),经过判断执行linkBefore(e, node(2))
其过程就是在修改链的指向。集合源码,还是很好理解的哦!删除元素目前就没有写了,道理都差不多。
结论:
双向链表优缺点:
- 和数组相对而言,数据量大时,链表结构增删元素性能更高,查找元素性能偏低
- 细节:若链表中查找的头结构或尾结点,查询性能不低
查找元素:遵循对半查找,若查找的节点下标小于size/2,从头节点开始挨个查找,若查找的节点下标大于size/2,从尾结点开始逆向挨个查找。
ArrayList和LinkedList的区别:
ArrayList底层采用的是数组结构,LinkedList底层采用的是双向链表结构。
当数据量大时,ArrayList查询性能更高,增删元素性能较低。
LinkedList增删元素性能更高,查询元素性能较低。