往期推荐
- (一)ArrayList详解
- Java集合框架
- 线程池详解
- synchronized简介
- ThreadLocal,你掌握了吗?
- Java异常机制
- JDK8新特性之Stream流
- 同学,你对volatile熟悉么?
- 初识MySQL索引
LinkedList概述
LinkedList是实现了List接口和Deque接口的双向链表,它也可以被当做堆栈、队列或双端队列进行使用,并且LinkedList是非线程安全的集合。
LinkedList类图
LinkedList的属性
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
//链表元素的个数
transient int size = 0;
//指向链表的第一个结点的引用,即指向头结点
transient Node<E> first;
//指向链表的最后一个结点的引用,即指向尾结点
transient Node<E> last;
链表结点,LinkedList的最小基本单位(LinkedList的静态内部类Node)
private static class Node<E> {
//存储的元素
E item;
//指向下一个结点的引用
Node<E> next;
//指向前一个结点的引用
Node<E> prev;
//Node<E>构造方法
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
LinkedList的构造方法
//无参构造
public LinkedList() {}
//带有集合参数的构造器
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
LinkedList添加元素方法
boolean add(E e)方法
- add(E e)方法的作用是向链表的尾部添加元素。
public boolean add(E e) {
linkLast(e);
return true;
}
- 在add(E e)方法中调用linkLast(E e)方法实现添加
void linkLast(E e) {
//获取尾结点(将指向链表尾结点的引用last赋值给l)
final Node<E> l = last;
//创建新的结点Node
//新结点newNode的prev引用指向原尾结点l
final Node<E> newNode = new Node<>(l, e, null);
//修改链表的尾结点为newNode
last = newNode;
//如果原链表的尾结点为空
//则说明原链表中不存在任何元素
if (l == null)
//则设置链表的头结点为newNode
first = newNode;
else
//否则设置链表的原尾结点的next指向为newNode
l.next = newNode;
//链表的长度+1
size++;
//链表结构性变化次数+1
modCount++;
}
- 过程图解
void add(int index, E element)
- 在index位置添加指定元素element
public void add(int index, E element) {
//调用checkPositionIndex(int index)方法校验位置是否合法
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
- 调用
checkPositionIndex(int index)方法校验位置是否合法,index不合法则抛出IndexOutOfBoundsException异常。
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//checkPositionIndex()方法里调用的是isPositionIndex进行index校验
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
- 如果
index==size则代表插入的位置为链表的尾部,则调用linkLast(E e)方法将元素插入到链表尾部。
void linkLast(E e) {
//获取尾结点(将指向链表尾结点的引用last赋值给l)
final Node<E> l = last;
//创建新的结点Node
//新结点newNode的prev引用指向原尾结点l
final Node<E> newNode = new Node<>(l, e, null);
//修改链表的尾结点为newNode
last = newNode;
//如果原链表的尾结点为空
//则说明原链表中不存在任何元素
if (l == null)
//则设置链表的头结点为newNode
first = newNode;
else
//否则设置链表的原尾结点的next指向为newNode
l.next = newNode;
//链表的长度+1
size++;
//链表结构性变化次数+1
modCount++;
}
-
否则,如果
index!=size,则表示是在链表的index位置进行插入,调用linkBefore(element, node(index));方法添加元素(其中,在调用linkBefore()方法之前,会先调用node(int index)方法获取index位置的非空结点)。 -
Node node(int index)方法,获取index位置的非空结点(采用二分查找)
Node<E> node(int index) {
//if语句中相当于index< size/2,二分查找
if (index < (size >> 1)) {
//如果index< size/2,则从链表的头结点开始找index位置的结点
//获取链表的头结点的引用
Node<E> x = first;
//for循环遍历查找
for (int i = 0; i < index; i++)
//指针后移,继续遍历下一个结点,直到找出结点
x = x.next;
return x;
} else {
//否则,则从链表的尾结点开始找index位置的结点
//获取链表的尾结点的引用
Node<E> x = last;
//for循环遍历查找
for (int i = size - 1; i > index; i--)
//指针前移,继续遍历上一个结点
x = x.prev;
return x;
}
}
- linkBefore(E e, Node succ)方法插入结点
//succ结点为插入元素前,index位置上的结点
void linkBefore(E e, Node<E> succ) {
//1.获取index位置的前一个结点的引用
final Node<E> pred = succ.prev;
//2.新建结点
//2.1让newNode的prev指向index位置的前一个结点
//2.2让newNode的next指向index位置的结点
final Node<E> newNode = new Node<>(pred, e, succ);
//3.让index位置的结点的prev指向newNode
succ.prev = newNode;
//判断index位置的前一个结点是否为null
if (pred == null)
//如果为null,则直接将新结点作为链表的头结点,fisrt指向newNode
first = newNode;
else
//否则,将index位置的前一个结点的next指向newNode
pred.next = newNode;
//链表的长度+1
size++;
//链表结构性变化次数+1
modCount++;
}
- 过程图解
LinkedList和 ArrayList的区别
- 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
- 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。且LinkedList集合不支持高效的随机随机访问(RandomAccess)
- 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为ArrayList 增删操作要影响数组内的其他数据的下标。
- 内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
- 线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用LinkedList。
LinkedList常用方法
添加
boolean add(E e)将指定的元素追加到此列表的末尾。void add(int index, E element)在此列表中的指定位置插入指定的元素。boolean addAll(Collection<? extends E> c)按照指定集合的迭代器返回的顺序将指定集合中的所有元素追加到此列表的末尾。boolean addAll(int index, Collection<? extends E> c)将指定集合中的所有元素插入到此列表中,从指定的位置开始。void addFirst(E e)在该列表开头插入指定的元素。void addLast(E e)将指定的元素追加到此列表的末尾。boolean offer(E e)将指定的元素添加为此列表的尾部(最后一个元素)。boolean offerFirst(E e)在此列表的前面插入指定的元素。boolean offerLast(E e)在该列表的末尾插入指定的元素。
删除
E remove()检索并删除此列表的头(第一个元素)。E remove(int index)删除该列表中指定位置的元素。boolean remove(Object o)从列表中删除指定元素的第一个出现(如果存在)。E removeFirst()从此列表中删除并返回第一个元素。boolean removeFirstOccurrence(Object o)删除此列表中指定元素的第一个出现(从头到尾遍历列表时)。E removeLast()从此列表中删除并返回最后一个元素。boolean removeLastOccurrence(Object o)删除此列表中指定元素的最后一次出现(从头到尾遍历列表时)。
检索
E element()检索但不删除此列表的头(第一个元素)。E get(int index)返回此列表中指定位置的元素。E getFirst()返回此列表中的第一个元素。E getLast()返回此列表中的最后一个元素。int indexOf(Object o)返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。int lastIndexOf(Object o)返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。E peek()检索但不删除此列表的头(第一个元素)。E peekFirst()检索但不删除此列表的第一个元素,如果此列表为空,则返回 null。E peekLast()检索但不删除此列表的最后一个元素,如果此列表为空,则返回 null。E poll()检索并删除此列表的头(第一个元素)。E pollFirst()检索并删除此列表的第一个元素,如果此列表为空,则返回 null 。E pollLast()检索并删除此列表的最后一个元素,如果此列表为空,则返回 null 。E pop()从此列表表示的堆栈中弹出一个元素。void push(E e)将元素推送到由此列表表示的堆栈上。
其他方法
Iterator<E> descendingIterator()以相反的顺序返回此deque中的元素的迭代器。boolean contains(Object o)如果此列表包含指定的元素,则返回 truevoid clear()从列表中删除所有元素。ListIterator<E> listIterator(int index)从列表中的指定位置开始,返回此列表中元素的列表迭代器(按适当的顺序)。E set(int index, E element)用指定的元素替换此列表中指定位置的元素。int size()返回此列表中的元素数。
以上就是对LinkedList的详细描述,接下来还会推出其他相关集合类的分析,欢迎大家关注留言、共同进步....