我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

这篇文章给大家介绍如何理解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的源码分析就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

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

下载Word文档

猜你喜欢

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

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

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

本篇文章为大家展示了如何理解Java容器中Map的源码分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。如果没有特别说明,以下源码分析基于 JDK 1.8。一、HashMap为了便于理解,以下源码分
2023-06-05

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

这期内容当中小编将会给大家带来有关如何理解Java 容器中并发容器的源码分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。如果没有特别说明,以下源码分析基于 JDK 1.8。CopyOnWriteArra
2023-06-05

源码分析Java中ThreadPoolExecutor的底层原理

这篇文章主要带大家从源码分析一下Java中ThreadPoolExecutor的底层原理,文中的示例代码讲解详细,具有一定的学习价值,需要的可以参考一下
2023-05-19

从源码解析Android中View的容器ViewGroup

这回我们是深入到ViewGroup内部\,了解ViewGroup的工作,同时会阐述更多有关于View的相关知识。以便为以后能灵活的使用自定义空间打更近一步的基础。希望有志同道合的朋友一起来探讨,深入Android内部,深入理解Androi
2022-06-06

Java异步编程中如何进行FutureTask源码分析

本篇文章给大家分享的是有关Java异步编程中如何进行FutureTask源码分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Java的异步编程是一项非常常用的多线程技术。但之
2023-06-19

如何解析Python源码分析的相关操作步骤

今天就跟大家聊聊有关如何解析Python源码分析的相关操作步骤,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。Python是一种动态的脚本语言。具体的我就不多介绍了,源代码链接在这里:
2023-06-17

Java中的InputStreamReader和OutputStreamWriter源码分析_动力节点Java学院整理

InputStreamReader和OutputStreamWriter源码分析1. InputStreamReader 源码(基于jdk1.7.40)package java.io; import java.nio.charset.Cha
2023-05-31

如何理解Java容器中的设计模式

如何理解Java容器中的设计模式,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。一、迭代器模式Collection 继承了 Iterable 接口,其中的 iterator(
2023-06-05

如何分析Kubernetes中的容器网络

这篇文章将为大家详细讲解有关如何分析Kubernetes中的容器网络,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。| 前言随着云计算的兴起,各大平台之争也落下了帷幕,Kubernetes作为
2023-06-04

如何用JVM源码分析Java对象的创建过程

这篇文章将为大家详细讲解有关如何用JVM源码分析Java对象的创建过程,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。基于HotSpot实现对Java对象的创建过程进行深入分析。定义两个简单的
2023-06-17

如何分析Java创建线程中的代码

如何分析Java创建线程中的代码,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Java创建线程经常在我们的编码中出现,当我们在使用的时候会有不少的问题困扰着我们
2023-06-17

Java网络编程与NIO详解11:Tomcat中的Connector源码分析(NIO)

本文转载 https://www.javadoop.com本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看https://github.com/h3pl/Java-Tutorial喜欢的话麻烦点
2023-06-02

如何分析Java开源工具在linux上的信号处理

今天就跟大家聊聊有关如何分析Java开源工具在linux上的信号处理,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。当java虚拟机启动的时候,会启动很多内部的线程,这些线程主要在th
2023-06-17

深入理解Python虚拟机中调试器实现原理与源码分析

本文主要给大家介绍python中调试器的实现原理,通过了解一个语言的调试器的实现原理我们可以更加深入的理解整个语言的运行机制,可以帮助我们更好的理解程序的执行,感兴趣的可以了解一下
2023-05-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录