LinkedList
# 前言
Java
集合可分为Collection
和Map
两种体系:
Collection
接口:单列数据,定义了存取一组对象的方法的集合List
:元素有序、可重复的集合ArrayList
、LinkedList
、Vector
Set
:元素无序、不可重复的集合HashSet
、LinkedHashSet
、TreeSet
Map
接口:双列数据,保存具有映射关系“key-value对
”的集合,也称为键值对。HashMap
、LinkedHashMap
、TreeMap
、Hashtable
、Properties
现在我们开始学习List接口的实现类LinkedList
。
# 概述
对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高
底层使用双向链表存储,内部没有声明数组,而是定义了内部类,内部类为
Node
类型的first
和last
,用于记录首末元素。同时,定义内部类Node
,作为LinkedList
中保存数据的基本结构。Node
除了保存数据,还定义了两个变量:prev
变量记录前一个元素的位置next
变量记录下一个元素的位置
# 源码分析
# 构造器
先看空参的构造器:
LinkedList list = new LinkedList();
空参实例化时,内部声明了Node类型的first和last属性,默认值为null。size=0,modCount=0。
# 添加元素
当追加元素时,调用add
方法:
public boolean add(E e) {
linkLast(e);
return true;
}
2
3
4
而add
方法里会调用linkLast
方法
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++;
}
2
3
4
5
6
7
8
9
10
11
我们解析一下这段代码:
- 声明
l节点
是当前集合的last
属性指向的节点,也就是上次最后的节点。 - 调用Node
的
实例化方法,把(l节点
,新节点
和null
)做为参数,调用Node
的构造器创建节点对象。 last
属性指向新节点
- 如果
l节点
是空的话,first
属性指向新节点。否则l节点
的next
属性指向新节点
。 size
和modCount
都自增。
下面是创建Node对象定义方式:
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;
}
}
2
3
4
5
6
7
8
9
10
11
这时如果是第一次追加元素,linkLast
方法调用Node
对象的构造器,实际上参数为:(l节点
,新节点
,null
),l节点是上次的最后节点,即null,会将当前对象的first
和last
属性指向null
。
如果不是第一次追加元素,linkLast
调用Node
对象的构造器,实际上参数为:(l节点
,新节点
,null
),,l节点是上次的最后后节点,并且prev
变量记录前一个元素的位置,next
变量记录下一个元素的位置。
看到这里,你就明白为什么是双向链表,每个元素,也就是节点,每次实例化时都有prev
和next
属性指向某个元素或null,分别指向前后节点,first
和last
都记录当前集合的首末元素。无论新增还是插入元素,只要改变指向就行了,所以当频繁的插入和删除元素时比ArrayList
效率高很多。
如果是ArrayList
节点在中间插入元素,还得让后面的元素挪位,重新指向。
假设ArrayList
集合里有1万个元素,我要从第三个位置插入,那集合的操作就很多了,后面每个元素都要挪位,这样子效率就很低了。
如果这是LinkeList
集合,只需要移动指针,需要做的操作是:
- 将第二个元素的next指针指向要插入的元素
- 要插入的元素的prev指针指向第二个元素
- 要插入的元素的next指针指向第四个元素
- 第四个元素的prev指针指向要插入的元素
# get和set方法
先源码:
- get
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
2
3
4
- set
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
2
3
4
5
6
7
- get和set方法都需要node方法来返回对象。
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;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这里很明显,当LinkedList随机get/set元素时,都需要从集合的首元素或者末元素开始,一个个遍历去找到想要寻找的元素,所以这里相对效率就低了。
而ArrayList集合底层是一个动态数组,直接通过下标就可以设置或者获取到元素了,所以在get和set方面,相对效率就高。
- ArrayList的get和set方法
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
2
3
4
5
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
2
3
4
5
6
7
E elementData(int index) {
return (E) elementData[index];
}
2
3
# 新增方法
void addFirst(Object obj)
void addLast(Object obj)
Object getFirst()
Object getLast()
Object removeFirst()
Object removeLast()