首页 > 编程语言 >ArrayList源码解析

ArrayList源码解析

时间:2023-09-25 12:04:21浏览次数:40  
标签:index int ArrayList elementData 源码 数组 解析 size


ArrayList是基于List 接口,大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。下面我们来分析ArrayList的源代码,ArrayList继承体系结构如下:

public class ArrayList<E> extends AbstractList<E>

implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList类继承AbstractList类并实现了List、Cloneable和Serializable接口,因而ArrayList具有克隆和序列化的功能。

属性

//默认初始容量
private static final int DEFAULT_CAPACITY = 10;
//用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//用于默认大小的空实例的共享空数组实例。与EMPTY_ELEMENTDATA数组的区别在于当第一个元素被加入进来的时候,它知道扩大多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//elementData数组用来存储ArrayList中的元素
transient Object[] elementData;
//ArrayList中存储的元素的个数
private int size;

构造函数

ArrayList提供了三种方式的构造器:

public ArrayList() {
   this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

源码的注释是构造一个初始容量为 10 的空列表。即当我们不提供参数而new一个对象时,底层的数组就是直接用长度为10的空实例数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA进行实例化。难点就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA的长度我们还不知道,关于如何使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA构造了一个初始容量为10的空列表在下面的add(E e)函数源码的介绍中就会知道答案了。

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

传入参数来实例化底层的数组对象,其中对参数的3中情况进行了处理。当参数小于0时抛异常;当参数等于0时;用空实例数组对象EMPTY_ELEMENTDATA来初始化底层数组elementData。

public ArrayList(Collection<? extends E> c) {  
    elementData = c.toArray();  
    size = elementData.length;  
    // c.toArray might (incorrectly) not return Object[] (see 6260652)  
    if (elementData.getClass() != Object[].class)  
        elementData = Arrays.copyOf(elementData, size, Object[].class);  
}

将容器Collection转化为数组赋给数组elementData,还对Collection转化是否转化为了Object[]进行了检查判断。如果Collection为空,则就将空的实例数组对象EMPTY_ELEMENTDATA赋给了elementData。

常用方法

修改

// 用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。  
public E set(int index, E element) {  
    RangeCheck(index);  
  
    E oldValue = (E) elementData[index];  
    elementData[index] = element;  
    return oldValue;  
}

添加

// 将指定的元素添加到此列表的尾部。  
public boolean add(E e) {  
    ensureCapacityInternal(size + 1);   
    elementData[size++] = e;  
    return true;  
} 

// 将指定的元素插入此列表中的指定位置。  
// 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。  
public void add(int index, E element) {  
    //位置有效性检查
    rangeCheckForAdd(index);
    //添加修改次数以及判断是否需要扩张数组长度
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //完成数组自身从index开始的所有元素拷贝到index+1开始且长度为size-index的位置上。
    System.arraycopy(elementData, index, elementData, index + 1,size - index);
    elementData[index] = element;
    size++;
} 

// 按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此列表的尾部。  
public boolean addAll(Collection<? extends E> c) {  
    Object[] a = c.toArray();  
    int numNew = a.length;  
    ensureCapacityInternal(size + numNew);  // Increments modCount 
    //将a中的所有元素拷贝到数组elementData最后一个元素的后面 
    System.arraycopy(a, 0, elementData, size, numNew);  
    size += numNew;  
    return numNew != 0;  
} 

// 从指定的位置开始,将指定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, size, numNew);  
    size += numNew;  
    return numNew != 0;  
}

读取

public E get(int index) {
        rangeCheck(index);//有效性检查
        return elementData(index);
    }

删除

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;
    }
// 移除此列表中首次出现的指定元素(如果存在)。这是应为ArrayList中允许存放重复的元素。
public boolean remove(Object o) {
// 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。
        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,都是在ArrayList中找到与此第一个相等的元素的位置,然后调用fastRemove(index)来进行移除;如果没有找到指定对象o的位置,则返回false,表示没有移除成功。

需要注意的是从数组中移除元素的操作,也会导致被移除的元素以后的所有元素的向前移动一个位置。remove(int index)从代码就可以看到,而 remove(Object o)则是调用了fastRemove(index),我们看下代码:

private void fastRemove(int index) {
        modCount++;
        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
}

可以看到,我们需要左移的功能是在这里实现的

清除

//直接将数组中的所有元素设置为null即可,这样便于垃圾回收
public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

扩容

扩容需要要重点说明的一个,在添加方法中涉及到ensureCapacityInternal()方法,源码如下:

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
}

如果elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则就用默认容量10来进行开辟空间。这里的源码就解释了DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组和EMPTY_ELEMENTDATA数组的区别之所在。也说明了当我们用无参构造函数来实例化一个对象时,确实是构造的一个长度为10的数组对象。

接下来再看下ensureExplicitCapacity()方法

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的作用为处理两种情况:
        1)第一种情况为:如果newCapacity扩展的过小。则应该至少扩张到所需的空间大小minCapacity
        2)第二种情况为:newCapacity扩张的过大,如果过大,则用Integer.MAX_VALUE来代替。
         */
        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倍。 

但是,由于扩张1.5倍可能和我们的需要不一致,即可能太小,也可能太大。因此,就有了源码中的两个if条件的处理。即如果扩张1.5倍太小,则就用我们需要的空间大小minCapacity来进行扩容。如果扩张1.5倍太大或者是我们所需的空间大小minCapacity太大,则进行Integer.MAX_VALUE来进行扩容。

 

参考:

http://zhangshixi.iteye.com/blog/674856



标签:index,int,ArrayList,elementData,源码,数组,解析,size
From: https://blog.51cto.com/u_6947107/7594378

相关文章

  • Java 8 Lambda 表达式解析
    Lambda表达式,也可称为闭包,它是推动Java8发布的最重要新特性。使用Lambda表达式可以使代码变的更加简洁紧凑。坦白的说,初次看见Lambda表达式瞬间头就大了,为了更好的理解,我们可以把Lambda表达式当作是一种匿名函数(对Java而言这并不完全正确,但现在姑且这么认为),简单地说,就是......
  • 理解并掌握C#的Channel:从使用案例到源码解读(一)
    引言在C#的并发编程中,Channel是一种非常强大的数据结构,用于在生产者和消费者之间进行通信。本文将首先通过一个实际的使用案例,介绍如何在C#中使用Channel,然后深入到Channel的源码中,解析其内部的实现机制。使用案例一:文件遍历和过滤在我们的使用案例中,我们需要遍历一个文件夹及......
  • Java LinkedList与ArrayList源码解析:根本区别和表面区别的详解
    在Java中,LinkedList和ArrayList是两个常见的集合类。它们都实现了List接口,但它们在实现方式上有很大的区别。本篇博客将详细解析LinkedList和ArrayList的源码,解释它们的根本区别和表面区别,并提供详细的代码解释。LinkedList与ArrayList的根本区别:数据结构:LinkedList是基于链表......
  • threejs源码
    剖分管道形状面地表文字水体相交光线三维实体材质运动svg动画输出......
  • 新版绿豆视频APP视频免授权源码 V6.6插件版
    简介:新版绿豆视频APP视频免授权源码插件版后端插件开源,可直接反编译修改方便对接苹果cms,自定义DIY页面布局!绿豆影视APP对接苹果cms所有页面皆可通过后端自由定制此版本后端源码+前端是壳(反编译版本)五款个人中心主题自由切换个人中心背景图后台可控后台控制幻灯片背景虚幻支持信......
  • 无法在web.xml或使用此应用程序部署的jar文件中解析绝对uri:[http://java.sun.com/jsp/
    今天解决了一个很早之前的问题!!!无法在web.xml或使用此应用程序部署的jar文件中解析绝对uri:[http://java.sun.com/jsp/jstl/core]之前一直以为是jar包不匹配,但是改了jar包之后连uri都分辨不出来了后来在网上查到是tomcat的问题,将tomcat的conf目录下的catalina.properties的tomc......
  • ArrayList常见面试题
    简介ArrayList是我们开发中非常常用的数据存储容器之一,其底层是数组实现的,我们可以在集合中存储任意类型的数据,ArrayList是线程不安全的,非常适合用于对元素进行查找,效率非常高。线程安全性对ArrayList的操作一般分为两个步骤,改变位置(size)和操作元素(e)。所以这个过程在多线程的环......
  • ArrayList常见面试题
    简介ArrayList是我们开发中非常常用的数据存储容器之一,其底层是数组实现的,我们可以在集合中存储任意类型的数据,ArrayList是线程不安全的,非常适合用于对元素进行查找,效率非常高。线程安全性对ArrayList的操作一般分为两个步骤,改变位置(size)和操作元素(e)。所以这个过程在多线......
  • Java面向对象思想解析:如何编写出高质量的代码
    面向对象思想面向对象思想面向对象是一种符合人类思维习惯的编程思想。现实生活中存在各种形态不同的事物,这些事物之间存在着各种各样的联系。在程序中使用对象来映射现实中的事物,使用对象的关系来描述事物之间的联系,这种思想就是面向对象。提到面向对象,自然会想到面向过程,面向过程......
  • Redis源码分析之启动流程
    源码版本:5.0图形工具:http://www.plantuml.com/plantuml/uml时序图源码:@startumlgroupmainserver.c->setproctitle.c:spt_init():为函数setproctitle调用做初始化工作server.c->server.c:setlocale(LC_COLLATE,"");server.c->server.c:tzset();......