ArrayList底层实现和原理分析

本文详细分析了ArrayList在Java中的底层实现和工作原理,包括ArrayList的构造方法、查询速度、扩容机制以及添加、删除、修改和转换操作。文章通过源码解读,阐述了ArrayList基于数组的实现方式,讨论了线程安全性,并举例说明了关键方法的执行流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ArrayList底层实现和原理分析

今天是周末,没什么事就在家里看了一下源码,我习惯使用Jdk1.8,所以我的代码全都是基于Jdk1.8,好了下面是正文,今天来说说ArrayList的底层实现和源代码。
首先,集合ArrayList是List的实现类,List还有两个实现类LinkedList和Vector。
先说说这几个实现类的区别吧:
1.首先在底层实现上ArrayList和Vector是基于数组实现的,在源代码中都维护了一个数组用于对数组进行操作,LinkedList底层维护的是链表;
2.类的继承路径不同,这里不多说继承路径了;
3.ArrayList线程不安全;其余两种线程安全
关于查询的速度:
ArrayList的查询速度快,LinkedList的增删改速度快;Vector因为是线程安全的所以它的查询速度也是很慢的;(原因在源代码中解释)

下面开始看代码:
首先:ArrayList提供了三个构造方法:
1.定义初始容量的集合

    /**
     * 定义了初始容量为10的Array List
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_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);
        }
    }
    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;
        }
    }

以上就是三种ArrayList的构造方法逻辑简单,代码简明就不做解释了,给个总结,这三个构造方法就是初始化一个存储数据的容器,其核心就是一个数组,也就是上面的elementData。

下面来看看ArrayList提供的方法:
首先要先解释下ArrayList中的扩容方法:

private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // 原数组长度
        int oldCapacity = elementData.length;
        //将数组长度扩容至原先的1.5倍>>1 右移一位相当于除以2, 左移一位相当于乘以2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //新长度小于元素个数,则将新长度变为元素个数
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //这是超限情形,即当新长度大于int最大值-8,则新长度为int最大值,hugeCapacity(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);
    }

这三个方法为扩容方法,但是其核心为grow(int minCapacity),
扩容操作发生在像集合中添加元素
这时候会首先调用ensureCapacityInternal(int minCapacity) ,在这个方法里会进行调用calculateCapacity(Object[] elementData, int minCapacity)进行判断在进行调用
ensureExplicitCapacity(int minCapacity)

在calculateCapacity(Object[] elementData, int minCapacity)中如上面代码可以看到一个判断条件
elementData ==DEFAULTCAPACITY_EMPTY_ELEMENTDATA
在这个方法中主要是要解决一个问题就是确定最小容量,这么说大家可能有点迷,说得通俗点就是判断是不是第一次添加元素,如果是也就是说这个集合是空的那么也就是说这个集合的核心数组是空的,这时候就要看添加的元素个数和默认长度谁大了,谁大就取谁,不是第一次添加直接就返回添加的元素个数。
那么好,现在就开始调用ensureExplicitCapacity(int minCapacity); 这个方法的作用是什么呢,很简单,就是判断集合是否需要扩容,判断的条件:
minCapacity - elementData.length > 0
minCapacity这个变量是之前比较出的最小容量,判断这个变量是否大于数组长度,如果大于则需要扩容,即调用扩容核心方法grow(int minCapacity);
在这个方法中可以很清楚的看到,这个方法每一步在上面都有很详细的注解,不在这里多做解释,现在介绍几个核心方法。

首先添加方法,添加提供了四种:
1.这个是添加单个元素的,就是先扩容,再将元素添加至数组中,完成添加。

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

2.向指定位置添加一个元素(代码解释放在代码中)

    public void add(int index, E element) {
    	//判断index是否合法(是否超过数组长度是否大于0,不符合抛异常)
        rangeCheckForAdd(index);
		//日常扩容没有什么问题
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将index及其之后的元素向后移动移动一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //为index下标位置的赋值为需要加入的元素
        elementData[index] = element;
        size++;
    }

3.像集合中添加一个指定集合

    public boolean addAll(Collection<? extends E> c) {
    	//先将传入的集合转化为Object数组
        Object[] a = c.toArray();
        //获得a数组的长度
        int numNew = a.length;
        //日常扩容没什么问题
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //将a数组copy进扩容之后的数组
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

4.向指定位置开始加入指定集合

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

这个方法与上面的并没有什么区别;

现在来看删除操作:
1.首先是删除单个元素的两个方法:

public E remove(int index) {
		//检查index是否合法(同上)
        rangeCheck(index);

        modCount++;
        //记录这个即将被删除的元素
        E oldValue = elementData(index);
		
        int numMoved = size - index - 1;
        //判断是否是最后一位元素,如果是就不需要将后面的元素向后移了,直接将元素设置为null
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //将元素设置为null
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }


//删除单个对象
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;
    }



2.删除多个元素:

public boolean removeAll(Collection<?> c) {
    // 参数为空校验
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        // 遍历当前数组
        for (; r < size; r++)
            // 判断参数中是否包含当前元素
            if (c.contains(elementData[r]) == complement)
                // 整理当前数组,w为已整理元素个数
                elementData[w++] = elementData[r];
    } finally {
        // 只有在前面循环完,数组扩容才会r!=size
        if (r != size) {
            // 把w至r的元素干掉,r至新size的元素前移
            System.arraycopy(elementData, r,
                    elementData, w, size - r);
            // 设置w长度
            w += size - r;
        }
        // 参数跟当前数组不一样
        if (w != size) {
            // 从w开始清空
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            // 修改元素个数
            size = w;
            modified = true;
        }
    }
    return modified;
}

下面来看看“修改”操作
1.将指定位置替换成指定元素
public E set(int index, E element) {
//检查是否合法
rangeCheck(index);
//记录需要替换的元素
E oldValue = elementData(index);
直接为数组赋值
elementData[index] = element;
return oldValue;
}

清空集合方法

    public void clear() {
        modCount++;
		
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
        	//遍历数组将每个元素置空
            elementData[i] = null;
		//将长度变为0
        size = 0;
    }

最后是转化为数组方法

public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}
public <T> T[] toArray(T[] a) {
    // 判断长度是否够用
    if (a.length < size)
        // 长度不够,创建长度为size的数组,并拷贝
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    // 长度不够用,直接拷贝
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

好了暂时总结到这,方法还有很多,基础方法懂了,其实都很简单;明天分享Map类的集合;再见各位。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值