ArrayList源码解析和设计思路

目录

目录

目录

一、整体构架

二、常用方法源码解析

1.初始化

2.新增和扩容实现(arrayList.add)

3、remove根据值删除元素

总结




提示:以下是本篇文章正文内容,下面案例可供参考



一、整体构架

ArrayList整体构架就是一个数组结构,比较简单,如下图:

图中展示的是长度为10的数组,从1开始计数,index表示数组的下标,从0开始计数,elementData表示数组本身,源码中除了这两个概念还有以下三个:

1.DEFAULT_CAPACITY 表示数组的初始大小,默认是 10,这个数字要记住;

2.size 表示当前数组的大小,类型 int,没有使用 volatile 修饰,非线程安全的;

3.modCount 统计当前数组被修改的版本次数,数组结构有变动,就会 +1。



二、常用方法源码解析

1.初始化

有三种方法初始化:无参数直接初始化、指定大小初始化、指定初始数据初始化,jdk1.8源码如下:

ArrayList 无参构造器初始化时,默认大小是空数组,并不是大家常说的 10,10 是在第一次add 的时候扩容的数组值。

1.1无参数初始化数组,默认值是空。但是如果是空的话岂不是不可以添加数据?其实不然,在进行.add操作的时候,算法会进行一系列计算最终得到minCapacity=10,后面会详细介绍。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//无参数直接初始化,数组大小为空
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

1.2指定初始数据初始化时,我们发现一个这样子的注释 see 6260652,这是 Java 的一个
bug,意思是当给定集合内的元素不是 Object 类型时,我们会转化成 Object 的类型。
 

public ArrayList(Collection<? extends E> c) {
        Object[] a = c.toArray();
        //如果给定的集合有值
        if ((size = a.length) != 0) {
            if (c.getClass() == ArrayList.class) {
                elementData = a;
                //如果集合元素类型不是 Object 类型,我们会转成 Object
            } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // replace with empty array.给定集合(c)无值,则默认空数组
            elementData = EMPTY_ELEMENTDATA;
        }
    }

2.新增和扩容实现(arrayList.add)

往数组里面添加元素,主要分为两步:判断是否需要扩容,如果需要执行扩容操作;直接赋值。

2、开始

  /**
     * 新增元素操作
     */
    // eg1:第一次新增元素e="a1",
    public boolean add(E e) {
        /** 确定是否需要扩容,如果需要,则进行扩容操作*/
        ensureCapacityInternal(size + 1);  // Increments modCount!!

        // eg1:size=0,elementData[0]="a1",然后a自增为1
        elementData[size++] = e;
        return true;
    }



3、执行ensureCapaInternal(),第一次新增元素,所以size=0,minCapacity=size+1=1。

 // eg1:第一次新增元素,所以size=0,则:minCapacity=size+1=1
    private void ensureCapacityInternal(int minCapacity) {
        // eg1:第一次新增元素,calculateCapacity方法返回值为DEFAULT_CAPACITY=10
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

4、执行判断calculateCapacity是否为空

5、为空返回minCapacity=10

  // eg1:第一次新增元素,elementData={} minCapacity=1
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // eg1:满足if判断,DEFAULT_CAPACITY=10
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

6、执行ensureExplicitCapacity(),进行操作计数。

   // eg1:第一次新增元素,minCapacity=10
    private void ensureExplicitCapacity(int minCapacity) {
        // eg1: modCount++后,modCount=1
        modCount++;

7、进行判断是否需要扩容,/** 如果所需的最小容量大于elementData数组(初始时候为空)的容量,则进行扩容操作 */

       /** 如果所需的最小容量大于elementData数组的容量,则进行扩容操作 */
        if (minCapacity - elementData.length > 0) { // eg1:10-0=10,满足扩容需求
            // eg1:minCapacity=10
            grow(minCapacity);
        }
    }

8、执行grow()方法,需要扩容获取当前元素数量,扩容后的长度=原来长度+原来长度/2,假设oldCapacity=10,那么10=1010,>>1=101=5,所以newcapacity=oldcapacity+(oldCapacity>>1)=15,若果扩容后的长度小于minCapacity,则=minCapacity。扩容完成之后,赋值是非常简单的,直接往数组上添加元素即可:elementData [size++] =e。也正是通过这种简单赋值,没有任何锁控制,所以这里的操作是线程不安全的,

    // eg1:第一次新增元素,minCapacity=10,即:需要将elementData的0长度扩容为10长度。
    private void grow(int minCapacity) {

        /** 原有数组elementData的长度*/
        int oldCapacity = elementData.length; // eg1:oldCapacity=0

        /**
         * A >> 1 等于 A/2
         * eg: 3 >> 1 = 3/2 = 1
         *     4 >> 1 = 4/2 = 2
         * ------------------------
         * A << 1 等于 A*2
         * eg: 3 << 1 = 3*2 = 6
         *     4 << 1 = 4*2 = 8
         *
         * 000100 >> 1 = 000010
         * 000100 << 1 = 001000
         */
        /** 新增oldCapacity的一半整数长度作为newCapacity的额外增长长度 */
        int newCapacity = oldCapacity + (oldCapacity >> 1); // eg1:newCapacity=0+(0>>1)=0

        /** 新的长度newCapacity依然无法满足需要的最小扩容量minCapacity,则新的扩容长度为minCapacity */
        if (newCapacity - minCapacity < 0) {
            // eg1:newCapacity=10
            newCapacity = minCapacity;
        }

        /** 新的扩容长度newCapacity超出了最大的数组长度MAX_ARRAY_SIZE */
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            newCapacity = hugeCapacity(minCapacity);
        }

        /** 扩展数组长度为newCapacity,并且将旧数组中的元素赋值到新的数组中 */
        // eg1:newCapacity=10, 扩容elementData的length=10
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

3、remove根据值删除元素

新增的时候没有null进行较检,所以删除的时候也是允许删除null值。

找到值在数组中的位置,通过equals()方法进行比较是否相等,相等删除第一个元素。

public boolean remove(Object o) {
// 如果要删除的值是 null,找到第一个值是 null 的删除
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
// 如果要删除的值不为 null,找到第一个和要删除的值相等的删除
for (int index = 0; index < size; index++)
// 这里是根据 equals 来判断值相等的,相等后再根据索引位置进行删除
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
} r
eturn false;
}

上面代码已经找到要删除元素的索引位置了,下面代码是根据索引位置进行元素的删除:

p
rivate void fastRemove(int index) {
// 记录数组的结构要发生变动了
modCount++;
// numMoved 表示删除 index 位置的元素后,需要从 index 后移动多少个元素到前面去
// 减 1 的原因,是因为 size 从 1 开始算起,index 从 0开始算起
int numMoved = size - index - 1;
if (numMoved > 0)
// 从 index +1 位置开始被拷贝,拷贝的起始位置是 index,长度是 numMoved
System.arraycopy(elementData, index+1, elementData, index, numMoved);
//数组最后一个位置赋值 null,帮助 GC
elementData[--size] = null;

4、线程安全

ArrayList 有线程安全问题的本质,是因为 ArrayList 自身的 elementData、size、modConut,在进行各种操作时,都没有加锁,而且这些变量的类型并非是可见(volatile)的,所以如果多个线程对这些变量进行操作时,可能会有值被覆盖的情况。
使用Synchronized()加上锁实现线程安全。

public boolean add(E e) {
synchronized (mutex) {// synchronized 是一种轻量锁,mutex 表示一个当前 SynchronizedList
return c.add(e);
}
}



总结

注解应该比较详细,我们需要注意的四点是:

1、扩容的规则并不是翻倍,是原来容量大小 + 容量大小的一半,直白来说,扩容后的大小是原

来容量的 1.5 倍;

2、ArrayList 中的数组的最大值是 Integer.MAX_VALUE,超过这个值,JVM 就不会给数组分配

内存空间了。

3、新增时,并没有对值进行严格的校验,所以 ArrayList 是允许 null 值的。

从新增和扩容源码中,下面这点值得我们借鉴:

4、源码在扩容的时候,有数组大小溢出意识,就是说扩容后数组的大小下界不能小于 0,上界

不能大于 Integer 的最大值,这种意识我们可以学习。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值