LinkedList (可参考)
概括:
- LinkedList是一个实现了List接口和Deque接口的双端链表。与ArrayList相比,ArrayList直接继承自AbstractList,而LinkedList继承自AbstractSequentialList,然后再继承自AbstractList。另外LinkedList没有实现RandomAccess接口,说明它不具备随机快速访问任一元素的能力,它访问元素时需要通过遍历节点来获取,至于从链头还是链尾取决于所要访问的元素的位置
- LinkedList实现了Deque接口(即双端队列)。双端队列是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出。
- LinkedList允许元素为null。
- LinkedList与索引相关的操作可能从链表头开始遍历到链表尾部,也可能从尾部遍历到链表头部,这取决于看索引更靠近哪一端,所以它改查效率低,增删效率高,这点和ArrayList是相反的。
- 它是线程不安全的,要想实现LinkedList为线程安全的,可通过
List<String> list = Collections.synchronizedList(new LinkedList<>());
实现
全局说明:第 index 个节点指的是下标为 index-1 处的节点
注意点:
- (1)LinkedList中的每个元素存储在Node节点中,Node是LinkedList类的内部类,LinkedList类中只有三个属性,分别是size(node节点数)、first(指向首个节点)、last(指向最后一个节点)。只要记录着首个节点和最后一个节点,便可以根据其找到其他中间节点
- (2)LinkedList的构造方法有两个
其中LinkedList(Collection<? extends E> c)
构造方法内调用的是addAll方法,整个方法其实就是如何操作链表的具体实现。该方法的详细解读如下/** * 往下标为index处插入多个新节点,比如index=1时,说明是往第一个节点后面插入新节点 * @throws IndexOutOfBoundsException {@inheritDoc} * @throws NullPointerException if the specified collection is null */ public boolean addAll(int index, Collection<? extends E> c) { // 校验index是否大于当前链表的总节点数,会则抛出IndexOutOfBoundsException异常 checkPositionIndex(index); // 将集合转换为数组,若集合的大小为0,则返回false Object[] a = c.toArray(); int numNew = a.length; if (numNew == 0) return false; // succ代表第index个节点的下一节点,pred代表第index个节点 Node<E> pred, succ; // 代表往链尾插入多个新节点,succ为null,pred为链尾 if (index == size) { succ = null; pred = last; } else { /** * 代表往链表中间插入新节点,所以succ代表第index个节点的下一节点,pred代表第index个节点 */ succ = node(index); pred = succ.prev; } // 遍历即将插入的新节点数组 for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; // 将当前遍历的节点的prev属性指向前节点,若当前遍历的节点插入的位置为链头,则pred为null Node<E> newNode = new Node<>(pred, e, null); // 第一次遍历时,pred可能为null,pred为null表示当前遍历的节点是链头 if (pred == null) first = newNode; else // 将第index个节点的next属性指定指向当前遍历的节点 pred.next = newNode; // 将pred置为当前遍历的节点 pred = newNode; } // pred代表遍历到的最后一个节点 // 只有当往链尾插入新节点时,succ才会为null,则pred为链尾,将last指向该节点 if (succ == null) { last = pred; } else { // 否则说明是在链中间插入新节点,则件将 pred 的 next属性指向 起初第index个节点的下一个节点 // 起初第index个节点的下一个节点的prev属性指向 pred pred.next = succ; succ.prev = pred; } size += numNew; modCount++; return true; }
- (3)linkedlist的添加操作实现较为简单,其中 add 和 addLast 方法的实现是一样的
// 添加新节点,默认是往链尾添加 public boolean add(E e) { linkLast(e); return true; } // 往第 index 个节点后添加新节点 public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); } // 往链头添加新节点 public void addFirst(E e) { linkFirst(e); } // 往链尾添加新节点,和 add 方法一样 public void addLast(E e) { linkLast(e); } // 将新节点绑定到链头 private void linkFirst(E e) { final Node<E> f = first; final Node<E> newNode = new Node<>(null, e, f); first = newNode; if (f == null) last = newNode; else f.prev = newNode; size++; modCount++; } // 将新节点绑定到链尾 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++; } // 将新节点绑定到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++; }
- (4)linkedlist删除节点
linkedlist删除节点底层调用到的方法是 unlinkFirst、unlinkLast和unlink**。源码如下**
// 移除第一个节点 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; // help GC first = next; if (next == null) last = null; else next.prev = null; size--; modCount++; return element; } // 移除最后一个节点 private E unlinkLast(Node<E> l) { // assert l == last && l != null; final E element = l.item; final Node<E> prev = l.prev; l.item = null; l.prev = null; // help GC last = prev; if (prev == null) first = null; else prev.next = null; size--; modCount++; return element; } // 移除某个节点 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; if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }
- (5)linkedlist是通过逐个遍历的方式获取元素的,不过LinkedList在遍历开始前会根据所要遍历的元素的位置来判断从链头还是链尾开始遍历,源码如下
public E get(int index) { // 校验index是否大于链长 checkElementIndex(index); // 获取第 index+1 个节点 return node(index).item; } /** * 根据index返回节点,index为0返回链头节点,index为1返回第二个节点 */ Node<E> node(int index) { // 判断index是否小于size的一半,是则从链头遍历 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; } }
- (6)linkedlist的clear方法是将节点中的属性置为null,方便gc回收,同时将LinkedList中的first和last节点置为null
public void clear() { for (Node<E> x = first; x != null; ) { Node<E> next = x.next; x.item = null; x.next = null; x.prev = null; x = next; } first = last = null; size = 0; modCount++; }
-
(7)linkedlist实现了Deque接口,使其具备了双端队列的属性。双端队列是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出。
-
(8)linkedlist的first节点和last节点为什么都加上了transient关键字,这样在对LinkedList进行序列化和反序列化时不就无法将元素信息保存起来吗?
答:其实LinkedList中重写了writeObject和readObject的方法,当对LinkedList进行序列化和反序列化时将分别调用重写了的writeObject和readObject方法