jdk源码分析之ArrayList

ArrayList关键属性分析ArrayList采用Object数组来存储数据/*** The array buffer into which&

大家好,又见面了,我是全栈君,今天给大家准备了Idea注册码。

ArrayList关键属性分析

ArrayList采用Object数组来存储数据

    /**

     * The array buffer into which the elements of the ArrayList are stored.

     * The capacity of the ArrayList is the length of this array buffer. 何问起 hovertree.com

     */

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

    /**

     * The size of the ArrayList (the number of elements it contains).

     * @serial

     */

    private int size;

Object[] elementData是一个buffer数组,用来存储ArrayList的数据,该数组的大小表示ArrayList的容量,而size属性表示的是ArrayList里边存储元素的个数。

    /**

     * Shared empty array instance used for default sized empty instances. We

     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when

     * first element is added.何问起 hovertree.com

     */

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

多个ArrayList实例共享的static属性,一个空数组的实例,使用ArrayList的无参构造函数创建ArrayList实例的时候,直接使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA给底层数组elementData赋值

    /**

     * Constructs an empty list with an initial capacity of ten.

     */

    public ArrayList() {

        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

    }

而当创建一个容量为0的ArrayList时,直接将层数组elementData赋值为另外一个static属性

    /**

     * Shared empty array instance used for empty instances.

     */

    private static final Object[] EMPTY_ELEMENTDATA = {};

    public ArrayList(int initialCapacity) {

        if (initialCapacity > 0) {

            this.elementData = new Object[initialCapacity];

        } else if (initialCapacity == 0) {

            this.elementData = EMPTY_ELEMENTDATA;

        } else {

            throw new IllegalArgumentException(“Illegal Capacity: “+

                                               initialCapacity);

        }

    }

从ArrayList获取数据get(int index)

ArrayList可以通过下标对数据进行随机访问,时间复杂度O(1),实现方法为get方法

    public E get(int index) {

        rangeCheck(index);

        return elementData(index);

    }

get方法很简单,输入参数合法的话直接返回底层数组elementData对应位置的元素即可。 
而rangeCheck主要是检查下标不能越界访问

    private void rangeCheck(int index) {

        if (index >= size)

            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

}

向ArrayList尾部添加一个数据add(E e)

添加数据有两个方法,调用add(E e) 向ArrayList末尾添加数据和调用add(int index, E element)添加数据到指定位置

    public boolean add(E e) {

        ensureCapacityInternal(size + 1);  // Increments modCount!!

        elementData[size++] = e;

        return true;

}

添加数据首先检查是不是需要扩充容量来添加新的数据,调用ensureCapacityInternal(size + 1)确保当前容量足够添加一个新的数据,然后elementData[size++] = e将新数据添加在数组的末尾

    private void ensureCapacityInternal(int minCapacity) {

        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

        }

        ensureExplicitCapacity(minCapacity);

 }

ensureCapacityInternal确保在ArrayList容量为0的时候添加数据扩容时,至少扩容DEFAULT_CAPACITY大小,而DEFAULT_CAPACITY大小默认为10

    /**

     * Default initial capacity.

     */

    private static final int DEFAULT_CAPACITY = 10;

然后调用 ensureExplicitCapacity(minCapacity)进行扩容

    private void ensureExplicitCapacity(int minCapacity) {

        modCount++;

        // overflow-conscious code

        if (minCapacity – elementData.length > 0)

            grow(minCapacity);

    }

modCount++用于记录修改次数,主要用于一个线程使用迭代器迭代数据的时候另一个数据更改ArrayList结构抛出ConcurrentModificationException异常 
if (minCapacity – elementData.length > 0)用于判断是否真的需要扩容,elementData.length表示的是容器容量,size表示容器存储数据的数量,而minCapacity =sie+1,因此elementData如果有多于一个位置空闲没有存储数据,就不需要扩容 
否则调用grow(minCapacity)进行真正的扩容

    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:

        elementData = Arrays.copyOf(elementData, newCapacity);

    }

可以看到扩容时直接将容量大小变为之前的1.5倍

int newCapacity = oldCapacity + (oldCapacity >> 1);

这里还需要对newCapacity进行调整,如果扩容后的newCapacity还是比minCapacity小 
满足

if (newCapacity – minCapacity < 0)

设置newCapacity为传进来的参数minCapacity

newCapacity = minCapacity;

然后判断现在的newCapacity是否比上限MAX_ARRAY_SIZE大

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE – 8;

如果newCapacity比MAX_ARRAY_SIZE还大,则需要调用hugeCapacity(minCapacity)判断是否是minCapacity传入负数溢出

    private static int hugeCapacity(int minCapacity) {

        if (minCapacity < 0) // overflow

            throw new OutOfMemoryError();

        return (minCapacity > MAX_ARRAY_SIZE) ?

            Integer.MAX_VALUE :

            MAX_ARRAY_SIZE;

    }

hugeCapacity(int minCapacity)首先检测是否溢出,没溢出的话就返回Integer.MAX_VALUE 作为最大值(minCapacity > MAX_ARRAY_SIZE条件在调用出就满足)

经过上述一系列步骤,最终满足各种条件的新容量值minCapacity得到满足

   // minCapacity is usually close to size, so this is a win:

    elementData = Arrays.copyOf(elementData, newCapacity);

这里是直接创建一个容量为newCapacity的新数组,把之前elementData保存的数据复制到新数组。数组的复制是比较耗费性能的,因此应该避免连续扩容,尽量调用有参构造函数ArrayList(int initialCapacity)并设置合理容量大小

向ArrayList任意位置添加一个数据add(int index, E element)

调用add(int index, E element)在ArrayList任意位置添加数据

    public void add(int index, E element) {

        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!

        System.arraycopy(elementData, index, elementData, index + 1,

                         size – index);

        elementData[index] = element;

        size++;

    }

传入参数合法性检查仍然是方法体第一件要做的事情,这里要检测插入数据的位置index是否合法

    private void rangeCheckForAdd(int index) {

        if (index > size || index < 0)

            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

 }

参数合法性通过后调用ensureCapacityInternal(size + 1)进行扩容,上边已经分析过扩容的固定套路。扩容后就是调用System.arraycopy进行数组的复制,由此可见ArrayList添加数据是比较耗费性能的,事件复杂度是O(n),因为插入数据总是伴随着数组的复制(数组元素的移动)。

在ArrayList尾部添加一个Collection集合addAll

    public boolean addAll(Collection<? extends E> c) {

        Object[] a = c.toArray();

        int numNew = a.length;

        ensureCapacityInternal(size + numNew);  // Increments modCount

        System.arraycopy(a, 0, elementData, size, numNew);

        size += numNew;

        return numNew != 0;

    }

先通过Collection的toArray方法把Collection转化为Object数组,然后通过ensureCapacityInternal(size + numNew)进行扩容,然后调用

System.arraycopy(a, 0, elementData, size, numNew); 
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);

System.arraycopy进行数组的拼接,System.arraycopy(a, 0, elementData, size, numNew)表示从数组a的下标0出开始复制length个元素导数组elementData的下标size处。

在ArrayList任意位置添加一个集合Collection

 public boolean addAll(int index, Collection<? extends E> c) {

        rangeCheckForAdd(index);

        Object[] a = c.toArray();

        int numNew = a.length;

        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size – index;

        if (numMoved > 0)

            System.arraycopy(elementData, index, elementData, index + numNew,

                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);

        size += numNew;

        return numNew != 0;

}

仍然是先做入口参数的合法性检查

    private void rangeCheckForAdd(int index) {

        if (index > size || index < 0)

            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    }

然后把集合转化为Object数组

Object[] a = c.toArray();

通过ensureCapacityInternal(size + numNew)进行扩容 
计算原数组需要移动的数据的个数int numMoved = size – index 
如果有数据需要移动,调用System.arraycopy进行数据整体移动

System.arraycopy(elementData, index, elementData, index + numNew, numMoved);

从elementData数组下标为index出复制numMoved个数据到elementData数组的下标为index + numNew处 
这样elementData数组下标为index到index+numNew-1的位置被空出来,用于放置新的数据 
然后集合的数据添加到elementData数组留出的位置即可

System.arraycopy(a, 0, elementData, index, numNew);

从数组a的下标为0的地方复制numNew个数据到elementData数组的下标为index处

ArrayList中获取某一数据的下标indexOf

    public int indexOf(Object o) {

        if (o == null) {

            for (int i = 0; i < size; i++)

                if (elementData[i]==null)

                    return i;

        } else {

            for (int i = 0; i < size; i++)

                if (o.equals(elementData[i]))

                    return i;

        }

        return -1;

    }

获取下标只能通过遍历的方式逐一比较,null数据不能调用方法,所以不能通过equals进行比较,所以分类讨论, 
如果传入的参数o是是null的话通过elementData[i]==null进行比较,否则通过o.equals(elementData[i])进行比较。 
如果遍历过程中找到该数据,返回该数据下标,遍历结束没有找到返回-1 
注意indexOf是找到第一个相等的数据就直接返回下标了,如果想找到该数据在ArrayList中的最后一个位置,使用lastIndexOf即可

清空ArrayList的全部数据clear()

    public void clear() {

        modCount++;

        // clear to let GC do its work

        for (int i = 0; i < size; i++)

            elementData[i] = null;

        size = 0;

    }

首先modCount++,这是如果其他线程正在通过迭代器进行数据的访问,检测到modCount发生了变化将会抛出ConcurrentModificationException 
然后设置数组中的每一个元素为null,方便垃圾回收,最后设置size=0; 
虽然数据全部置null,size归0,但是elementData的大小没有变化

ArrayList的克隆clone()

    /**

     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The

     * elements themselves are not copied.)

     *

     * @return a clone of this <tt>ArrayList</tt> instance

     */

    public Object clone() {

        try {

            ArrayList<?> v = (ArrayList<?>) super.clone();

            v.elementData = Arrays.copyOf(elementData, size);

            v.modCount = 0;

            return v;

        } catch (CloneNotSupportedException e) {

            // this shouldn’t happen, since we are Cloneable

            throw new InternalError(e);

        }

    }

可以看到clone方法只是进行了浅克隆,在某些需要深克隆的场景出,需要自己负责每一个元素的深克隆

ArrayList删除指定位置的数据

    public E remove(int index) {

        rangeCheck(index);

        modCount++;

        E oldValue = elementData(index);

        int numMoved = size – index – 1;

        if (numMoved > 0)

            System.arraycopy(elementData, index+1, elementData, index,

                             numMoved);

        elementData[–size] = null; // clear to let GC do its work

        return oldValue;

}

代码流程还是比较清晰 
下标检查,modCount++达到迭代器快速失败,数组元素的移动,返回旧值 
其中比较重要的一点就是

elementData[–size] = null; // clear to let GC do its work

Effective Java提到了这点,自己申请内存自己要记得管理,否则造成内存泄露

ArrayList删除某一个数据

    public boolean remove(Object o) {

        if (o == null) {

            for (int index = 0; index < size; index++)

                if (elementData[index] == null) {

                    fastRemove(index);

                    return true;

                }

        } else {

            for (int index = 0; index < size; index++)

                if (o.equals(elementData[index])) {

                    fastRemove(index);

                    return true;

                }

        }

        return false;

    }

这里同样是根据传入的参数o是否为null进行分类处理,找到元素所在的位置index后直接调用 fastRemove(index)进行数据的删除,fastRemove比remove不需要检查数组下标

ArrayList的排序sort

    public void sort(Comparator<? super E> c) {

        final int expectedModCount = modCount;

        Arrays.sort((E[]) elementData, 0, size, c);

        if (modCount != expectedModCount) {

            throw new ConcurrentModificationException();

        }

        modCount++;

 }

先保存modCount,如果modCount在排序过程中被改变,抛出ConcurrentModificationException异常 
排序是直接调用Arrays.sort方法

ArrayList的缩容trimToSize()

    public void trimToSize() {

        modCount++;

        if (size < elementData.length) {

            elementData = (size == 0)

              ? EMPTY_ELEMENTDATA

              : Arrays.copyOf(elementData, size);

        }

   }

先登记modCount++是迭代器可以快速失败 
然后通过 Arrays.copyOf进行数组创建和复制,使ArrayList的容量等于保存的数据的数量的大小

推荐:http://www.cnblogs.com/roucheng/p/javatimu.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/120402.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Vue调试工具安装(vue devtools)

    Vue调试工具安装(vue devtools)Vue调试工具安装(vuedevtools)第一步:下载源码第二步:执行命令第一步:下载源码在github下载devtools源码,地址:https://github.com/vuejs/vue-devtools。第二步:执行命令下载好后进入vue-devtools-master工程执行cnpminstall,下载依赖,然后执行npmrunbuild,编译源程序。cmd回车后进入到vuedevtools的安装目录下。执行命令:(1)…

    2025年6月9日
    7
  • gimp教程:gimp界面介绍「建议收藏」

    gimp教程:gimp界面介绍「建议收藏」GIMP(跨平台图像处理程序)是一个开发源代码的光栅与图像编辑的先进功能,关于GIMP的界面,初学者都了解吗?下面是小编整理的关于gimp教程中gimp界面介绍,快来分享吧!gimp界面介绍:gimp图像窗口Gimp图像窗口是打开图形图像文件时图像显示的窗口,关闭窗口右上角的关闭按钮的话程序也将随之关闭。如下图所示,其窗口中包含下面几个元素:A、居于最上面的标题栏,最左面是Gimp图标(icons),中间是图像名,如果是刚开始打开无图像的话显示”GNUImageManipulatio..

    2022年6月15日
    37
  • httpclient4.x访问https[通俗易懂]

    httpclient4.x访问https[通俗易懂]https有单向认证和双向认证之分,单向认证即客户端只会认证服务端,双向认证是客户端需要认证服务端,服务端也需要认证客户端。先说单向认证,浏览器访问服务端,服务端接收请求,会把证书(包含密钥和其他信息)和加密后响应返回给浏览器。如果这个证书不是向第三方权威机构申请的,浏览器会提示证书有问题(使用httpclient访问的话会报错)。如果忽略错误,则浏览器接受证书并解密响应,发送的数据也用此密钥

    2022年7月22日
    12
  • 4g模块连接阿里云_国外4G模块

    4g模块连接阿里云_国外4G模块作者:如果能编程回忆最后修改时间:2020年6月12日概述Air724模组内置TCP/IP协议栈,提供TCP客户端和服务器端服务(PS:模块没有公网IP所以服务端模式多用于专属VPN网络)。可使用AT指令,LUAT二次开发,CSDK,开源DTU等多种方式开发,开发者根据实际需求合理选择开发方式。AT指令通过AT指令使用TCP服务主要包含设备联网,配置连接,建立连接,发送数据等步骤,具体流程如图高清版TCP流程图.pdf![](https://imgconvert.csdnimg.cn/aHR0c

    2025年11月29日
    7
  • eureka集群搭建[通俗易懂]

    eureka集群搭建[通俗易懂]1.分布式和集群有啥区别?可能有很多人对分布式和集群这两个概念有点混淆。我先用通俗易懂的话给大家解释下:分布式:一个业务分拆多个子业务,部署在不同的服务器上集群:同一个业务,分别部署在不同的服务器上所以分布式的每一个节点,完成的是不同的业务,一个节点挂了,那么这个业务功能就无法访问了,甚至可能会影响到其他业务。而集群是一个比较有组织的架构,正因为有组织性,一个服务节点挂了,其…

    2022年6月10日
    23
  • Kali linux新手入门视频教程|Kali linux安装

    Kali linux新手入门视频教程|Kali linux安装一、 Kalilinux是什么?KaliLinux是基于Debian的Linux发行版,设计用于数字取证操作系统。KaliLinux面向专业的渗透测试和安全审计.因此,KaliLinux已经进行了如下的多处核心的修改。单用户,设计成root权限登录:由于安全审计的本质,KaliLinux被设计成使用”单用户,root权限“方案。二、 Kalilinux新手入门教程目录(视频教程)…

    2022年5月26日
    65

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号