Java ArrayList源码分析

ArrayList 简介

ArrayList 概述

ArrayList 集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引访问集合中的指定元素。另外,List集合中还有一个特点就是元素有序,即元素的存入顺序和取出顺序是一致的。

ArrayList 底层是一个 Object[] 数组,每一个类对象都有一个capcacity 属性,表示数组的长度,当向ArrayList 添加元素时,capcacity 属性会自动增加。

在这里插入图片描述

ArrayList 数据结构

底层数据结构是一个数组,元素的类型是Object类型,即可以存放所有类型的数据;

 private static final Object[] EMPTY_ELEMENTDATA = {};

ArrayList 的数据结构如下:
在这里插入图片描述

ArrayList 源码分析

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

  • 集成 AbstractList , 主要是 集合公共的方法
  • Cloneable 表示可克隆
  • Serializable : 可序列化

类中的属性

//默认数组大小
private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空对象数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 缺省空对象数组.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 元素数组
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     *数组大小
     */
    private int size;


说明:类的属性中核心的属性为elementData,类型为Object[],用于存放实际元素,并且被标记为transient,也就意味着在序列化的时候,此字段是不会被序列化的。

构造方法

无参构造方法

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

DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是一个空的数据,是个空的Object[];

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

默认代销为10;

有参构造方法

  public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {\
        // 默认 initialCapacity 为10
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

指定 elementData数组的大小,不允许初始化大小小于0,否则抛出异常。

ArrayList(Collection<? extends E>)型构造函数

 public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();//转换为数组
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

核心方法

add 方法

  1. add(E)
    默认直接在末尾添加元素
  public boolean add(E e) {
   // size 是数组元素的个数,因为这时要添加一个元素,所以先判断size + 1 这个大小数组是否容得下
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

ensureCapacityInternal 确定内部容量的方法

    private void ensureCapacityInternal(int minCapacity) {
    //如果 elementData 是空元素,size + 1 为1
    // 那么 minCapacity 的最大值 就是10,
    // 这时的空数据还没有进行初始化
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
      // 确定实际的容量,判断elementData 是否够用
        ensureExplicitCapacity(minCapacity);
    }

ensureExplicitCapacity

   private void ensureExplicitCapacity(int minCapacity) {
   //modCount 记录这集合的修改次数,也就是每次add或者remove它的值都会加1,那么由什么用呢?
   // 至于作用,我们在下文中进行分析
        modCount++;

        // overflow-conscious code
        // 第一种情况: elementData 是空数组,minCapacity = 10
        //第二种情况,elementData 不是空数据,add 的时候 minCapacity= size+1,
        // 也就是elementData 增加后的个数,拿它和 elementData的length 是否够用,如果
        // 不够用,则需要扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

grow 能够扩展数据的大小

    private void grow(int minCapacity) {
        // overflow-conscious code
        // 扩容前的实际大小
        int oldCapacity = elementData.length;
        // newCapacity 为 oldCapacity 的 1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //这个对应的是 elementData是空数组情况下
        // oldCapacity 为0,那么newCapacaity 也为0
        // minCapacity 为 10 ,所以相减小于0
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;//这时新的容量就为10
            // 如果newCapacity 大于最大容量
            //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
        if (newCapacity - MAX_ARRAY_SIZE > 0)
             //调用hugeCapacity方法
            newCapacity = hugeCapacity(minCapacity);
        // 新的容量大小确认后,copy原来的数据到扩容后的数据
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

  • oldCapacity + (oldCapacity >> 1): 比如原始容量为13,当新添加一个元素时,依据前面的计算方法,得出13的二进制数为1101,随后右移一位操作后得到的二进制为 119,即十进制数为6.最终扩容的计算结果为 oldCapacity + (oldCapacity >> 1) = 13+6 = 19。使用位运算主要是基于计算效率的考虑。
  • 当ArrayList 使用无参狗子啊方法时,默认的大小为10。也就是说在第一次add的时候,分配为10的容量,后续的每次扩容都会调用Arrays.copyOf 方法,创建新数组再复制,可以想象的是,假如需要将 100 个元素放置在 ArrayList中,采用默认构造方法,则需要被扩容13次才可以完成存储。反之,无果在初始化时便指定了容量new ArrayList(1000,那么在初始化ArrayList对象的时候就直接分配1000个存储空间,从而避免被动扩容和数组复制的额外开销。最后,进一步向,如果这个值达到更大的量级,却没有注意初始的容量分配问题,那么无形中造成的性能损耗是非常大的,甚至导致OOM的风险。

hugeCapacity

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
            //如果 minCapacity  大于 Max_ARRAY_SIZE,那么就返回Integer.MAX_VALUE
            // 否则返回 MAX_ARAY_SIZE = Integer.MAX_VALUE - 8
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

当我们调用add方法时,实际上的函数调用如下
在这里插入图片描述

2) add(int,E) 在特定位置添加元素,也就是插入元素

   public void add(int index, E element) {
   // 判断index 是否越界
        rangeCheckForAdd(index);

       // 扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //插入元素后,将index之后的元素都后移一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
         // 在index 处插入目标元素
        elementData[index] = element;
        size++;// size + 1
    }

rangeCheckForAdd(index)

private void rangeCheckForAdd(int index) {
// 插入的元素不能大于size 或者 小于 0,否则抛异常
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

System.arraycopy(…):
就是将elementData在插入位置后的所有元素往后面移一位。

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
代码解释:

  • Object src : 原数组
  • int srcPos : 从元数据的起始位置开始
  • Object dest : 目标数组
  • int destPos : 目标数组的开始起始位置
  • int length : 要copy的数组的长度

System.arraycopy(elementData, index, elementData, index + 1,
size - index);
举例如下:

 List<String> list = new ArrayList<>(10);
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("5");

此时elementData 为
在这里插入图片描述
此时我们在 第一个位置插入 4
list.add(1,“6”);
System.arraycopy(elementData, 1, elementData, 2, 4);

在这里插入图片描述
然后把6插入到1的位置
在这里插入图片描述

remove 方法

  1. remove(int)

通过删除指定位置上的元素

public E remove(int index) {
// 检查 index 是否合法
        rangeCheck(index);
        modCount++;
        // 获取index位置的元素,保存到oldValue上
        E oldValue = elementData(index);
        // 计算要移动的位数
        int numMoved = size - index - 1;
        if (numMoved > 0)
        // 移动元素,将指定位置index后的元素都要往前移一位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 将最后一个元素置空,让GC自动回收
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

2) remove(Object)

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

遍历所有的对象,得到对象的索引位置,然后调用 fastRemove方法,执行remove操作

  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
    }

定位到需要remove 元素的索引,先将index后面的元素往前移动一位,然后将最后的元素置空。

其他方法比较简单,就不一罗列了

总结

  • ArrayList 可以存放null元素。
  • ArrayList 可以自动扩展,当ArrayList 容量不足以容纳全部元素的时候,ArrayList 会重新设置容量。新的容量=“(原始容量x3)/2 + 1”;如果设置后的新容量还不够,则直接把新容量设置为传入的参数。
  • arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果
  • arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。
  • 集合初始化时,需要指定集合初始值大小。如果暂时无法确定集合大小,那么指定相应的默认值,这也要求我们记得各种集合的默认值大小,ArayList默认大小为10。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

半夏_2021

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值