四、域的解读
// LinkedList节点个数 transient int size = 0; /** * Pointer to first node. 指向头结点 * Invariant: (first == null && last == null) || * (first.prev == null && first.item != null) */ transient Node<E> first; /** * Pointer to last node. 指向尾节点 * Invariant: (first == null && last == null) || * (last.next == null && last.item != null) */ transient Node<E> last;
LinkedList内部有两个引用,一个first,一个last,分别用于指向链表的头和尾,另外有一个size,用于标识这个链表的长度,而它的接的引用类型是Node,这是他的一个内部类:
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; } }
Node 类是LinkedList中的私有内部类,LinkedList中就是通过Node来存储集合中的元素。
E :节点的值。
Node next:当前节点的后一个节点的引用(可以理解为指向当前节点的后一个节点的指针)
Node prev:当前节点的前一个节点的引用(可以理解为指向当前节点的前一个节点的指针)
五、构造方法
LinkedList提供了两个构造器,ArrayList比它多提供了一个通过设置初始化容量来初始化类。
LinkedList不提供该方法的原因:因为LinkedList底层是通过链表实现的,每当有新元素添加进来的时候,都是通过链接新的节点实现的,也就是说它的容量是随着元素的个数的变化而动态变化的。而ArrayList底层是通过数组来存储新添加的元素的,所以我们可以为ArrayList设置初始容量(实际设置的数组的大小)。
相关阅读:【手撕源码系列】ArrayList源码解读—Java8版本
5.1 空参构造函数
/** * Constructs an empty list. */ public LinkedList() { }
5.2 collection参数构造函数
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) { // 1.添加位置的下标的合理性检查 checkPositionIndex(index); // 2.将集合转换为Object[]数组对象 Object[] a = c.toArray(); int numNew = a.length; if (numNew == 0) return false; // 3.得到插入位置的前继节点和后继节点 Node<E> pred, succ; if (index == size) { // 从尾部添加的情况:前继节点是原来的last节点;后继节点是null succ = null; pred = last; } else { // 从指定位置(非尾部)添加的情况:前继节点就是index位置的节点,后继节点是index位置的节点的前一个节点 succ = node(index); pred = succ.prev; } // 4.遍历数据,将数据插入 for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; // 创建节点 Node<E> newNode = new Node<>(pred, e, null); if (pred == null) // 空链表插入情况: first = newNode; else // 非空链表插入情况: pred.next = newNode; // 更新前置节点为最新插入的节点(的地址) pred = newNode; } if (succ == null) { // 如果是从尾部开始插入的,则把last置为最后一个插入的元素 last = pred; } else { // 如果不是从尾部插入的,则把尾部的数据和之前的节点连起来 pred.next = succ; succ.prev = pred; } size += numNew; // 链表大小+num modCount++; // 修改次数加1 return true; }
六、核心方法
6.1 List接口
6.1.1 add(E e)方法
// 作用:将元素添加到链表尾部 public boolean add(E e) { linkLast(e); return true; } void linkLast(E e) { final Node<E> l = last; // 获取尾部元素 final Node<E> newNode = new Node<>(l, e, null); // 以尾部元素为前继节点创建一个新节点 last = newNode; // 更新尾部节点为需要插入的节点 if (l == null) // 如果空链表的情况:同时更新first节点也为需要插入的节点。(也就是说:该节点既是头节点first也是尾节点last) first = newNode; else // 不是空链表的情况:将原来的尾部节点(现在是倒数第二个节点)的next指向需要插入的节点 l.next = newNode; size++; // 更新链表大小和修改次数,插入完毕 modCount++; }