java-基础-ArrayList原理解析

前言:

一.在其他播客上看到下面这段话,可以说是总结的非常精辟了。读者们可以仔细品味:

ArrayList和LinkedList在性能上各 有优缺点,都有各自所适用的地方,总的说来可以描述如下:
1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对 ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是 统一的,分配一个内部Entry对象。

2.在ArrayList的 中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。

3.LinkedList不 支持高效的随机元素访问。

4.ArrayList的空 间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间


可以这样说:当操作是在一列 数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中 间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。
所以,如果只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是对其它指定位置的插入、删除操作,最好选择LinkedList


 

二:下面我从源码上分析ArrayList:

  属性:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 序列化 ID
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * ArrayList 默认的数组容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    // 一个默认的空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};

    // 在调用无参构造方法的时候使用该数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

  
    // 存储 ArrayList 元素的数组
    // transient 关键字这里简单说一句,被它修饰的成员变量无法被 Serializable 序列化 
    transient Object[] elementData; // non-private to simplify nested class access

    // ArrayList 的大小,也就是 elementData 包含的元素个数
    private int size;
}

  实现了 Serializable 是序列化接口,因此它支持序列化,能够通过序列化传输。
  实现了 Cloneable 接口,能被克隆。
  实现了Iterable 接口,可以被迭代器遍历
  实现了 Collection ,拥有集合操作的方法
  实现了 List 接口,拥有增删改查等方法
  实现了 RandomAccess 随机访问接口,支持快速随机访问,实际上就是通过下标序号进行快速访问。

构造方法:

可以看到elementData 数组 就是ArrayList用来存储数据的数组。

// 指定大小的构造方法,如果传入的是 0 ,直接使用 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);
    }
}
// 调用该构造方法构造一个默认大小为 10 的数组,但是此时大小未指定,
// 还是空的,在第一次 add 的时候指定
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 传入一个集合类
// 首先直接利用Collection.toArray()方法得到一个对象数组,并赋值给elementData 
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray 出错的时候,使用Arrays.copyOf 生成一个新数组赋值给 elementData
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        //如果集合c元素数量为0,则将空数组EMPTY_ELEMENTDATA赋值给elementData 
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

方法:

我们重点说明 addAll 方法,涉及到ArrayList的数组扩容原理。

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

1. add方法调用ensureCapacityInternal()方法,可以看到如果初始大小为{},则数组默认大小为10,继续调用

ensureExplicitCapacity()方法。

private void ensureCapacityInternal(int minCapacity) {
    // 如果创建 ArrayList 时候,使用的无参的构造方法,那么就取默认容量 10 和最小需要的容量(当前 size + 1 )中大的一个确定需要的容量。
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

2. 主要代码是 :

 int newCapacity = oldCapacity + (oldCapacity >> 1);

 这里表示新的数组大小为 原数组大小 + 原数组大小向右位移 1 位。后面我专门做右位移运算讲解。

private void ensureExplicitCapacity(int minCapacity) {
    // 修改 +1 
    modCount++;
    // 如果 minCapacity 比当前容量大, 就执行grow 扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    // 拿到当前的容量
    int oldCapacity = elementData.length;
    // oldCapacity >> 1 意思就是 oldCapacity/2,所以新容量就是增加 1/2.
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果新容量小于,需要最小扩容的容量,以需要最小容量为准扩容
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 如果新容量大于允许的最大容量,则以 Inerger 的最大值进行扩容
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 使用 Arrays.copyOf 函数进行扩容。
    elementData = Arrays.copyOf(elementData, newCapacity);
}

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

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

ArrayList的数组扩容原理介绍:

到这里我们可以看到 当我们创建一个ArrayList集合时。elementData 数组初始大小为0,当我们添加一个元素时elementData 大小为默认大小10,集合大小超过默认大小时,elementData 为之前的1.5倍大小,实际上是原数组大小 + 原数组大小右移1位。

下面我们来做一次计算:当我不停的往ArrayList 添加数据时:

List<String> list = new ArrayList<String>();
    	System.out.println(list);
    	for(int i = 0; i<100; i++) {
    		list.add("a");	
    	}
    	
    	System.out.println(list);

List<String> list = new ArrayList<String>();  //elementData 大小为0

第一次添加时  //elementData 大小为10

list.size()>10时   //  10的二进制大小 为  1010 右移一位大小为  0101,二级制 0101 的大小为5,所以elementData 大小为15

list.size()>15时  //   10的二进制大小 为  1111 右移一位大小为  0111,二级制 0111 的大小为7,所以elementData 大小为22

所以 当我 创建一个ArrayList时

当我add一个数据时 elementData大小为10

当集合大小超过10的时候,elementData大小为15

 当集合大小超过15时,elementData大小为22

 至此,验证成功。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值