java集合系列(一)——ArrayList

2019-01-21  本文已影响0人  Kino_7abb

今天看了下ArrayList作为自己读源码的第一步。ArrayList从名字可以知道底层是由数组实现的。而作为Collection的大家庭一员,可以说是很基础的集合了。

1.什么是ArrayList

ArrayList 底层是一个动态数组,大小是可变的。它实现了List接口,允许null添加进来。有以下几点注意:

2.我们来看看ArrayList的源码

2.1.底层使用数组

 private transient Object[] elementData;

由此可以看出底层是一个数组 transient 关键字:

当对象存储时,它的值不需要维持。Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。(可以参考)

2.2构造

    /**
     * 构造一个具有指定初始容量的空列表。
     */
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
 /**
     * 构造一个初始容量为 10 的空列表
     */
    public ArrayList() {
        this(10);
    }

构造基本上是对ArrayList进行初始化操作,默认是10

2.3新增 add操作

 public boolean add(E e) {
    //检测扩容
    ensureCapacity(size + 1);  
    elementData[size++] = e;
    return true;
 }

新增元素也就是在数组的末尾新增元素。这里ensureCapacity()方法是对ArrayList集合进行扩容操作

public void add(int index, E element) {
        //判断索引位置是否正确
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(
            "Index: "+index+", Size: "+size);
        //扩容检测
        ensureCapacity(size+1);  
        /*
         * 对源数组进行复制处理(位移),从index + 1开始,长度为size - index
         * 主要目的就是空出index位置供数据插入,
         * 即向右移动当前位于该位置的元素以及所有后续元素。
         */
        System.arraycopy(elementData, index, elementData, index + 1,
                 size - index);
        //在指定位置赋值
        elementData[index] = element;
        size++;
 }
  • add方法本质上是,arraycopy()这个复制数组的方法。
  • 这是非常麻烦和耗时的,
  • 所以如果指定的数据集合需要进行大量插入(中间插入)操作,推荐使用LinkedList。

2.4删除操作

remove(int index)

public E remove(int index) {
        //位置验证
        RangeCheck(index);

        modCount++;
        //需要删除的元素
        E oldValue = (E) elementData[index];   
        //向左移的位数
        int numMoved = size - index - 1;
        //若需要移动,则想左移动numMoved位
        if (numMoved > 0)
            System.arraycopy(elementData, index + 1, elementData, index,
                    numMoved);
        //置空最后一个元素
        elementData[--size] = null; // Let gc do its work

        return oldValue;
    }

由源码可以知道删除的操作本质上还是index+1开始长度为 size - index -1 到 ,然后复制到新的数组里,然后最后一个元素置空,由GC处理掉

remove(Object o)移除此列表中首次出现的指定元素(如果存在)。

public boolean remove(Object o) {
        //因为ArrayList中允许存在null,所以需要进行null判断
        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;
    }

ArrayList是可以存null,因此需要null判断,其余的只要用equals进行匹配即可其中

其中fastRemove()方法用于移除指定位置的元素。如下:

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; // Let gc do its work
    }

modCount 表示的是修改次数,根据线程不安全的话可以判断其他线程是否修改了该集合的元素,如果modCount和存储的mcount不一样那就抛异常 。因此推荐迭代器遍历

2.5查找

public E get(int index) {
        RangeCheck(index);
        return (E) elementData[index];
    }
}

由此可以看出来ArrayList 查找元素是很快的,只需要把索引赋值进去就可以啦,时间复杂度为O(1)

2.6关键的地方:扩容

  • 在上面的新增方法的源码中我们发现每个方法中都存在这个方法:ensureCapacity(),
  • 若新增元素后元素的个数会超过ArrayList的容量,就会进行扩容操作来满足新增元素的需求。
  • 所以当我们清楚知道业务数据量或者需要插入大量元素前,我可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。
public void ensureCapacity(int minCapacity) {
        //修改计时器
        modCount++;
        //ArrayList容量大小
        int oldCapacity = elementData.length;
        /*
         * 若当前需要的长度大于当前数组的长度时,进行扩容操作
         */
        if (minCapacity > oldCapacity) {
            Object oldData[] = elementData;
            //计算新的容量大小,为当前容量的1.5倍
            int newCapacity = (oldCapacity * 3) / 2 + 1;
            if (newCapacity < minCapacity)
                newCapacity = minCapacity;
            //数组拷贝,生成新的数组
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    }
}

1.5倍考虑到资源的分配不会浪费很多资源也不会,也能够满足性能的需求

上一篇下一篇

猜你喜欢

热点阅读