ArrayList源码分析

天涯浪子 提交于 2021-02-19 22:50:38

简介

List接口的可变大小数组实现(动态数组)。允许null值。此类与Vector大致等效,但它是不同步的。
所有的操作大致是线性的,加入一个元素需要O(1)时间,加入n个元素需要O(n)时间。
此实现未同步。如果多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改了该列表,则必须在外部进行同步。
可以这样构造同步集合:Collections.synchronizedList(new ArrayList(…)) 或者使用Vector
迭代器支持快速失败。

类继承关系

在这里插入图片描述
Serializable (标志接口) 可以被序列化网络传输
RandomAccess(标志接口)可以随机访问 并且for循环遍历优于迭代器
Cloneable(标志接口)能被克隆
继承AbstractList 抽象类 实现List接口

属性

//默认容量
private static final int DEFAULT_CAPACITY = 10;

//当size=0的时候设置元素
private static final Object[] EMPTY_ELEMENTDATA = {};

//用以实例化默认创建的实例
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//存储元素的数组
transient Object[] elementData;

//集合中包含多少元素
private int size;

//最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

构造方法

public ArrayList() {
	//初始化元素为空数组
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
    	//如果传入容量大于0 创建一个对应长度的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
    	//如果传入容量为0 使用默认数组初始化
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    }
}

public ArrayList(Collection<? extends E> c) {
   //将传入集合元素放入自己数据
   elementData = c.toArray();
   //设置size 并检查长度(这一行做了赋值比较 很妙)
   if ((size = elementData.length) != 0) {
       // 转换类型 c.toArray 返回的可能不是Object[]类型
       if (elementData.getClass() != Object[].class)
           elementData = Arrays.copyOf(elementData, size, Object[].class);
   } else {
       // 如果传入为空 将数据设置为空数组
       this.elementData = EMPTY_ELEMENTDATA;
   }
}

常用方法解析

添加

public boolean add(E e) {
	//使容量够用
  	ensureCapacityInternal(size + 1);
    elementData[size++] = e;//将元素插入最后
    return true;
}
public void add(int index, E element) {
    ensureCapacityInternal(size + 1);//增加容量
    //将index之后数据往后移动一位
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;//设置index位置为传入数据
    size++;//增加数量
}

/** * 这下面都是对容量进行增加的判断 */
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++;//增加操作次数
    // 如果传入的容量大于数组实际大小 就要增加数组大小了
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
//增加容量
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    //默认扩充为原来的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //如果扩充1.5倍小于传入的容量 那么就使用传入的容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //如果容量大于最大允许容量 确定使用integet最大值还是使用最大数组容量
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 将数组数据复制到新的数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}
//巨大容量处理
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) throw new OutOfMemoryError();
    //如果容量大于MAX_ARRAY_SIZE 使用int最大值 否则使用max array
    return (minCapacity > MAX_ARRAY_SIZE)?Integer.MAX_VALUE:MAX_ARRAY_SIZE;
}

添加多个

public boolean addAll(Collection<? extends E> c) {
  	Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // 扩充长度
    System.arraycopy(a, 0, elementData, size, numNew); //复制集合到后面
    size += numNew; //修正长度
    return numNew != 0;
}

获取

public E get(int index) {
    return elementData(index);
}
E elementData(int index) {
    return (E) elementData[index];//返回数组中指定位置的数据
}

是否包含

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}
//从前往后找到第一个相同元素
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;
    }
    return -1;
}

移除

public E remove(int index) {
    modCount++;
    E oldValue = elementData(index);
	//将index之后的数据依次往前移动一位
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // 将最后一位置空 这样垃圾回收器才能回收 并修正size 
    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;
}

交集

public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);//验证集合非空
    return batchRemove(c, true);
}
//批量删除数据 complemet为true 删除c中包含的数据 false 删除c中不包含的数据
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;//定义两个指针 读和写
    boolean modified = false;
    try {
    	//遍历本集合 元素包含在c中 就放入本集合 否则跳过
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // 正常来说r最后是等于size的,除非c.contains()抛出了异常
        if (r != size) {
        	 //把未读的元素都拷贝到写指针之后
            System.arraycopy(elementData, r, elementData, w, size - r);
            w += size - r;
        }
        if (w != size) {
            // 读指针后面的数据清空
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}

总结

内部采用Object类型数组存储数据; 初始化时为空数组
当长度不够的时候; 默认扩容原来一半的空间; 不会缩容
随机访问元素快 直接通过下标就能获取到数据 时间复杂度O(1)
插入和删除尾部数据比较快 平均复杂度O(1)
插入和删除中间元素效率差 删除后要将后边数据放到依次前移 平均O(n)
一般的可以认为: 遍历查询快 增加删除慢

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!