【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
类图关系:
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的属性变量:
//长度
transient int size = 0;
//第一个元素
transient Node<E> first;
//最后一个元素
transient Node<E> last;
一、构造方法:
LinkedList的构造方法,除了一个无参构造方法,还有一个以Collection 实例为入参的构造方法,内部调用了allAll(Collection实例)方法,后面再细说吧。看过LinkedList的底层数据结构和属性变量之后,在这里需要细说一下LinkedList的结构:
一个Node节点对象,它的next属性引用指向下一个节点,prev属性引用指向上一个节点,因此节点与节点之间形成一个链表的结构,不管从哪个节点开始,通过这两个属性都可以找到链表中其他所有的节点,所以针对于LinkedList底层元素的操作,其实就是针对节点的next和prev的引用指向的修改。
同样的,我们可以想到first作为链表的第一个节点,肯定是没有前一个节点的,所以其属性prev引用指向null,同样的道理,last的next属性引用也指向null。
画个图会比较好理解一些:
二、常用方法解析:
粗略浏览一遍Linked的源码,发现大多数常用方法都是调用几个非public方法,那先看看里面的非public方法:
1.linkFirst(E e):向链表的首部添加一个节点,入参e作为该节点的值
//把e作为第一个节点的值关联起来
private void linkFirst(E e) {
//获取第一个节点的副本f
final Node<E> f = first;
//新建一个节点,将副本f作为下一个节点,e作为本节点的值,前一个节点为null
final Node<E> newNode = new Node<>(null, e, f);
//将新建的节点赋值给first,使其作为第一个节点
first = newNode;
//如果f为null,说明是第一次增加节点,整个链表只有一个节点,最后一个节点也就是第一个节点,赋值给last
if (f == null)
last = newNode;
else
//如果f不为null,此时f作为第一个节点的下一个节点,需要将其的属性prev引用指向第一个节点,即newNode
f.prev = newNode;
//长度+1
size++;
//修改次数+1
modCount++;
}
2.linkLast(E e):向链表尾部新增一个节点,入参e作为该节点的值
void linkLast(E e) {
//拷贝尾部节点的副本l
final Node<E> l = last;
//新建一个节点,使其上一个节点为副本l,下一个节点为null
final Node<E> newNode = new Node<>(l, e, null);
//将该节点重新赋值给尾部节点
last = newNode;
//如果副本l为null,说明是第一次添加元素,first也指向新建的节点元素
if (l == null)
first = newNode;
else
//如果副本l不为null,将其下一个节点的引用指向新的尾部节点
l.next = newNode;
size++;
modCount++;
}
3.linkBefore(E e,Node<E>succ): 将元素e插入到一个不为空的节点的前面
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//获取succ节点的前一个节点作为副本pred
final Node<E> pred = succ.prev;
//新建一个节点,使其prev指向副本pred,也就是原节点succ的prev
// 使其next指向原节点succ本身,e作为该节点的值
final Node<E> newNode = new Node<>(pred, e, succ);
//修改原节点succ的prev的引用指向新建节点
succ.prev = newNode;
//如果副本pred为null,说明原节点succ在做修改之前就是first的引用,
//修改之后,原节点的前一个节点应该为第一个节点
if (pred == null)
first = newNode;
else
//如果pred不为null,将pred的next引用指向新建的节点
pred.next = newNode;
size++;
modCount++;
}
4.unlinkFirst(Node<E> f): 删掉第一个节点(f为第一个节点作为参数传进方法)
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
//获取节点f的值element
final E element = f.item;
//获取节点f的下一个节点作为副本next(即第二个节点)
final Node<E> next = f.next;
//以下两步赋值走完,f此时属性全为null(确保参数f是第一个节点first的情况下)
f.item = null;
f.next = null; // help GC
//将next赋值给first变量,此时第二个节点就变成了第一个节点
first = next;
//如果next为null,说明对象实例的节点只有一个,以上操作将去掉唯一一个节点的引用.first和next都为null
if (next == null)
last = null;
else
//如果next不为null,此时next作为第一个节点,prev引用原指向从f变成指向null,
//至此,第一个节点f将没有变量进行引用,将会被GC回收
next.prev = null;
size--;
modCount++;
//返回保存下来的element的值
return element;
}
5.unlinkLast(Node<E>l):删除最后一个节点
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
//获取上一个节点保存到prev变量中
final Node<E> prev = l.prev;
//将最后一个节点属性都赋值为null
l.item = null;
l.prev = null; // help GC
//改变last的引用指向为prev
last = prev;
if (prev == null)
//如果prev为null,说明以上操作是删除本对象实例的唯一一个节点,实例中不再包含节点,first和last都为null
first = null;
else
//将最后一个节点的next赋值为null,至此,原最后一个节点l将没有任何变量引用,会被GC回收
prev.next = null;
size--;
modCount++;
return element;
}
6.unlink(Node<E> x): 将节点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;
//prev为null说明节点x是第一个元素,去掉该元素意味着第二个节点将作为first的引用
if (prev == null) {
first = next;
} else {
//设置节点x的上一个元素prev的下一个元素next 指向x的下一个元素next
//看起来读起来有点绕,其实就是将节点x去掉,那节点x的上下节点要连接起来,就要修改上下节点的next和prev属性
//这一步是修改上节点的next属性
prev.next = next;
//设置x的prev为null
x.prev = null;
}
//next为null,说明节点x是最后一个元素,去掉该元素意味着x的上节点将作为last的引用
if (next == null) {
last = prev;
} else {
//同上,这一步是修改x节点下节点的prev属性,
//这一步修改完成后,将没有任何变量的引用指向x,方法执行完,x将会被GC回收
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
好了,分析完这几个非public方法,基本上就已经了解了LinkedList对底层数据的操作是怎么一回事了,再详细看看常用的方法吧:
7.获取方法:基本上都是调用以上几个非public方法
//获取第一个元素
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
//获取最后一个元素
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
//获取下标第index个元素
public E get(int index) {
//检查index是否在0-size的范围内
checkElementIndex(index);
return node(index).item;
}
//获取第index个元素
Node<E> node(int index) {
// assert isElementIndex(index);
//LinkedList的变量中只有第一个和最后一个节点,获取元素只有从这两个变量着手
//先做一次判断,用index和size/2的关系,来判断顺序还是倒序查找元素
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 int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
//获取某个元素在链表中最后一次出现的位置(倒序查找)
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
//嗯...获取第一个元素,并且会移除第一个元素,FIFO的概念
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
8.新增
//直接调用linkLast
public boolean add(E e) {
linkLast(e);
return true;
}
//在链表尾部添加集合元素
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//从某个位置开始添加元素集合
public boolean addAll(int index, Collection<? extends E> c) {
//判断index在不在0-size之间
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
//将链表在index位置拆开,分成两个链表,
//阅读完代码可知:pred表示前半段链表的最后一个节点,succ表示后半段链表的第一个节点
Node<E> pred, succ;
if (index == size) {
//在链表尾部添加元素
//那么pred(前半段)为最后一个节点,succ(后半段)为null
succ = null;
pred = last;
} else {
//succ(后半段)为下标index位置的元素,node(index)方法在获取方法中已经解析
succ = node(index);
//pred(前半段)为succ的前一节点
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//每循环一个数组的元素,将新建一个节点,修改节点的前一节点为pred(前半段),
//新建节点为(前半段+新元素)
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
//如果pred为null,说明是在第一个位置添加元素,first的引用应指向新建的节点
first = newNode;
else
//pred不为null,那将修改pred(前半段链表)的next为当前节点
pred.next = newNode;
//重置前半段链表为新建节点(原前半段+新元素)
pred = newNode;
}
//循环走完后,pred为原前半段+数组中所有元素组成的新链表的最后一个节点,
//此时该节点的next仍然没有和后半段链表相连
//如果succ(后半段)为null,那么pred就是最后的新链表的最后一个节点了,赋值给last即可
if (succ == null) {
last = pred;
} else {
//succ不为null,将彼此的next和prev互相引用即可
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
//设置指定下标节点的值,并返回原来的值
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
//在指定下标出新增节点
public void add(int index, E element) {
//老规矩,先做判断
checkPositionIndex(index);
//如果index==size,直接在尾部添加即可
if (index == size)
linkLast(element);
else
//直接指定下标节点前添加元素,那添加的元素的下标就为index了,原下标index的元素会往后移一位
linkBefore(element, node(index));
}
9.删除
//删除元素 是删除第一个元素 并返回该元素的值
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//删除指定元素,只会删除第一个匹配的元素
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;
}
//删除指定下标的元素
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
还有一些方法如pop(),push()之类的,都是调用上面那些非public方法,就不再赘述了
三、迭代器
LinkedList的迭代器是实现在类里的内部类:
private class ListItr implements ListIterator<E> {
//最后一个被返回的节点
private Node<E> lastReturned;
//下一个将要被返回的节点
private Node<E> next;
//下一个将要被返回的节点的下标
private int nextIndex;
private int expectedModCount = modCount;
//如果指定下标index,则该迭代器从下标index开始迭代,next指向下标index的节点即可
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
//链表结构迭代非常方便,调用一次,先保存next的副本,直接给next指向下一节点next.next,
//返回副本的值即可
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
//移除当前元素
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
//直接调用unlink(E e),在链表中删除该元素,再修改变量属性next,lastReturned,nextIndex的值即可
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
//一般情况下next为lastReturned的下一节点,如果出现两者引用地址一样的话,
//应该是多线程的影响,重新指向lastReturned的下一个节点
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
//忽略其他方法
}
对比ArrayList的迭代器,因为LinkedList的数据结构的特殊性,LinkedList迭代器就方便很多。
END
来源:oschina
链接:https://my.oschina.net/siwcky90/blog/3147760