[Java 集合] ArrayList源码分析

[Java 集合] ArrayList源码分析

源码基于JDK 11
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, Serializable{ }

一、属性

	
    private static final long serialVersionUID = 8683452581122892189L;
    //默认容量
    private static final int DEFAULT_CAPACITY = 10;
    //空数组
    private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
    //默认的空数组,创建集合时使用
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
    //存放数据的数组
    transient Object[] elementData;
    //集合中元素的个数
    private int size;
    //最大容量
    private static final int MAX_ARRAY_SIZE = 2147483639;

分析

  1. DEFAULT_CAPACITY 默认容量,我在源码中没有看到使用这个常量。不过代码中,通过空参的构造方法创建集合时,数组的初始长度是0,当第一次调用add方法时,会创建一个长度为10的数组(下面会结合源码分析)。
  2. EMPTY_ELEMENTDATA 空数组,通过new ArrayList(0)创建时用的是这个空数组。
  3. DEFAULTCAPACITY_EMPTY_ELEMENTDATA 通过new ArrayList()创建时用的是这个空数组。
    与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为10个元素。
  4. elementData 真正存放元素的地方,使用transient是为了不序列化这个字段。
  5. size 存储元素的个数,而不是elementData数组的长度。

二、构造方法

ArrayList()

初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组,会在添加第一个元素的时候扩容为默认的大小,即10。

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

ArrayList(int initialCapacity)

传入初始容量,如果大于0就初始化elementData为对应大小,如果等于0就使用EMPTY_ELEMENTDATA空数组,如果小于0抛出异常。

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else {
            if (initialCapacity != 0) {
                throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
            }
			// 如果传入的初始容量等于0,使用空数组EMPTY_ELEMENTDATA
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

ArrayList(Collection<? extends E> c)

传入集合并初始化elementData,这里会使用拷贝把传入集合的元素拷贝到elementData数组中,如果元素个数为0,则初始化为EMPTY_ELEMENTDATA空数组。

    public ArrayList(Collection<? extends E> c) {
    	//集合转数组
        this.elementData = c.toArray();
        if ((this.size = this.elementData.length) != 0) {
        	//类型检查,如果c.toArray()不是Object[]类型,那么就重新拷贝
            if (this.elementData.getClass() != Object[].class) {
                this.elementData = Arrays.copyOf(this.elementData, this.size, Object[].class);
            }
        } else {
        	//传入集合的元素个数为0,初始化为EMPTY_ELEMENTDATA空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }

    }

三、常用成员方法

add(E e)

添加元素到末尾

    public boolean add(E e) {
    	//modCount是继承自AbstractList类,初始为0
        ++this.modCount;
        //调用私有的add方法,将要添加的元素,当前的数组,现有元素个数传入
        this.add(e, this.elementData, this.size);
        return true;
    }
    
    private void add(E e, Object[] elementData, int s) {
    	//如果当前数组长度和元素个数相等(也就是数组满了),那么就要先扩容了
        if (s == elementData.length) {
        	//数组扩容
            elementData = this.grow();
        }
		//在数组尾部插入数据
        elementData[s] = e;
        this.size = s + 1;
    }

    private Object[] grow() {
    	//设置最小容量为size+1
        return this.grow(this.size + 1);
    }

    private Object[] grow(int minCapacity) {
    	// 以新容量拷贝出来一个新数组
        return this.elementData = Arrays.copyOf(this.elementData, this.newCapacity(minCapacity));
    }

    private int newCapacity(int minCapacity) {
    	//原来的容量,也就是当前数组的长度
        int oldCapacity = this.elementData.length;
        //新容量,为原来容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果newCapacity  <= minCapacity ,则考虑minCapacity 
        if (newCapacity - minCapacity <= 0) {
        	//如果当前数组是个空数组,需要判断minCapacity和DEFAULT_CAPACITY的大小(这里并没有使用常量)
            if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                return Math.max(10, minCapacity);
            } else if (minCapacity < 0) {
                throw new OutOfMemoryError();
            } else {
                return minCapacity;
            }
        } else {
        	//如果newCapacity > minCapacity ,还要比较newCapacity 和MAX_ARRAY_SIZE的大小(这里也没有使用常量)
            return newCapacity - 2147483639 <= 0 ? newCapacity : hugeCapacity(minCapacity);
        }
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) {
            throw new OutOfMemoryError();
        } else {
        	//如果minCapacity > MAX_ARRAY_SIZE则取Integer.MAX_VALUE
            return minCapacity > 2147483639 ? 2147483647 : 2147483639;
        }
    }
  1. 首先modCount自增
  2. 判断 元素的个数数组的长度 是否相等(只考虑相等情况)
    • 将(现有元素个数+1)作为 minCapacity,对数组进行扩容
    • 将当前数组长度作为oldCapacity,将oldCapacity的1.5倍作为newCapacity
    • 比较newCapacity和minCapacity的大小
      • newCapacity<= minCapacity
        • 当前数组是空数组,新数组的长度取minCapacity和DEFAULT_CAPACITY中较大的
        • minCapacity,抛出异常
        • 新数组的长度取minCapacity
      • newCapacity > minCapacity ,还要比较newCapacity 和MAX_ARRAY_SIZE的大小
        • newCapacity <= MAX_ARRAY_SIZE,新数组的长度取newCapacity
        • newCapacity > MAX_ARRAY_SIZE
          • minCapacity < 0 ,抛出异常
          • 如果minCapacity > MAX_ARRAY_SIZE则取Integer.MAX_VALUE
          • 如果minCapacity < MAX_ARRAY_SIZE则取minCapacity
    • 调用Arrays.copyOf(T[] original, int newLength)方法,将原来的数据拷贝到新数组中
  3. 在数组的尾部插入数据
  4. 元素个数自增

add(int index, E element)

添加元素到指定位置

    public void add(int index, E element) {
    	//检查索引是否越界
        this.rangeCheckForAdd(index);
        ++this.modCount;
        int s;
        Object[] elementData;
        //如果当前元素个数=数组长度,需要扩容
        if ((s = this.size) == (elementData = this.elementData).length) {
        	//扩容逻辑同上(add(E e))
            elementData = this.grow();
        }
		//将index及其之后的元素往后挪一位,则index位置处就空出来了
        System.arraycopy(elementData, index, elementData, index + 1, s - index);
        //将新元素插入到index索引位置
        elementData[index] = element;
        this.size = s + 1;
    }

    private void rangeCheckForAdd(int index) {
        if (index > this.size || index < 0) {
            throw new IndexOutOfBoundsException(this.outOfBoundsMsg(index));
        }
    }
  1. 检查索引是否越界;
  2. 检查是否需要扩容;
  3. 将索引位置及之后的元素都往后挪一位;
  4. 在索引位置插入的元素;
  5. 数组元素个数加1;

addAll(Collection<? extends E> c)

将新集合的元素添加到原集合的后面

有很多文章把这种行为成为取两个集合的并集,这个说法是不对的。
例如:集合A[1,2,3],集合B[2,3,4]
那么通过addAll方法的到的结果是[1,2,3,2,3,4]
而A和B的并集结果是[1,2,3,4]

    public boolean addAll(Collection<? extends E> c) {
    	//将集合转成数组
        Object[] a = c.toArray();
        ++this.modCount;
        //数组a的长度,也是集合c中元素的个数
        int numNew = a.length;
        if (numNew == 0) {
            return false;
        } else {
            Object[] elementData;
            int s;
            //如果数组a的长度超过了数组elementData中的空余容量(装不下),那么就要扩容
            if (numNew > (elementData = this.elementData).length - (s = this.size)) {
            	//扩容时,minCapacity = 两个集合中元素个数的总和
                elementData = this.grow(s + numNew);
            }
			// 将数组a中元素全部拷贝到数组elementData的最后
            System.arraycopy(a, 0, elementData, s, numNew);
            this.size = s + numNew;
            return true;
        }
    }
  1. 集合c转成数组a
  2. 检查数组a是否为空
  3. 检查是否需要扩容
  4. 把数组a中的元素拷贝到elementData的尾部;

remove(int index)

删除指定索引位置的元素

    public E remove(int index) {
    	//检查索引是否越界
        Objects.checkIndex(index, this.size);
        //数组elementData地址给到es
        Object[] es = this.elementData;
        //获取数组中索引为index位置的元素
        E oldValue = es[index];
        this.fastRemove(es, index);
        //返回被删除的元素
        return oldValue;
    }

    private void fastRemove(Object[] es, int i) {
        ++this.modCount;
        int newSize;
        //判断删除的是否是末尾的元素
        if ((newSize = this.size - 1) > i) {
        	//不是末尾元素,将数组es中索引(i+1)及之后的元素,向前移动一位
            System.arraycopy(es, i + 1, es, i, newSize - i);
        }
		//将数组中最后一个元素置为null
        es[this.size = newSize] = null;
    }
	
  1. 检查索引是否越界;
  2. 获取指定索引位置的元素;
  3. 如果删除的不是最后一位,则其它元素往前移一位;
  4. 将最后一位置为null,方便GC回收;
  5. 返回删除的元素。

可以看到,ArrayList删除元素的时候并没有缩容。

remove(Object o)

删除指定元素

    public boolean remove(Object o) {
        Object[] es = this.elementData;
        int size = this.size;
        int i = 0;
        //检查删除的元素是否是null
        if (o == null) {
        	//需要删除null时,先便利数组,然后尝试找到第一个null所在位置的索引i
            while(true) {
                if (i >= size) {
                    return false;
                }
                if (es[i] == null) {
                    break;
                }
                ++i;
            }
        } else {
        	//遍历整个数组,找到元素第一次出现的位置
            while(true) {
                if (i >= size) {
                    return false;
                }
                if (o.equals(es[i])) {
                    break;
                }
                ++i;
            }
        }
		//删除数组中索引为i的元素
        this.fastRemove(es, i);
        return true;
    }
  1. 找到指定元素第一次出现的位置,如果未找到,返回false
  2. 快速删除;

set(int index, E element)

修改指定索引位置的元素为指定元素

    public E set(int index, E element) {
    	//检查索引是否越界
        Objects.checkIndex(index, this.size);
        //记录要被修改的元素
        E oldValue = this.elementData(index);
        //将index位置的值设置成指定元素
        this.elementData[index] = element;
        //返回被删除的元素
        return oldValue;
    }
  1. 检查索引是否越界;
  2. 记录将要被修改的元素;
  3. 将指定元素放置在指定索引位置;
  4. 返回被删除的元素;

get(int index)

获取指定索引位置的元素

    public E get(int index) {
    	//检查索引是否越界
        Objects.checkIndex(index, this.size);
        //返回数组中索引index位置的元素
        return this.elementData(index);
    }

	E elementData(int index) {
        return this.elementData[index];
    }
  1. 检查索引是否越界
  2. 返回索引位置处的元素
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值