浅谈ArrayList实现原理

最近又重新学习了一下集合框架,包括看书和看博客,故自己也作了一些总结,以自己的理解对集合做一些记录。
本次参考源码基于JDk1.8
一、基本层级
它继承自抽象类AbstractList,
实现了List接口,具备了一些集合的基本方法。
实现了RandomAccess接口 支持随机访问
实现了Serializable 接口, 支持序列化,
实现了Cloneable接口,可实现克隆。可看如下图
这里写图片描述
二、数据机构
ArrayList底层是基于数组实现的,但它是一个动态数组,意思是可自己动态扩容,
三、构造函数
ArrayList 总共有三个构造函数,

//1、构造一个空列表的初始容量10。
public ArrayList() {
  this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
  2、构造一个包含指定集合的元素的列表,将它copy到ArrayList。
* 此构造函数可延伸,可用于集合间的转换,如Set转换成List
*/
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) { 
          if (elementData.getClass() != Object[].class)
              elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 新建一个数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
//3、构造一个与指定初始容量的空列表。
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);
        }
    }

四、常用属性

/* 定义一个数组,使用transient修饰,该关键字声明数组默认不会被序列化。ArrayList 
具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。
ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充
那部分内容。*/
transient Object[] elementData;
// size 标识它包含元素的个数
private int size;

五、常用方法
1) 添加方法 add(E e),看以下源码

public boolean add(E e) {
    //ensureCapacityInternal 方法很关键,这个是加入元素之前,判断是否需要扩容,
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将添加的元素 赋给elementData,然后size++
    elementData[size++] = e;
    return true;
}
private void ensureCapacityInternal(int minCapacity) {
   ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0) // 如果添加的元素个数,大于数组的初始容量,则会使用grow() 方法扩容
            grow(minCapacity);
}
private void grow(int minCapacity) {
     // overflow-conscious code
     int oldCapacity = elementData.length;
     //设置新数组的容量扩展为原来数组的1.5倍
     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:
      // 最后一步,将旧数组的元素,copy到新数组,
      elementData = Arrays.copyOf(elementData, newCapacity);
 }

因为ArrayList 要扩容,需要复制数组,所以代价很高,那怎么个代价高了呢?我们查看Arrays.copyOf()的源码可知,最终回到System 类下的一个native方法

public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

它的功能是从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。从src引用的源数组到dest引用的目标数组,数组组件的一个子序列被复制下来。被复制的组件的编号等于length参数。源数组中位置在srcPos到srcPos+length-1之间的组件被分别复制到目标数组中的destPos到destPos+length-1位置
2) 添加方法 add(int index, E element),与上个方法不同的是,这个是将元素插入到指定索引处,add(E e) 是直接在ArrayList 的末尾添加元素,
添加完元素,size+1,然后index 后的元素,需要整体往后移动一位,

public void add(int index, E element) {

        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将elementData从index开始后面的元素往后移一位。
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

3) 删除 E remove(int index)

//  删除ArrayList指定位置的元素
public E remove(int index) {
    // 范围检测,如果index比size 大,则会抛出
    // new IndexOutOfBoundsException 异常
     RangeCheck(index);
     modCount++;
     E oldValue = (E) elementData[index];
     int numMoved = size - index - 1;
     if (numMoved > 0)
         // 把后面的元素 往前移
         System.arraycopy(elementData, index + 1, elementData, index, numMoved);
         //最后把最后一个元素置为 null
     elementData[--size] = null; // Let gc do its work
     return oldValue;
}

4) set(int index, E element)方法
将element元素 放到 指定index位置上,并返回 *被覆盖的元素*

public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

5) 读取 get(int index)
指定具体的索引值读取,相当于数组中的查找,

// 返回此列表中指定位置上的元素
public E get(int index) {
     rangeCheck(index);
     return elementData(index);
}

先列了一些常规用法的方法,API中还有很多其他的方法, 等后一步在慢慢完善!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值