JDK核心JAVA源码解析(7)- 集合相关(1) - LinkedList

简介: JDK核心JAVA源码解析(7)- 集合相关(1) - LinkedList

想写这个系列很久了,对自己也是个总结与提高。原来在学JAVA时,那些JAVA入门书籍会告诉你一些规律还有法则,但是用的时候我们一般很难想起来,因为我们用的少并且不知道为什么。知其所以然方能印象深刻并学以致用。

本篇文章针对JAVA中集合类LinkedList进行分析,通过代码解释Java中的Fail-fast设计思想,以及LinkedList底层实现和与ArrayList对比下的就业场景。

本文会通过QA的方式行文

本文基于JDK 11


1. LinkedList实际结构是什么样子的?是单向链表还是双向?


是双向链表。LinkedList不光实现了List接口,还实现了Deque接口。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable


2. 为何LinkedList更加耗费空间?


因为要实现双向链表,双向队列的机制,同时可以存储null值(也就是有额外的引用变量指向实际的节点数据),每个节点都是由三个引用组成:

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;
    }
}

这样,存储占用了更多空间。


3. LinkedList有哪些基本操作,复杂度如何?


  • 除了基本的add(E element)还有addAll(Collection<E> elements)以外,还有addFisrt(E element)addLast(E element)可以使用。由于LinkedList而外维护了指向头结点还有尾节点的指针,所以复杂度都是O(1)
/**
* Pointer to first node.
*/
//由于这些没必要序列化,所以加上transient
transient Node<E> first;
/**
* Pointer to last node.
*/
transient Node<E> last;
  • get(int index)等所有涉及到下标操作的方法,由于需要遍历,复杂度都是O(N/2).实现遍历的核心方法是:
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;
   }
}
  • add(E element)类似,remove(E element)也有对应的removeFirst()removeLast()存在(对于空的链表会抛出NoSuchElementException),并且由于LinkedList而外维护了指向头结点还有尾节点的指针,所以复杂度都是O(1)。但是remove(Object o)的复杂度是O(N),因为是从开头遍历寻找第一个equals返回true的。同时还有removeFirstOccurrence(Object o)(和remove等价)和removeLastOccurrence(Object o)方法。
public boolean removeLastOccurrence(Object o) {
   //对于null的值,直接寻找为null的,否则通过调用equals判断是否相等
   if (o == null) {
       for (Node<E> x = last; x != null; x = x.prev) {
           if (x.item == null) {
               unlink(x);
               return true;
           }
       }
   } else {
       for (Node<E> x = last; x != null; x = x.prev) {
           if (o.equals(x.item)) {
               unlink(x);
               return true;
           }
       }
   }
   return false;
}
  • 基本队列操作也有。poll()pop()效果相同,都是将队列第一个元素剔除并返回。不同的是,针对空链表,poll()返回null,pop()抛出NoSuchElementException异常(因为底层实现就是removeFirst()


4. 什么是fail-fast,集合类中是如何实现的?LinkedList如何实现的?


fail-fast指的是在可能的损害发生前,直接失败。在JAVA中的一种体现就是当某个集合的Iterator已经创建后,如果修改了集合,再访问Iterator进行遍历就会抛出ConcurrentModificationException

LinkedList和其它集合类似,通过modCount这个field实现。

所有修改都会让modCount++。生成的Iterator会记录这个modCount,如果modCount发生改变,抛出ConcurrentModificationException

private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned;
    private Node<E> next;
    private int nextIndex;
    private int expectedModCount = modCount;
    ListItr(int index) {
        // assert isPositionIndex(index);
        next = (index == size) ? null : node(index);
        nextIndex = index;
    }
    //举一个方法作为例子,其他省略...
    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (modCount == expectedModCount && nextIndex < size) {
            action.accept(next.item);
            lastReturned = next;
            next = next.next;
            nextIndex++;
        }
        checkForComodification();
    }
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

还有一些其他的扩展迭代器,例如Spliterator,也是fail-fast的


5. 如果需要对List排序,用ArrayList比较好还是LinkedList?


集合排序基本上都是基于这个方法:Collections.swap()

public static void swap(List<?> list, int i, int j) {
    final List l = list;
    l.set(i, l.set(j, l.get(i)));
}

基本都是下标操作,所以,ArrayList更好


6. 为何LinkedList非线程安全?


我们来看在末尾加元素的方法:

void linkLast(E e) {
    //假设原始last为1,新加入两个节点2,3
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    //假设这里发生了线程切换,将会发生,1->2->null之后变成1->3->null的情况
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    //++都不是线程安全的,导致size误判
    size++;
    modCount++;
}

如果想实现线程安全的list:

  • 使用Vector(适用于更新比较多)或者CopyOnWriteArrayList(适用于读取比较多)
  • 通过:
List list = Collections.synchronizedList(new LinkedList(...));

获取并发安全LinkedList


7. 为什么JDK默认的List实现是ArrayList而不是LinkedList?


在大部分场景(遍历,下标查找,排序等)下,ArrayList性能更佳并且占用存储空间小。只有下标插入删除这种场景,LinkedList更有优势。

相关文章
|
2天前
|
Java 开发框架 XML
JDK、JRE、Java SE、Java EE和Java ME有什么区别?
JDK、JRE、Java SE、Java EE和Java ME有什么区别?
|
2天前
|
数据采集 前端开发 Java
Java医院绩效考核系统源码maven+Visual Studio Code一体化人力资源saas平台系统源码
医院绩效解决方案包括医院绩效管理(BSC)、综合奖金核算(RBRVS),涵盖从绩效方案的咨询与定制、数据采集、绩效考核及反馈、绩效奖金核算到科到组、分配到员工个人全流程绩效管理;将医院、科室、医护人员利益绑定;全面激活人才活力;兼顾质量和效益、长期与短期利益;助力医院降本增效,持续改善、优化收入、成本结构。
9 0
|
3天前
|
Java
【JAVA进阶篇教学】第四篇:JDK8中函数式接口
【JAVA进阶篇教学】第四篇:JDK8中函数式接口
|
3天前
|
Java API
【JAVA进阶篇教学】第三篇:JDK8中Stream API使用
【JAVA进阶篇教学】第三篇:JDK8中Stream API使用
|
3天前
|
Java
【JAVA进阶篇教学】第二篇:JDK8中Lambda表达式
【JAVA进阶篇教学】第二篇:JDK8中Lambda表达式
|
3天前
|
Java API
【JAVA进阶篇教学】第一篇:JDK8介绍
【JAVA进阶篇教学】第一篇:JDK8介绍
|
3天前
|
监控 前端开发 Java
Java基于B/S医院绩效考核管理平台系统源码 医院智慧绩效管理系统源码
医院绩效考核系统是一个关键的管理工具,旨在评估和优化医院内部各部门、科室和员工的绩效。一个有效的绩效考核系统不仅能帮助医院实现其战略目标,还能提升医疗服务质量,增强患者满意度,并促进员工的专业成长
14 0
|
3天前
|
Java 云计算
Java智能区域医院云HIS系统SaaS源码
云HIS提供标准化、信息化、可共享的医疗信息管理系统,实现医患事务管理和临床诊疗管理等标准医疗管理信息系统的功能。优化就医、管理流程,提升患者满意度、基层首诊率,通过信息共享、辅助诊疗等手段,提高基层医生的服务能力构建和谐的基层医患关系。
18 2
|
4天前
|
Java
从源码出发:JAVA中对象的比较
从源码出发:JAVA中对象的比较
13 3
|
4天前
|
Java
JDK环境下利用记事本对java文件进行运行编译
JDK环境下利用记事本对java文件进行运行编译
13 0

推荐镜像

更多