1、实现原理、内部组成
我们知道,
ArrayList
内部是数组,元素在内存中是连续存放的,但LinkedList
不是。
LinkedList
直译就是链表
,确切的说,它的内部实现就是双向链表
,每个元素在内存都是单独存放的,元素之间通过连接一起
为了表示链接关系,需要一个节点
的概念
节点包括实际的元素,但同时有两个连接,分别指向前面一个节点(前驱)和后一个节点(后继)。
节点是一个内部类,具体定义如下:
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
内部组成就是如下三个实例变量:
//LinkedList的所有public方法内部操作都是这三个实例变量
transient int size = 0; //表示链表长度,默认为0
transient Node<E> first; //指向头节点
transient Node<E> last;//指向尾结点
LinkedList
的所有public
方法操作都是这三个实例变量
2、add方法
public boolean add(E e) {
linkLast(e);//在链表的尾部添加元素
return true;
}
void linkLast(E e) {
final Node<E> l = last;//l指向原来的尾部节点
final Node<E> newNode = new Node<>(l, e, null);//创建一个新的节点
last = newNode;//修改尾部节点,指向新的最后节点newNode
if (l == null) //修改前节点的后向连接,如果原来链表为空,则让头节点指向新节点
first = newNode;
else
l.next = newNode;//否则让前一个节点的next指向新节点
size++;//增加链表大小
modCount++;//modCount的目的与ArrayList是一样的,记录修改次数,便于迭代中间检测结构性变化
}
可以看出,与
ArrayList
不同,LinkedList
的内存是按需分配,不需要预先分配多余内存,添加元素只需要分配新元素的空间,然后调节几个连接即可
3、get方法
//根据索引访问元素get(index)
public E get(int index) {
checkElementIndex(index);//检查索引位置的有效性,如果无效,则抛出异常
return node(index).item;//如果index有效,则调用node方法查找对应的节点,其
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) { //size>>1等于size/2,如果索引位置在前半部分(index<(size>>1)),则从头节点开始查找,否则从尾及诶按开始查找。
Node<E> x = first;
for (int i = 0; i < index; i++)//for循环,复杂度为O(n)
x = x.next;
return x;
} else {
Node<E> x = last; //可以看出,与ArrayList明显不同,ArrayList中数组元素可以连续存放,可以根据索引直接定位,而在LinkedList中,则必须从头或尾顺着连接查找,效率较低
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
4、indexOf方法
代码很简单,从头节点顺着连接往后找,如果要找的是null,则找到第一个
item
为null的节点,否则使用equals
方法进行比较
public intf indexOf(Object o) {
int index = 0;
if (o == null) {//如果要找的是null.则找到第一个Item为null的节点
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {//使用equals方法进行比较
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
5、node(index)
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) { //size>>1等于size/2,如果索引位置在前半部分(index<(size>>1)),则从头节点开始查找,否则从尾及诶按开始查找。
Node<E> x = first;
for (int i = 0; i < index; i++)//for循环,复杂度为O(n)
x = x.next;
return x;
} else {
Node<E> x = last; //可以看出,与ArrayList明显不同,ArrayList中数组元素可以连续存放,可以根据索引直接定位,而在LinkedList中,则必须从头或尾顺着连接查找,效率较低
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
5、在中间插入元素
public void add(int index, E element) {
checkPositionIndex(index);//
if (index == size)//若index为size,添加到最后面
linkLast(element);
else //一般情况,插入到Index对应节点的前面
linkBefore(element, node(index));//node(index)找到该节点,遍历
}
void linkLast(E e) {
final Node<E> l = last;//l指向原来的尾部节点
final Node<E> newNode = new Node<>(l, e, null);//创建一个新的节点
last = newNode;//修改尾部节点,指向新的最后节点newNode
if (l == null) //修改前节点的后向连接,如果原来链表为空,则让头节点指向新节点
first = newNode;
else
l.next = newNode;//否则让前一个节点的next指向新节点
size++;//增加链表大小
modCount++;//modCount的目的与ArrayList是一样的,记录修改次数,便于迭代中间检测结构性变化
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev; //succ 表示后继节点,变量pred表示前驱节点
//目的是在pred和success中间插入一个节点
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;//后继的前驱节点指向新节点
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;//增加长度
modCount++;//增加修改长度
}
7、删除元素remove
删除x节点,基本思想就是让x的前驱和后继直接连接起来,next是x的后继,prev是x的前驱
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));//node(index)查找Index索引位置处的节点
}
//删除x节点,基本思想就是让x的前驱和后继直接连接起来,next是x的后继,prev是x的前驱
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item; //待返回节点的值
final Node<E> next = x.next;//x的后继节点
final Node<E> prev = x.prev;//x的前驱节点
//让x的前驱的后继指向x的后继
//如果x没有前驱,说明删除的是头结点,则修改头街点指向x的后继
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++;//修改次数+1
return element;
}
8、LinkedList特点分析
用法上,
LinkedList
是一个List
,但也实现了Deque
接口,可以作为队列
,栈
和双端对哦咧
使用。实现原理上,
LinkedList
内部是一个双向链表,并维护了长度
,头结点
,尾结点
,这决定了他有如下特点:
- 按需要分配空间,不需要预先分配很多空间
- 不可以随机访问,按照索引位置访问效率比较低,必须葱油或尾顺着链表查找,效率为O(N/2)
- 不管列表是否已经排序,只要按照内容查找元素,效率都比较低,必须逐个比较,效率为O(N)
- 在两端添加、删除元素的效率很高,为O(1)
- 在中间插入、删除元素,需要先定位,效率比较低,为O(N)
’