一.序言
在上一篇队列中,只讲解了基本原理,今天就来聊聊队列的一个实现,也是面试中经常和ArrayList对比的一个类--LinkedList。java源码系列。
他们的不同点:
1.实现结构不同。ArrayList 是基于数组的list。LinkedList是基于链表的list。
2.初始容量不同。ArrayList初始容量是10,之后按1.5倍扩容。LinkedList没有初始容量一说。
3.访问方式。ArrayList是基于随机访问的,查询会比LinkedList快,但是添加和删除比LinkedLis慢。
看看LinkedList图谱:
LinkedList是队列和列表的集合,他俩的特性它都有。它由一个一个的Node链起来,是一个双向链表。
二.源码解读
1.初始化
public LinkedList() { 第一种初始化方式 }
public LinkedList(Collection<? extends E> c) { //添加一个集合来构造自己 this(); addAll(c); }
public boolean addAll(Collection<? extends E> c) { return addAll(size, c); //添加 }
public boolean addAll(int index, Collection<? extends E> c) { //看看是怎么添加的 checkPositionIndex(index); //检查一下索引是否在0和队列长度之间,不然要抛出数组越界异常 Object[] a = c.toArray(); int numNew = a.length; //保存数组的长度 if (numNew == 0) //长度为0 就相当于空的LinkedList构造 return false; Node<E> pred, succ; if (index == size) { succ = null; pred = last; //pred 指向队列的尾部 } else { succ = node(index); //找到索引的节点,如果索引的index小于队列长度的一半,从前往后找也就不断的next找,反之,则往前找prev pred = succ.prev; //pred 现在指向了succ的前一个元素 } for (Object o : a) { //遍历数组 @SuppressWarnings("unchecked") E e = (E) o; Node<E> newNode = new Node<>(pred, e, null); //创建一个新的node,尾节点指向为空,前一个节点就是pred if (pred == null) //如果之前队列是空的,队列的头结点(first)就指向刚创建的节点 first = newNode; else pred.next = newNode; //一个一个的往后添加节点 pred = newNode; } if (succ == null) { //如果队列为空的话,直接last指向pred就可以了 last = pred; } else { pred.next = succ; //补上succ和后面的值 succ.prev = pred; } //简单的归纳一下就是将原链表劈成了两半,将新添加的集合拼在前一段,后面也就是succ指定的节点再拼接在链表上面。比如之前队列是a->b->c,在第二个位置插了g,h 变成了a->g->h->b->c size += numNew; //队列加上集合的个数 modCount++; //操作记录数加1 return true; }2.添加值
它的添加方法有这么多。,还有offer(e),就选offer(E)来说吧!
public boolean offer(E e) { return add(e); }
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; //队列last现在变成了这个新的节点 if (l == null) //如果是空队列,头结点也指向这个新建的节点 first = newNode; else l.next = newNode; //之前的队列不为空,队尾的下一个指向新建的节点 size++; //添加了一个,数值加一 modCount++; }
addFist(E)方法是在队列头插入,反队列设计,但是有时会有这样的设计?可以用栈替代吧!其实想想,还真有这样的需求。
如果用插入a,b,c都需要他们是顺序输出的,但是这个又有需求过来了,d要插入,并且要放在第一位。
3.出队列
出队列的方法也有很多,就拿poll来说吧。
public E poll() { final Node<E> f = first; //拿到第一个值,看看是不是空,如果是空,直接放回空就好了,不是空,就删除第一个 return (f == null) ? null : unlinkFirst(f); }
private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item; //队列第一个节点里面的值 final Node<E> next = f.next; f.item = null; f.next = null; // 方便gc,可达性分析,没有引用就是垃圾 first = next; //队列的已经指向了下一个 if (next == null) //就只有一个元素,last就是空 last = null; else next.prev = null; //第一个的next指向了null,也就是前面没有元素了 size--; //队列头减少了一个节点 modCount++; return element; }
4.获取节点
public E peek() { final Node<E> f = first; //直接拿到first,如果是空就返回null,否则返回里面元素值。LinkedList里面建了一个first,这样就可以直接拿,拿到最后一个值,是不是也很容易,把first替换成last就可以了,时间复杂度为o(1),效率提高了。 return (f == null) ? null : f.item; }
只看了peek方法,其他方法差不多。
到此,LindkedList基本源码差不多将完了,你懂了吗?
总之请记住队列的特点,先进先出(后进后出),以不变应万变!