thinking in java (十一) ----- 集合之ArrayList

ArrayList概述

ArrayList是实现了List接口的动态数组,所谓的动态是其大小可变。允许包括null在内的元素。除了实现List接口外,此类还提供了一些方法来操控内部来储存列表的数组大小

每个ArrayList实例都有一个容量,该容量是指用来存储元素的数组大小。默认初始容量是10,随着ArrayList元素的增加,它的容量也会一直增加。在每一次增加新的元素的时候,ArrayList都会检查是否需要扩容,扩容操作带来数据向新的数组的COPY,所以我们如果知道具体业务数量(一般不能),在构造ArrayList的时候可以指定一个初始容量,这样会减少扩容操作。

需要注意的是,ArrayList不是同步的,如果多线程访问一个ArrayList实例,而其中至少一个线程从结构上改变了元素列表,那么必须保持外部同步,为了同步,最好是在创建时完成同步操作:

List list = Collection。synchronizedList(new ArrayList(...));

ArrayList源码分析

  • 底层使用数组实现
private static final int DEFAULT_CAPACITY = 10;
————————————————————
默认初始容量10
transient Object[] elementData;
+++++++++++++++++
没必要持久化其值
  • 构造函数

ArrayList有三个构造函数

ArrayList():默认构造函数,提供初始容量为10的空列表

ArrayList(int initialCapacity):构造一个含有指定初始容量的空列表

 public ArrayList(Collection<? extends E> c) :构造一个包含指定collection的元素的列表,

源码:


    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_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);
        }
    }
——————————————————————————
 public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

以上的代码分别是构造一个空的ArrayList列表,一个有初始容量的空列表,一个指定collection元素的列表,

  • 新增(add)

ArrayList提供了add(E e) ,add(index,E e),addAll(Collection(<? extends E> c),addAll(index,Collection(<? extends E> c)(这俩只能添加泛型)set(index,E e)这五种方法向ArrayList添加元素。

add(E e):

public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
    }
——————————————————
将指定的元素插入此列表中的尾巴
  ensureCapacity方法是扩容操作,elementData[size++] = e;将列表末尾元素指向e

add(index,E e):

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++;
        }

这个方法中最根本的方法是arraycopy方法 ,该方法的根本目的是将index位置空出来以供新数据的插入,这里需要进行数组的右移,这是很麻烦耗时的。多以如果指定的数据集合要进行大量插入的话,建议使用linkedList。

addAll(Collection(<? extends E> c):按照指定collection的迭代器返回的元素顺序,将添加的元素放在列表的末尾

public boolean addAll(Collection<? extends E> c) {
        // 将集合C转换成数组
        Object[] a = c.toArray();
        int numNew = a.length;
        // 扩容处理,大小为size + numNew
        ensureCapacity(size + numNew); // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

  这个方法无非就是使用System.arraycopy()方法将C集合(先准换为数组)里面的数据复制到elementData数组中。

addAll(index,Collection(<? extends E> c):从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。

public boolean addAll(int index, Collection<? extends E> c) {
        //判断位置是否正确
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
                    + size);
        //转换成数组
        Object[] a = c.toArray();
        int numNew = a.length;
        //ArrayList容器扩容处理
        ensureCapacity(size + numNew); // Increments modCount
        //ArrayList容器数组向右移动的位置
        int numMoved = size - index;
        //如果移动位置大于0,则将ArrayList容器的数据向右移动numMoved个位置,确保增加的数据能够增加
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                    numMoved);
        //添加数组
        System.arraycopy(a, 0, elementData, index, numNew);
        //容器容量变大
        size += numNew;   
        return numNew != 0;
    }

 set(int index, E element):用指定的元素替代此列表中指定位置上的元素。

public E set(int index, E element) {
        //检测插入的位置是否越界
        RangeCheck(index);

        E oldValue = (E) elementData[index];
        //替代
        elementData[index] = element;
        return oldValue;
    }
  • 删除

ArrayList提供了remove(int index)、remove(Object o)、removeRange(int fromIndex, int toIndex)、removeAll()四个方法进行元素的删除。

 remove(int index):移除此列表中指定位置上的元素。

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

removeRange(int fromIndex, int toIndex):移除列表中索引在 fromIndex(包括)和toIndex(不包括)之间的所有元素。

  • 查找

  ArrayList提供了get(int index)用读取ArrayList中的元素。由于ArrayList是动态数组,所以我们完全可以根据下标来获取ArrayList中的元素,而且速度还比较快,故ArrayList长于随机访问。

public E get(int index) {
        RangeCheck(index);

        return (E) elementData[index];
    }
  • 扩容

我们可以看到,在新增方法的源代码都含有一个函数,ensureCapacity()方法,该方法就是扩容方法,如果新增元素后容量大过初始容量。就会进行扩容操作。所以当我们知道业务数据量或者需要插入大量元素的时候,我们使用ensureCapacity()方法来手动添加初始容量,以减少再扩容的操作

为什么每次扩容处理会是1.5倍,而不是2.5、3、4倍呢?通过google查找,发现1.5倍的扩容是最好的倍数。因为一次性扩容太大(例如2.5倍)可能会浪费更多的内存(1.5倍最多浪费33%,而2.5被最多会浪费60%,3.5倍则会浪费71%……)。但是一次性扩容太小,需要多次对数组重新分配内存,对性能消耗比较严重。所以1.5倍刚刚好,既能满足性能需求,也不会造成很大的内存消耗。

猜你喜欢

转载自blog.csdn.net/sinat_38430122/article/details/83419483