前言:之前写了HashMap 和 ArrayList,还差 LinkedList 和 HashTable 没有学习,这篇学习下LinkedList。
PS: 源码分析针对于Android O 的jdk,即jdk1.8
同事推荐的流程图画图网站:https://www.processon.com/
demo地址:jiatai的demo
1. LinkedList简单介绍
LinkedList从实现方面用的数据结构是双向链表,它也可以被当作堆栈、队列或双端队列进行操作,不需要考虑扩容。
2. LinkedList简单使用
简单的往LinkedList里面塞数字0-9:
package com.example.demo_27_linkedlist; import java.util.LinkedList; public class MyClass { public static void main(String[] args){ LinkedList linkedList = new LinkedList<Integer>(); linkedList.add(0); for (int i = 1; i < 10; i++) { linkedList.add(i); } System.out.print(linkedList); } }
对应log打印:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
3. 简单使用所对应的内在
上面的使用主要涉及了三个方法
- 构造方法
- add()
- toString()
下面来简单地看下内在=-=
3.1 无参构造方法
很光棍,没啥看的。
/** * Constructs an empty list. */ public LinkedList() { }
3.2 add(E e)
/** * Appends the specified element to the end of this list. * * <p>This method is equivalent to {@link #addLast}. * * @param e element to be appended to this list * @return {@code true} (as specified by {@link Collection#add}) */ public boolean add(E e) { linkLast(e); return true; }
/** * Links e as last element. */ 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++; }
从上面的代码可以看出,使用add(E e)往LinkedList里添加元素的时候,会缓存一个last成员变量,实时记录上一次添加的元素,即链表的尾巴,添加完成后,更新链表的尾巴为新添加的元素。当链表为空的时候,last自然是null,所以第一个添加的元素即为first也为last。
这里再关注一下Node的构造方法,传入了last/E/null,分别对应prev、element和next。
来看下源码,一个简单的双向链表静态内部类:
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是个双向链表。
3.3 toString()
和ArrayList 一样的,使用了继承自父类AbstractCollection的toString方法。
/** * Returns a string representation of this collection. The string * representation consists of a list of the collection's elements in the * order they are returned by its iterator, enclosed in square brackets * (<tt>"[]"</tt>). Adjacent elements are separated by the characters * <tt>", "</tt> (comma and space). Elements are converted to strings as * by {@link String#valueOf(Object)}. * * @return a string representation of this collection */ public String toString() { Iterator<E> it = iterator(); if (! it.hasNext()) return "[]"; StringBuilder sb = new StringBuilder(); sb.append('['); for (;;) { E e = it.next(); sb.append(e == this ? "(this Collection)" : e); if (! it.hasNext()) return sb.append(']').toString(); sb.append(',').append(' '); } }
这里贴一下自己画的继承关系图
3.4 get(int dex)
// Positional Access Operations /** * Returns the element at the specified position in this list. * * @param index index of the element to return * @return the element at the specified position in this list * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { checkElementIndex(index); return node(index).item; }
/** * Returns the (non-null) Node at the specified element index. */ 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; } }
linkedList的get方法相比ArrayList效率肯定是低不少的,从上面可以看出linkedList是做了优化的,如果是前一半就从first开始遍历,如果是后一半就从last开始往前遍历,而ArrayList不需要这么遍历,直接就可以获取对应index的元素了。
3.5 add(int index, E element)
/** * Inserts the specified element at the specified position in this list. * Shifts the element currently at that position (if any) and any * subsequent elements to the right (adds one to their indices). * * @param index index at which the specified element is to be inserted * @param element element to be inserted * @throws IndexOutOfBoundsException {@inheritDoc} */ public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); }
/** * Inserts element e before non-null Node succ. */ void linkBefore(E e, Node<E> succ) { // assert succ != null; final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; }
add的过程其实包含了get和重新链接的过程,先找到对应index的链表元素,然后将新插入元素放在该元素前面,重新处理前后的链接关系。比ArrayList的劣势在于get的劣势,而插入就很容易了,第一不要考虑扩容,第二不需要挪一下后面所有元素。在元素很多的情况下显然LinkedList效率会更高。
4. 总结
就如我们一直所知LinkedList是个双向链表,与ArrayList相比,获取指定位置的元素效率较低,但是插入和移除元素的效率是更高的。