编码如梦,望归来还是老友。
LinkedList 是一个可能会经常被问到的集合类型,大多数都是和ArrayList进行对比,有什么不同?啥啥啥的,但是真的和ArrayList相比的那些优势有彻底或者更深入一步的了解吗?主要弄明白链表和动态数组(数组)之前对保存数据和删除数据和获取数据之间的区别的时候,就会对这种问题的回答比较清晰。
来一张 LinkedList 的上层类的结构图
这个图主要是对上层比较清晰。
好啦,开始记录一下看源码的代码心得了啦。
1 : 参数的说明
// 长度的变量 transient int size = 0;
// 第一个节点
transient Node<E> first;
// 最后一个节点
transient Node<E> last;
2 : 构造方法说明
// 无参构造方法,也就是什么都没有特别的做。 public LinkedList() { }
// 传入集合的构造方法; 先调用上面的无参构造方法;然后调用addAll()方法
public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
3 : 方法的介绍
// 检查是否越界 private boolean isPositionIndex(int index) { return index >= 0 && index <= size; }
// 调用上面的方法进行判断是否越界 private void checkPositionIndex(int index) { if (!isPositionIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
// 在链表的末尾插上元素 void linkLast(E e) { // 尾部的拿出来,用一个新的节点变量来进行存储 final Node<E> l = last; // 用尾节点值 传入进来的e值 用内部类来生成新对象Node final Node<E> newNode = new Node<>(l, e, null); // 新对象赋值给最后尾节点 last = newNode; // 如果之前尾节点是null的话,first节点给赋值上新的节点 if (l == null) first = newNode; else // 否则就指向下一个节点 l.next = newNode; // 长度 ++ size++; modCount++; }
// 传入 E 值 和 一个节点 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节点指向新的节点 succ.prev = newNode; // succ 最初的prev判断是否是null;如果是null的话,就赋值给first;否则的话,就用pred.next指向给新的节点 if (pred == null) first = newNode; else pred.next = newNode; // 长度 ++ size++; modCount++; } // 添加前判断 checkPositionIndex 传入进来的索引是否越界;然后根据index的大小判断是否等于长度的大小,然后来调用不同的方法 public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); }
// node方法 Node<E> node(int index) { // assert isElementIndex(index); // size 右移1;然后判断是否大于index if (index < (size >> 1)) { // 大于index 使用x 来储存first节点信息 Node<E> x = first; // 遍历,x -> x.next 让自身的下一个节点赋值给自己 for (int i = 0; i < index; i++) x = x.next; return x; } else { // 用 x 存储最后一个节点 Node<E> x = last; // 倒叙遍历,返回prev节点的信息 for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
// set 方法相对于add 方法;就没那么复杂了 public E set(int index, E element) { // 先检查index是否越界和符合要求 checkElementIndex(index); // 调用node方法来获取Node信息 Node<E> x = node(index); // 获取节点的值 oldVal E oldVal = x.item; x.item = element; return oldVal; } // 根据index 获取值;先检查index是否越界等;然后调用node()方法 这里主要 node()是遍历的;时间复杂度是O(n);而数组的话是直接根据下标获取;时间复杂度是O(1)也就是常数 public E get(int index) { checkElementIndex(index); return node(index).item; }
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; }
// 获取第一个节点的值;先将第一个节点值给 f ;然后判断f 是不是null ; 如果是null 就抛出异常,如果不是的话;就调用iten属性 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; }
4: 内部类说明
类 Node 就是存储数据的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; } }还有其他的三个内部类;感兴趣的话;可以自己研究下。