JDK核心JAVA源码解析(7)

Wesley13
• 阅读 692

想写这个系列很久了,对自己也是个总结与提高。原来在学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 first;

    /**

    • Pointer to last node.

    */ transient Node last;

  • get(int index)等所有涉及到下标操作的方法,由于需要遍历,复杂度都是O(N/2).实现遍历的核心方法是:

    Node node(int index) { //看index是否大于size一半或者小于,决定从链表头遍历还是末尾遍历 if (index < (size >> 1)) { Node x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node 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 x = last; x != null; x = x.prev) { if (x.item == null) { unlink(x); return true; } } } else { for (Node 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更有优势。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
JDK核心JAVA源码解析(1)
想写这个系列很久了,对自己也是个总结与提高。原来在学JAVA时,那些JAVA入门书籍会告诉你一些规律还有法则,但是用的时候我们一般很难想起来,因为我们用的少并且不知道为什么。知其所以然方能印象深刻并学以致用。首先我们从所有类的父类Object开始:1\.Object类(1)hashCode方法和equals方法
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
JDK核心JAVA源码解析(4)
想写这个系列很久了,对自己也是个总结与提高。原来在学JAVA时,那些JAVA入门书籍会告诉你一些规律还有法则,但是用的时候我们一般很难想起来,因为我们用的少并且不知道为什么。知其所以然方能印象深刻并学以致用。本篇文章针对堆外内存与DirectBuffer进行深入分析,了解Java对于堆外内存处理的机制,为下一篇文件IO做好准备Java堆栈内存与堆外内
Wesley13 Wesley13
3年前
JDK核心JAVA源码解析(8)
想写这个系列很久了,对自己也是个总结与提高。原来在学JAVA时,那些JAVA入门书籍会告诉你一些规律还有法则,但是用的时候我们一般很难想起来,因为我们用的少并且不知道为什么。知其所以然方能印象深刻并学以致用。本文基于Java14在JDK1.5引入自动装箱/拆箱,让开发更高效。自动装箱时编译器调用valueOf()将原始类型值转换成对象,同
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这