Java集合源码分析之ArrayList

ArrayList简介

1635822-20190422142856640-42838015.png

从上图可以看到,ArrayList是集合框架中List接口的一个实现类,它继承了AbstractList类,实现了List, RandomAccess, Cloneable, Serializable。

  • 实现List接口,对数组的基本增删改查操作。
  • 实现RandomAccess接口,快速随机访问功能。
  • 实现Cloneable接口,可以被复制,clone()方法。
  • 实现Serializable接口,支持序列化,可以序列化传输。保证类的一致。

在使用基本数组保存数据的时候,数组长度固定对于增删数据来说非常不方便,ArrayList可以动态的增删数组中的元素,和Vector但是ArrayList是不同步的,线程不安全的。

ArrayList成员变量

类的成员变量介绍:

//可序列化版本号
private static final long serialVersionUID = 8683452581122892189L;

//初始化默认数组长度为10
private static final int DEFAULT_CAPACITY = 10;

//空数组,用户指定数组长度为0时返回此数组
private static final Object[] EMPTY_ELEMENTDATA = {};

//空数组,当用户没有指定数组长度时返回此数组,
//用户第一次添加元素时,数组扩容成默认容量DEFAULT_CAPACITY
//此数组时默认返回的,而EMPTY_ELEMENTDATA数组返回是在用户指定数组长度为0时返回的。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//用来缓存数组存储的元素,ArrayList的容量和他的长度相等,
//第一次添加元素时会被扩充至DEFAULT_CAPACITY
transient Object[] elementData; 

//ArrayList的长度,包含元素个数
private int size;

构造方法

1. 参数为指定容量:
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        //创建传入大小的elementData数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        //传入长度为0时空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        //传入负数报错
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
2. 空参构造
public ArrayList() {
    //创建默认空数组,初始化长度为10
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
3. 参数为集合元素
public ArrayList(Collection<? extends E> c) {
    //传入集合转换为数组赋给elementData
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        //如果不是Object类型的数组,转化为Object类型数组
        //调用了Arrays的copyof方法,在Arrays中用System.arraycopy拷贝数组实现
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        //长度为0,空数组
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

ArrayList关于容量的方法

1. trimToSize改变数组长度为实际长度

集合中实际的元素个数size和数组长度elementData.length可能并不相等,在不需要长度大于实际元素个数时,可以使用此方法,减少内存浪费。

public void trimToSize() {
    //每次对数组进行修改操作都会modCount自加,理解为数组版本号
    modCount++;
    if (size < elementData.length) {
        //实际元素个数小于数组长度并且实际元素个数为0时返回空数组
        //否则复制数组并且长度为以及元素实际个数的数组
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}
2. ensureCapacity确认数组长度如果需要就扩容
public void ensureCapacity(int minCapacity) {
    //传入的最小容量值大于目前数组长度,并且当前数组不是默认空数组,
    //并且传入的容量值大于默认容量值(10),对数组增加为传入的容量值
    if (minCapacity > elementData.length
        && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
             && minCapacity <= DEFAULT_CAPACITY)) {
        //对数组操作,数组版本自加
        modCount++;
        //增加数组长度
        grow(minCapacity);
    }
}
3. grow扩容
private Object[] grow(int minCapacity) {
    //返回数组扩容后的数组,具体会不会扩充到传入的容量newCapacity方法再做判断
    return elementData = Arrays.copyOf(elementData,
                                        newCapacity(minCapacity));
}
private Object[] grow() {
    //返回一个增加一个容量的数组
    return grow(size + 1);
}
4. newCapacity容量判断

传入一个数组的容量,这个方法会判断这个容量,

private int newCapacity(int minCapacity) {
    int oldCapacity = elementData.length;
    //新的容量为原容量1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //如果这个1.5倍的容量小于传入的容量
    if (newCapacity - minCapacity <= 0) {
        //如果数组是空数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            //返回默认容量10和传入的容量中的大值
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        //如果传入的容量为负数报错
        if (minCapacity < 0)
            throw new OutOfMemoryError();
        //返回传入的容量(1.5倍的容量小于传入的容量并且数组不是空数组并且传入的容量大于0)
        return minCapacity;
    }
    //传入的容量是不是小于最大容量返回传入的容量,否则返回最大容量判断
    return (newCapacity - MAX_ARRAY_SIZE <= 0)
        ? newCapacity
        : hugeCapacity(minCapacity);
}
5. hugeCapacity最大容量判断

判断传入的容量值是不是大于最大容量值

private static int hugeCapacity(int minCapacity) {
    //如果传入的容量小于0报错
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    //如果传入的容量值大于最大容量值返回最大的Integer值2^31次方,2147483648
    return (minCapacity > MAX_ARRAY_SIZE)
        ? Integer.MAX_VALUE
        : MAX_ARRAY_SIZE;
}

最大容量值定义:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

最大的Integer数-8,为什么要减去8?

  • 为了存储数组的头信息
  • 为了防止内存溢出

增删改查及其他方法

1. size返回集合长度
public int size() {
    return size;
}
2. isEmpty是否为空
public boolean isEmpty() {
    return size == 0;
}
3. contains是否包含某个元素
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}
4. indexOf查找元素索引
public int indexOf(Object o) {
    //如果传入的元素是空的判断
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        //遍历数组查找值相同的元素返回其索引
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    //没有这个元素返回-1
    return -1;
}
5. lastIndexOf元素最后出现的索引
public int lastIndexOf(Object o) {
    //传入元素是空的判断
    if (o == null) {
        for (int i = size-1; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        //从后向前遍历数组如果值相等返回索引
        for (int i = size-1; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    //没有找到返回-1
    return -1;
}
6. clone克隆ArrayuList
public Object clone() {
    try {
        //声明一个新的ArrayList v
        ArrayList<?> v = (ArrayList<?>) super.clone();
        //调用Arrays类的copyOf方法复制数组传给v的elementData
        v.elementData = Arrays.copyOf(elementData, size);
        //将v的modCount初始化为0
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        throw new InternalError(e);
    }
}
7. toArray转换为数组
public Object[] toArray() {
    //集合的elementData复制一份返回
    //为了安全没有直接返回这个集合的elementData数组而是新复制一份
    return Arrays.copyOf(elementData, size);
}
8. toArray(T[] a)转换为数组
public <T> T[] toArray(T[] a) {
    if (a.length < size)
        //传入的数组长度小于集合长度,创建一个新的数组复制元素返回
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    //复制集合元素给a数组
    System.arraycopy(elementData, 0, a, 0, size);
    //如果a的长度大于集合的长度 将最后数组一位置null
    if (a.length > size)
        a[size] = null;
    return a;
}
9. get获取索引处元素
public E get(int index) {
    //判断传入索引是否合法再0到size之间
    Objects.checkIndex(index, size);
    //返回对应索引处的元素
    return elementData(index);
}
10.set替换指定索引处的元素
public E set(int index, E element) {
    //判断传入索引是否合法
    Objects.checkIndex(index, size);
    //先取到索引处原来的元素
    E oldValue = elementData(index);
    //给索引处赋新元素
    elementData[index] = element;
    //返回索引处原来的元素
    return oldValue;
}
11.add添加一个元素
private void add(E e, Object[] elementData, int s) {
    //数组长度是否等于集合size
    if (s == elementData.length)
        //要添加元素数组长度不够所以扩容
        elementData = grow();
    //给size处赋传进来的元素e
    elementData[s] = e;
    size = s + 1;
}

直接向集合尾部添加元素方法:

public boolean add(E e) {
    //对数组做修改操作modCount先自加
    modCount++;
    add(e, elementData, size);
    return true;
}

向指定的索引位置添加一个元素:

public void add(int index, E element) {
    //传入索引检查是否合法
    rangeCheckForAdd(index);
    //对元素修改操作modCount先自加
    modCount++;
    final int s;
    Object[] elementData;
    //判断数组长度与集合size是否相等
    if ((s = size) == (elementData = this.elementData).length)
        //相等的情况数组需要增容
        elementData = grow();
    //拷贝数组,将原数组索引处(包括索引)后的元素拷贝到新数组中,从index+1位置开始
    System.arraycopy(elementData, index,
                     elementData, index + 1,
                     s - index);
    //向索引处添加元素
    elementData[index] = element;
    //集合元素个数加一
    size = s + 1;
}
12.remove删除元素

删除指定索引处的元素:

public E remove(int index) {
    //判断传入索引是否合法
    Objects.checkIndex(index, size);
    //对元素修改操作modCount先自加
    modCount++;
    //取到索引处原来的元素
    E oldValue = elementData(index);
    //索引处元素删除后,需要向前移动的元素个数
    int numMoved = size - index - 1;
    if (numMoved > 0)
        //将索引+1到数组结尾的元素复制到索引处到数组结尾
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //将size-1处的索引置为null垃圾回收
    elementData[--size] = null;
    //返回删除的元素
    return oldValue;
}

删除指定元素:

public boolean remove(Object o) {
    //需要删除的元素为null时
    if (o == null) {
        //遍历数组找到null元素的索引
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                //将索引位置删除
                fastRemove(index);
                return true;
            }
    } else {
        //遍历数组找到o元素的索引
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                //将索引位置删除
                fastRemove(index);
                return true;
            }
    }
    return false;
}

快速删除:

private void fastRemove(int index) {
    //对元素修改操作modCount先自加
    modCount++;
    //索引处元素删除后,需要向前移动的元素个数
    int numMoved = size - index - 1;
    if (numMoved > 0)
        //将索引+1到数组结尾的元素复制到索引处到数组结尾
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //将size-1处的索引置为null垃圾回收
    elementData[--size] = null; 
}
13.删除所有元素
public void clear() {
    //对元素修改操作modCount先自加
    modCount++;
    final Object[] es = elementData;
    for (int to = size, i = size = 0; i < to; i++)
        es[i] = null;
}
14.addAll添加集合到集合

传入参数是要添加的集合:

public boolean addAll(Collection<? extends E> c) {
    //将传入的集合转换为数组
    Object[] a = c.toArray();
    //对元素修改操作modCount先自加
    modCount++;
    int numNew = a.length;
    //传入集合为空返回false
    if (numNew == 0)
        return false;
    Object[] elementData;
    final int s;
    //当前数组长度不够保存新的数组元素时数组扩容
    if (numNew > (elementData = this.elementData).length - (s = size))
        elementData = grow(s + numNew);
    //复制数组到扩容后的数组
    System.arraycopy(a, 0, elementData, s, numNew);
    //集合个数增加
    size = s + numNew;
    return true;
}

向指定索引处复制一个集合

public boolean addAll(int index, Collection<? extends E> c) {
    //索引检查是否合法
    rangeCheckForAdd(index);
    //将传入的集合转换为数组
    Object[] a = c.toArray();
    //对元素修改操作modCount先自加
    modCount++;
    int numNew = a.length;
    //传入集合为空返回false
    if (numNew == 0)
        return false;
    Object[] elementData;
    final int s;
    //当前数组长度不够保存新的数组元素时数组扩容
    if (numNew > (elementData = this.elementData).length - (s = size))
        elementData = grow(s + numNew);
    //将要复制到的索引位置到数组结尾的元素复制到当前位置+要复制的数组长度
    int numMoved = s - index;
    if (numMoved > 0)
        System.arraycopy(elementData, index,
                         elementData, index + numNew,
                         numMoved);
    //将要复制的数组复制到指定的索引处
    System.arraycopy(a, 0, elementData, index, numNew);
    //集合个数增加
    size = s + numNew;
    return true;
}
15.removeRange删除指定范围的元素
protected void removeRange(int fromIndex, int toIndex) {
    //起始位置索引大于结束位置索引报错
    if (fromIndex > toIndex) {
        throw new IndexOutOfBoundsException(
                outOfBoundsMsg(fromIndex, toIndex));
    }
    //对元素修改操作modCount先自加
    modCount++;
    shiftTailOverGap(elementData, fromIndex, toIndex);
}
private void shiftTailOverGap(Object[] es, int lo, int hi) {
    //复制指定一段位置的元素向前滑动
    System.arraycopy(es, hi, es, lo, size - hi);
    //将复制过的元素置null
    for (int to = size, i = (size -= hi - lo); i < to; i++)
        es[i] = null;
}

ArrayList的迭代器

ArrayList的迭代器部分的源码分析见这里

总结

  1. 什么是ArrayList?

    是集合框架中List的一个实现类,底层是用数组实现的,可以动态的对数组进行增删改查,当容量不足时自动扩容,默认容量为10,自动扩充容量为当前容量的1.5倍,是用System.arraycopy()复制为新的数组实现的。查找快,但是增删性能较查,因为每次增删数据都会需要复制数组,线程不安全。

  2. ArrayList如何实现自动扩容?

    在grow()方法中实现扩容,具体的是在newCapacity()方法中来确定扩容的大小,自动扩容1.5倍,实现自动扩容是通过Arrays.copyof()复制原数组到新的容量的数组中去。

  3. ArrayList扩容最大容量?

    ArrayList不能无线扩容,最大容量为

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    但是如果需要的容量大于上面的值,ArrayList会扩容到Integer.MAX_VALUE = 2 ^ 31,关于MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8为什么需要减8,首先数组需要保存头信息,其次是为了放置溢出。

转载于:https://www.cnblogs.com/Linteresting/p/10749932.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值