Java容器之ArrayList源码阅读(一)

Java容器之ArrayList源码阅读(一)
ArrayList——百度翻译数组列表,相信只要是java开发人员都会每天接触到,百度一下,各种介绍与分析的。面试大纲中必有知识点。网上也有很多文章都有分析java这几种容器之间的结构、效率对比。事实上只有当自己亲自翻阅JDK中的源代码才会有真正的了解。后面就通过JDK1.8版本探索其中的实现原理。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList 继承了抽象类AbstractList,实现了List、RandomAccess、Cloneable、Serializable接口,需要注意的是RandomAccess “随机访问”,是一个标记接口,表明实现这个接口的 List 集合是支持快速随机访问的,而且获取数据使用for循环的效率会优于迭代器Iterator。Cloneable也是一个标记接口,只有实现这个接口后,才支持克隆。

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

private static final Object[] EMPTY_ELEMENTDATA = {};

// 默认数组容量为空
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 

// transient关键字声明数组默认不会被序列化,将数组的序列化交给了writeObject方法,
为什么要使用transient关键字呢?java通过以空间换时间的方式来提升数组的操作效率,创建数
组对象时必须分配空间,实际上数组的每个元素都会有其默认类型的初始值。假如数组实际存了5个
元素,而elementData的大小可能是10,那么在序列化时只需要5个元素,后面的五个元素是没有
实际意义的,不需要序列化。
transient Object[] elementData;

// size属性,元素长度,不要理解为就是数组elementData的长度。
private int size;
// 有参构造,此时会创建数组对象,参数作为数组大小。实际上为什么我们在使用ArrayList的时
候推荐使用这个构造方法来创建对象。而且最好能够一次指定,避免底层数组频繁进行扩容操作。也
不要出现实际需要添加多个数据,但是实例化时初始长度却很少,如new ArrayList<String>(0)
执行add方法10次,需要底层数组扩容8次,可想而知...
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);
        }
    }


// 无参构造,第一次新增,直接扩容数组大小为DEFAULT_CAPACITY=10。
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

ArrayLis中添加元素操作

// 直接新增元素,首先ensureCapacityInternal()方法会优先保证容量足够,如果不够调用
grow()方法扩容。由size属性记录添加元素数量,而不是数组大小。
public boolean add(E e) {
	ensureCapacityInternal(size + 1);  // Increments modCount!!
	elementData[size++] = e;
	return true;
}
// 扩容后容量的大小为 oldCapacity + (oldCapacity >> 1),也就是旧容量的 1.5 倍。
使用Arrays.copyOf复制数组操作代价是比较高的,这就是为什么数组结构的ArrayList添加操作
如果触发数组扩容效率比较慢的原因。但是如果一开始实例对象时大小设置合理,不会引发内部数组
扩容操作,那add操作效率是非常高的。
private void grow(int minCapacity) {
   // overflow-conscious code
   int oldCapacity = elementData.length;
   int newCapacity = oldCapacity + (oldCapacity >> 1);
   if (newCapacity - minCapacity < 0)
       newCapacity = minCapacity;
   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);
}

// 根据下标新增元素,此种方式可能会进行两次数组拷贝,效率上来讲更慢。
public void add(int index, E element) {
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}
// 无参构造实例ArrayList时 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,
在此处进行条件判断时,才会对数组进行容量扩容DEFAULT_CAPACITY = 10操作。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
   if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
       return Math.max(DEFAULT_CAPACITY, minCapacity);
   }
   return minCapacity;
}

ArrayLis中删除元素操作

// 要区分数组 elementData大小与有效元素数量,我们把size大小当作有效元素数量,数组中有
elementData-size个元素还是数组进行初始化时分配的类型默认值。这些元素我们可以把它叫做
无效元素,所有数组大小与size属性不一定是相等的。通过add方法可以看出往ArrayList中添加
元素,元素都是根据添加时的顺序存放到内部数组中的,而删除元素使用的System.arraycopy方
法是有先决条件 numMoved > 0,不满足此条件只有一种情况,那就是index位置的元素是数组中
有效元素集的最后一个元素。所以得出结论:从数组列表末尾删除元素效率并不慢。只有在开始或
中间删除元素才会使用拷贝方法,才会有效率问题。
public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,numMoved);
    elementData[--size] = null; // clear to let GC do its work
    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;
}

// 和根据下标删除元素方法一样,只是不需要返回被删除的元素
private void fastRemove(int index) {。
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

ArrayLis中获取元素操作

// 根据下标获取元素操作直接返回下标元素,效率最高。
public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}

ArrayLis中修改元素操作

// 根据下标修改元素,返回被修改元素。效率也是非常快的
public E set(int index, E element) {
    rangzoeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

总结:ArrayList中还有其他几个方法是比较常用的,但是实现原理无非都是对几个关键属性的操作。对于JDK1.8以前版本的ArrayList实现原理也未去看过,不太清楚他们之间区别。还有一个就是ArrayList中某些方法线程不安全也很好理解。至于很多人说其增删效率比较慢,个人感觉是比较片面的。关键是我们如何去使用它…就比如说玩游戏,没有菜的英雄只有菜的玩家。就这样,下一个LinkedList。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值