iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >如何理解Java容器中ArrayList的源码分析
  • 897
分享到

如何理解Java容器中ArrayList的源码分析

2023-06-05 03:06:58 897人浏览 八月长安
摘要

这篇文章给大家介绍如何理解Java容器中List的源码分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。如果没有特别说明,以下源码分析基于 jdk 1.8。一、ArrayList1. 概览实现了 RandoMacces

这篇文章给大家介绍如何理解Java容器中List的源码分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

如果没有特别说明,以下源码分析基于 jdk 1.8。

一、ArrayList

1. 概览

实现了 RandoMaccess 接口,因此支持随机访问。这是理所当然的,因为 ArrayList 是基于数组实现的。

public class ArrayList<E> extends AbstractList<E>        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

数组的默认大小为 10。

private static final int DEFAULT_CAPACITY = 10;

2. 扩容

添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容, 新容量的大小为 oldCapacity + (oldCapacity >> 1),也就是旧容量的 1.5 倍。

扩容操作需要调用 Arrays.copyOf() 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。

public boolean add(E e) {    //添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,    ensureCapacityInternal(size + 1);  // Increments modCount!!    elementData[size++] = e;    return true;}private void ensureCapacityInternal(int minCapacity) {    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);    }    ensureExplicitCapacity(minCapacity);}private void ensureExplicitCapacity(int minCapacity) {    modCount++;    // overflow-conscious code    if (minCapacity - elementData.length > 0)        grow(minCapacity);}// grow() 方法进行扩容private void grow(int minCapacity) {    // overflow-conscious code    int oldCapacity = elementData.length;    int newCapacity = oldCapacity + (oldCapacity >> 1);    if (newCapacity - minCapacity < 0)        newCapacity = minCapacity;    if (newCapacity - MAX_ARRAY_SIZE > 0)        newCapacity = hugeCapacity(minCapacity);    // minCapacity is usually close to size, so this is a win:    //这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。    elementData = Arrays.copyOf(elementData, newCapacity);}

3. 删除元素

需要调用 System.arraycopy() 将 index+1 后面的元素都向左移动一位,该操作的时间复杂度为 O(N),可以看出 ArrayList 删除元素的代价是非常高的。

public E remove(int index) {    rangeCheck(index);    modCount++;    E oldValue = elementData(index);    //index+1 后面的元素都向左移动一位 即index+1位置的后面元素个数 (size-1)-(index+1)+1    int numMoved = size - index - 1;    if (numMoved > 0)        //将 index+1后面的元素都向左移动一位,原来的 (index+1)位置元素就移到 index位置        System.arraycopy(elementData, index+1, elementData, index, numMoved);    elementData[--size] = null; // clear to let GC do its work    return oldValue;}

4. Fail-Fast

modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。

在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变, 如果改变了需要抛出 ConcurrentModificationException。

private void writeObject(java.io.ObjectOutputStream s)    throws java.io.IOException{    // Write out element count, and any hidden stuff    //这里 记录操作前的 modCount    int expectedModCount = modCount;    s.defaultWriteObject();    // Write out size as capacity for behavioural compatibility with clone()    s.writeInt(size);    // Write out all elements in the proper order.    for (int i=0; i<size; i++) {        s.writeObject(elementData[i]);//操作    }    //这里的modCount是操作后的 modCount与之前的作比较    if (modCount != expectedModCount) {        throw new ConcurrentModificationException();    }}

5. 序列化

ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。

保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。

transient Object[] elementData; // non-private to simplify nested class access

ArrayList 实现了 writeObject() 和 readObject() 来只序列化数组中有元素填充那部分内容。

序列化时需要使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。

private void writeObject(java.io.ObjectOutputStream s)    throws java.io.IOException{    // Write out element count, and any hidden stuff    int expectedModCount = modCount;    s.defaultWriteObject();    // Write out size as capacity for behavioural compatibility with clone()    s.writeInt(size);    // Write out all elements in the proper order.    for (int i=0; i<size; i++) {        // 使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。        s.writeObject(elementData[i]);    }    if (modCount != expectedModCount) {        throw new ConcurrentModificationException();    }}

反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理类似。

private void readObject(java.io.ObjectInputStream s)    throws java.io.IOException, ClassNotFoundException {    elementData = EMPTY_ELEMENTDATA;    // Read in size, and any hidden stuff    s.defaultReadObject();    // Read in capacity    s.readInt(); // ignored    if (size > 0) {        // be like clone(), allocate array based upon size not capacity        //根据size来分配内存,来控制只序列化数组中有元素填充那部分内容        ensureCapacityInternal(size);        Object[] a = elementData;        // Read in all elements in the proper order.        for (int i=0; i<size; i++) {            // 使用的是 ObjectInputStream 的 readObject() 方法进行反序列化            a[i] = s.readObject();        }    }}

6.System.arraycopy()和Arrays.copyOf()方法

Arrays.copyOf() 的源代码内部调用了 System.arraycopy() 方法。

  • System.arraycopy() 方法需要目标数组,将原数组拷贝到目标数组里,而且可以选择拷贝的起点和长度以及放入新数组中的位置;

  • Arrays.copyOf() 是系统自动在内部创建一个数组,并返回这个新创建的数组。

二、Vector

1. 同步

它的实现与 ArrayList 类似,但是使用了 synchronized 进行同步。

public synchronized boolean add(E e) {    modCount++;    ensureCapacityHelper(elementCount + 1);    elementData[elementCount++] = e;    return true;}public synchronized E get(int index) {    if (index >= elementCount)        throw new ArrayIndexOutOfBoundsException(index);    return elementData(index);}

2. 与 ArrayList 的比较

  • Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。 最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;

  • Vector 每次扩容新容量是旧容量的 2 倍空间,而 ArrayList 是 1.5 倍。

3. 替代方案

可以使用 Collections.synchronizedList(); 得到一个线程安全的 ArrayList。

List<String> list = new ArrayList<>();List<String> synList = Collections.synchronizedList(list);

也可以使用 java.util.concurrent 并发包下的 CopyOnWriteArrayList 类。

List<String> list = new CopyOnWriteArrayList<>();

三、LinkedList

1. 概览

基于双向链表实现,使用 node 存储链表节点信息。

private static class Node<E> {    E item;    Node<E> next;    Node<E> prev;}

每个链表存储了 first 和 last 指针:

transient Node<E> first;transient Node<E> last;

如何理解Java容器中ArrayList的源码分析

2. 添加元素

将元素添加到链表尾部

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 = newNode;    else        l.next = newNode;//指向后继元素也就是指向下一个元素    size++;    modCount++;}

将元素添加到链表头部

public void addFirst(E e) {    linkFirst(e);}
private void linkFirst(E e) {    final Node<E> f = first;    final Node<E> newNode = new Node<>(null, e, f);//新建节点,以头节点为后继节点    first = newNode;    //如果链表为空,last节点也指向该节点    if (f == null)        last = newNode;    //否则,将头节点的前驱指针指向新节点,也就是指向前一个元素    else        f.prev = newNode;    size++;    modCount++;}

3. 删除指定元素

public boolean remove(Object o) {    //如果删除对象为null    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;}
E unlink(Node<E> x) {    // assert x != null;    final E element = x.item;    final Node<E> next = x.next;//得到后继节点    final Node<E> prev = x.prev;//得到前驱节点        //如果删除的节点是头节点,直接删除该头结点    if (prev == null) {        first = next;     } else {        prev.next = next;//将前驱节点的后继节点指向后继节点        x.prev = null; //TODO:十分重要    }        //如果删除的节点是尾节点,直接删除该尾节点    if (next == null) {        last = prev;    } else {        next.prev = prev;        x.next = null;    }    x.item = null;    size--;    modCount++;    return element;}

4. 与 ArrayList 的比较

  • ArrayList 基于动态数组实现,LinkedList 基于双向链表实现;

  • ArrayList 支持随机访问,LinkedList 不支持;

  • LinkedList 在任意位置添加删除元素更快。

关于如何理解Java容器中List的源码分析就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

--结束END--

本文标题: 如何理解Java容器中ArrayList的源码分析

本文链接: https://www.lsjlt.com/news/241071.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何理解Java容器中ArrayList的源码分析
    这篇文章给大家介绍如何理解Java容器中List的源码分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。如果没有特别说明,以下源码分析基于 JDK 1.8。一、ArrayList1. 概览实现了 RandomAcces...
    99+
    2023-06-05
  • 如何理解Java容器中Map的源码分析
    本篇文章为大家展示了如何理解Java容器中Map的源码分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。如果没有特别说明,以下源码分析基于 JDK 1.8。一、HashMap为了便于理解,以下源码分...
    99+
    2023-06-05
  • 如何理解Java 容器中并发容器的源码分析
    这期内容当中小编将会给大家带来有关如何理解Java 容器中并发容器的源码分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。如果没有特别说明,以下源码分析基于 JDK 1.8。CopyOnWriteArra...
    99+
    2023-06-05
  • 如何理解ArrayList源码
    本篇内容主要讲解“如何理解ArrayList源码”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何理解ArrayList源码”吧!ArrayList类图如下:A...
    99+
    2024-04-02
  • java 中Buffer源码的分析
    java 中Buffer源码的分析BufferBuffer的类图如下:除了Boolean,其他基本数据类型都有对应的Buffer,但是只有ByteBuffer才能和Channel交互。只有ByteBuffer才能产生Direct的buffe...
    99+
    2023-05-31
    java buffer源码 buf
  • Java中的CyclicBarrier源码分析
    这篇文章主要介绍了Java中的CyclicBarrier源码分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java中的CyclicBarrier源码分析文章都会有所收获,下面我们一起来看看吧。CyclicB...
    99+
    2023-06-30
  • Java ConcurrentHashMap的源码分析详解
    目录概述ForwardingNode节点TreeNodeTreeBinSizeCtl初始化初始化流程查找插入扩容红黑树的读&写读操作写操作小结容器计数总结概述 Concurr...
    99+
    2023-03-02
    Java ConcurrentHashMap源码 Java ConcurrentHashMap
  • 源码分析Java中ThreadPoolExecutor的底层原理
    目录一、根据代码查看jdk提供的3种线程池创建二、3种方式源码分析1、Executors.newCachedThreadPool()2、Executors.newFixedThrea...
    99+
    2023-05-19
    Java ThreadPoolExecutor原理 Java ThreadPoolExecutor
  • Java源码解析之ConcurrentHashMap的示例分析
    小编给大家分享一下Java源码解析之ConcurrentHashMap的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!早期 ConcurrentHashMap,其实现是基于:分离锁,也就是将内部进行分段(Segme...
    99+
    2023-06-15
  • Java中Handler源码的示例分析
    这篇文章主要介绍了Java中Handler源码的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。从很早开始就认识到 Handler 了,只不过那时修为尚浅,了解的不够深...
    99+
    2023-06-02
  • java中CopyOnWriteArrayList源码的示例分析
    这篇文章将为大家详细讲解有关java中CopyOnWriteArrayList源码的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。简介CopyOnWriteArrayList是ArrayList的...
    99+
    2023-06-29
  • 如何进行HashMap扩容机制源码分析
    这期内容当中小编将会给大家带来有关如何进行HashMap扩容机制源码分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。具体看源码之前,我们先简单的说一下HashMap的底层数据结构  1、HashMap底...
    99+
    2023-06-02
  • 如何用源码分析Java HashMap实例
    本篇文章为大家展示了如何用源码分析Java HashMap实例,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。引言HashMap在键值对存储中被经常使用,那么它到底是如何实现键值存储的呢?一 Entr...
    99+
    2023-06-17
  • Java定时器Timer的源码分析
    目录一、TimerTask1. 任务状态2. 任务属性说明3. 任务方法说明二、Timer1. sched方法2. cancel方法3. purge方法三、TaskQueue四、Ti...
    99+
    2022-11-13
    Java Timer源码 Java Timer定时器 Java Timer
  • Java详解HashMap实现原理和源码分析
    目录学习要点:1、什么是HashMap?2、HashMap的特性3、HashMap的数据结构4、HashMap初始化操作4.1、成员变量4.2、 构造方法5、Jdk8中HashMap...
    99+
    2024-04-02
  • 如何解析hanlp源码中文分词算法
    如何解析hanlp源码中文分词算法,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。  解析hanlp源码中文分词算法。词图指的是...
    99+
    2024-04-02
  • Libtask源码解析之如何理解锁
    这篇文章主要讲解了“Libtask源码解析之如何理解锁”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Libtask源码解析之如何理解锁”吧!libtask中...
    99+
    2024-04-02
  • Java异步编程中如何进行FutureTask源码分析
    本篇文章给大家分享的是有关Java异步编程中如何进行FutureTask源码分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Java的异步编程是一项非常常用的多线程技术。但之...
    99+
    2023-06-19
  • Java源码解析之接口Collection的示例分析
    小编给大家分享一下Java源码解析之接口Collection的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一、图示二、方法定义我们先想一想,公司如果要我...
    99+
    2023-06-15
  • Vue中AST源码解析的示例分析
    这篇文章主要介绍Vue中AST源码解析的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!从这个函数开始的:// Line-3924  Vue.prototy...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作