LinkedList 底层数据结构是一个双向链表,总体结构以下图所示:
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; } }
新增节点时,咱们能够选择追加到链表头部,仍是追加到链表尾部,add 方法默认是从尾部开始追加,addFirst 方面试
法是从头部开始追加:算法
/** * 将元素添加到链表尾部,等于addFirst */ public boolean add(E e) { linkLast(e); return true; }
/** * 从尾部开始追加节点 */ void linkLast(E e) { // 把尾节点数据暂存 final Node<E> l = last; // 新建新的节点,初始化入参含义: // l 是新节点的前一个节点,当前值是尾节点值 // e 表示当前新增节点,当前新增节点后一个节点是 null final Node<E> newNode = new Node<>(l, e, null); // 将新建节点追加到尾部 last = newNode; //若是链表为空 l 是尾节点,尾节点为空,链表即空,头部和尾部是同一个节点,都是新建的节点 if (l == null) first = newNode; else //不然把前尾节点的下一个节点,指向当前尾节点。 l.next = newNode; //大小和版本更改 size++; modCount++; }
/** *从头部开始追加节点 */ private void linkFirst(E e) { // 把头节点数据暂存 final Node<E> f = first; // 新建新的节点,初始化入参含义: // l 是新节点的前一个节点,当前值是尾节点值 // f 表示当前新增节点,当前新增节点后一个节点是 null final Node<E> newNode = new Node<>(null, e, f); // 将新建节点追加到头部 first = newNode; //若是链表为空 f 是头节点,头节点为空,链表即空,头部和尾部是同一个节点,都是新建的节点 if (f == null) last = newNode; else //上一个头节点的前一个节点指向当前节点 f.prev = newNode; size++; modCount++; }
LinkedList节点删除的方式和追加相似,咱们能够选择从头部删除,也能够选择从尾部删除,删除操做会把节点的值,先后指spring
向节点都置为 null。数据库
/** * 从头部开始删除节点 */ private E unlinkFirst(Node<E> f) { //拿出头节点的值,做为方法的返回值 final E element = f.item; // 拿出头节点的下一个节点 final Node<E> next = f.next; //帮助 GC 回收头节点 f.item = null; f.next = null; //头节点的下一个节点成为头节点 first = next; //若是 next 为空,代表链表为空 if (next == null) last = null; else //链表不为空,头节点的前一个节点指向 null 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; }
/** * 根据链表索引位置查询节点 */ Node<E> node(int index) { // 若是 index 处于队列的前半部分,从头开始找,size >> 1 是 size 除以 2 的意思。 if (index < (size >> 1)) { Node<E> x = first; // 直到 for 循环到 index 的前一个 node 中止 for (int i = 0; i < index; i++) x = x.next; return x; } else { // 若是 index 处于队列的后半部分,从尾开始找 Node<E> x = last; // 直到 for 循环到 index 的后一个 node 中止 for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
LinkedList 并无采用从头循环到尾的作法,而是采起了二分法,首先看 index 是在链表的前半部分,仍是后半部分。若是是前半部分,就从头开始寻找,反之亦然。经过这种方式,使循环的次数 至少下降了一半,提升了查找的性能。设计模式
get() 获取第几个元素,依次遍历,复杂度O(n)
add(E) 添加到末尾,复杂度O(1)
add(index, E) 添加第几个元素后,须要先查找到第几个元素,直接指针指向操做,复杂度O(n) (这个比较容易想错)
remove()删除元素,直接指针指向操做,复杂度O(1)数组
只有当 LinkedList做为共享变量时,才会有线程安全问题,当 LinkedList是方法内的局部变量时,是没有线程安全的问题的。缓存
LinkedList有线程安全问题的缘由,是由于 LinkedList自身的 size、modConut 在进行各类操做时,都没有加锁,并且这些变量的类型并不是是可见(volatile)的,因此若是多个线程对这些变量进行操做时,可能会有值被覆盖的状况。安全
类注释中推荐使用 Collections#synchronizedList 来保证线程安全,SynchronizedList 是经过在每一个方法上面加上锁来实现,虽然实现了线程安全,可是性能大大下降,具体实现源码:数据结构
public boolean add(E e) { synchronized (mutex) {return c.add(e);} }
咱们也可使用ConcurrentLinkedQueue来保证线程安全,
LinkedList的底层是链表结构 ,适用于适合于常常新增和删除的场景, 欢迎关注公众号:前程有光,领取一线大厂Java面试题总结+各知识点学习思惟导+一份300页pdf文档的Java核心知识点总结! 这些资料的内容都是面试时面试官必问的知识点,篇章包括了不少知识点,其中包括了有基础知识、Java集合、JVM、多线程并发、spring原理、微服务、Netty 与RPC 、Kafka、日记、设计模式、Java算法、数据库、Zookeeper、分布式缓存、数据结构等等。