【Java集合】ArrayList源码(动态数组)

·ArrayList集合底层数据结构介绍
·ArrayList继承关系
·ArrayList源码

一、ArrayList继承关系

继承自AbstractList类,其中有一些add,remove方法,可以直接调用,也可以重写;
实现了RandomAccess, Cloneable, java.io.Serializable接口
三个接口:
|–都是标记接口:接口中没有需要实现的方法,是空接口,注解也可以实现标记
|–RandomAccess:说明该类支持随机访问,大部分是基于数组实现,仿佛打标签
|–遍历方式:for循环,如果不支持随机,用迭代器遍历
|–Cloneable:说明该类支持拷贝,数据量比较大的时候调用clone方法,速度很快,涉及深拷贝,浅拷贝
|–浅拷贝:直接使用Object中的clone方法,拷贝出来的对象不是独立的对象,都是拷贝的栈中的内容
|–引用类型对象拷贝之后,旧值随着新值改变,因为引用对象的值存在堆中,拷贝栈中的内容后,变 量名变了,但是地址没变,指向的对象也是一个
|–int类型对象拷贝之后,旧值不变,因为栈中不允许两个相同的变量名,改过变量名之后新值和旧值 分别有对应的数据
|–深拷贝:将对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响源对象,ArrayList是深拷贝
|–String虽然也是引用类型,但是由于存在常量池中,所以单拿出来说,string对象在栈中复制之后,不允许重名,但是地址没有变化,但是如果改变的话,会新开辟一段内存空间,然后将新复制的地址指向堆中对象,然后新地址也会更改
|–java.io.Serializable:序列化用的最多的地方,网络传输,写磁盘,arrayList都存在内存中,但是断电就 无了,所以要存盘; 反序列化就是从磁盘/网络读取对象,把流恢复成对象;
|–serialVersionUID:类文件的签名,理解为md5值,如果类发生变化,ID值就会变,md5值变化,反序列化就会失败,所以源代码中它用final修饰,写死就不会变了。
在这里插入图片描述

二、全局变量
private static final long serialVersionUID = 8683452581122892189L;
// 默认容量是10
private static final int DEFAULT_CAPACITY = 10;
// 给空对象用的数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 看下面,说明调用的是无参构造器,默认容量是10,第一次添加元素时初始化容量
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 实际存储元素的数组,长度不可变
transient Object[] elementData; // non-private to simplify nested class access
private int size;

DEFAULTCAPACITY_EMPTY_ELEMENTDATA在内存中的位置,作用是节省空间
在这里插入图片描述

三、add方法(有两个,一个参数的,两个参数的)

①add(E e)方法

  1. 首先进入入口方法add,看是否指定了初始容量,未指定则默认为1,指定了则为指定的
  2. 修改modCount值,自增1,如果当前数组已使用长度+1后大于当前数组的容量,调用grow方法,增长数组,grow方法将当前数组扩容为1.5倍,如果扩容为1.5被还放不下,那只能扩容到你需要的最小,且不能超过Integer.Max_Value
  3. 确保新增数据有地方存储后,将新数组添加到位于size的位置上
  4. 返回bool值
public boolean add(E e) {
        //检查当前数组长度  确保内部容量   如果不足size+1 则grow扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

// 确保内部容量的方法,第一次添加元素时size值为1
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
值为1,将当前elementData数组的长度变为10,但是要是指定了容量的话就不走判断了
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

// 修改次数,modCount自增,判断是否需要扩充数组长度,若当前所需数组的最小长度大于数组长度,增长数组长度
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

// 如果当前数组使用空间+1后,大于数组长度,增大数组容量,
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //扩容到1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果扩容到1.5倍还不够,则扩容到当前期望的容量minCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
		// 需要的容量不能超出Integer.Max,超出了可能就oom了
        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);
    }

// 把原数组拷贝一份,重新赋值给new elementData,原数组没有引用了,就被回收啦
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
            // 调用了arraycopy,这是个一native方法
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
插个modcount,迭代器

1、for:ArrayList线程不安全,数组中的元素占用的内存空间是相等的(对象是存的内存地址),当我们要找一个元素时,首先是寻址,只需要知道数组的头部地址,就能得到下标的内存地址,则第i个地址为xxx+i*y,y是初始每个元素占用的内存长度,时间复杂度O(1),外面裹上for循环就是O(n),如果是链表,那寻找起来有点麻烦,所以arrayList是通过for循环,没通过迭代器。
2、迭代器:有个游标,存在全局变量中,游标指向链表头部,next是通过游标找到的链表头部,再调用一次把游标往后移一位,然后next找到第二个,不需要一个一个遍历了,只需要取到游标就知道返回什么。
3、modCount:如果数组中有元素被删除了,那迭代器是感知不到的,会报错,数组下标越界;多线程时可能就发生这种情况了,modcount是存储操作数,然后给expectedModcount,如果后面modcount不等于expectedModcount了,说明list有改动,再去迭代,会导致脏读或者幻读,所以源码中直接抛出异常了,这也就是fail-fast机制,提前检查,节省性能。
4、并行迭代器:将list分割为多个,单线程累加,每一个都用单线程求和,最后汇总

int expectedModCount = modCount;
 if (i >= elementData.length)
                throw new ConcurrentModificationException();

②add(int index, E element)方法
根据元素的位置,指定位置去插入元素

  1. 确保插入的位置小于等于当前数组长度,并且不小于0,否则抛异常
  2. 确保size+1后能存下下一个数据
  3. 修改modcount值,如果当前数组已使用长度(size)加1后的大于当前的数组长度,则调用grow方法,增长数组
  4. grow方法会将当前数组的长度变为原来容量的1.5倍。
  5. 确保有足够的容量之后,使用System.arraycopy 将需要插入的位置(index)后面的元素统统往后移动一位。
  6. 将新的数据内容存放到数组的指定位置(index)上
public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //从index开始  所有元素后移一个,
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
四、subList方法,截取元素

指向的还是原list,并不是独立的list,没有生成新的数组,只是存了下标值而已。

public List<E> subList(int fromIndex, int toIndex) {
		// 先检查是否越界
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }
  • // todo
    get,set,remove方法整理
五、ArrayList特点

ArrayList自己实现了序列化和反序列化的方法,因为它自己实现了 private void writeObject(java.io.ObjectOutputStream s)和 private void readObject(java.io.ObjectInputStream s) 方法
ArrayList基于数组方式实现,无容量的限制(会扩容)
添加元素时可能要扩容(所以最好预判一下),删除元素时不会减少容量(若希望减少容量,trimToSize()),删除元素时,将删除掉的位置元素置为null,下次gc就会回收这些元素所占的内存空间。
线程不安全
add(int index, E element):添加元素到数组中指定位置的时候,需要将该位置及其后边所有的元素都整块向后复制一位
get(int index):获取指定位置上的元素时,可以通过索引直接获取(O(1))
remove(Object o)需要遍历数组
remove(int index)不需要遍历数组,只需判断index是否符合条件即可,效率比remove(Object o)高
contains(E)需要遍历数组
使用iterator遍历可能会引发多线程异常

参考链接

https://blog.csdn.net/fighterandknight/article/details/61240861
https://www.bilibili.com/video/BV1gV411y772?p=6

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值