ArrayList类
一、概述
java.util.ArrayList集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。
- ArrayList知识点:
- 底层实现是数组。
- ArrayList无参构造的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍。
- 在增删时候,需要数组的拷贝复制(数组的复制调用系统的navite方法,由C/C++实现)。
- 删除元素后容量不会自动减少,若希望减少容量则调用trimToSize()。
- 它不是线程安全的。它能存放null值。
二、源码剖析ArrayList
- 对象实现:底层实现是数组(注意注释)。
/** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** * 由new Object[initialCapacity]可知,底层创建的是一个数组。 * 而且有指定长度时根据指定创建,为0时直接返回一个为空的数组,长度为0 */ 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); } } /** * Constructs an empty list with an initial capacity of ten. * 没有指定长度时:初始容量为10,但没有元素。 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
- 扩容:ArrayList的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍。
- 从add加入元素开始查看。
- ensureCapacityInternal确认list容量,尝试容量加1,看看有无必要
- 添加元素
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
- 如果需要要的最小容量比数组的长度要大,则调用grow()来扩容。最小容量指的是(当数组长度为10,要添加第11个元素时,至少增加一个长度就是11,所以最小容量为11,前面还有一步是将旧的元素个数加上新增的元素个数得到最小容量minCapacity的计算)
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
- int newCapacity = oldCapacity + (oldCapacity >> 1);这句就是相当于容量加1.5倍,oldCapacity >> 1就是除于2嘛!!!
- 知识点:这里用到位移运算而不用乘2,原因是cpu本来是不支持乘法运算的,所有的乘法最后还需要转化为位移运算,使用位移运算是为了提高cpu的运行效率。
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); }
- 从add加入元素开始查看。
- 扩容后修改数据:在增删时候,需要数组的拷贝复制(数组的复制调用系统的navite方法,本地方法由C/C++实现)。
- elementData = Arrays.copyOf(elementData, newCapacity);扩容的底层同样是根据最新的容量长度创建一个新的数组,并将旧的数组通过调用系统本地的复制方法将数据复制到新的数组(新的ArrayList)。
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos, int length);
- elementData = Arrays.copyOf(elementData, newCapacity);扩容的底层同样是根据最新的容量长度创建一个新的数组,并将旧的数组通过调用系统本地的复制方法将数据复制到新的数组(新的ArrayList)。
- 不扩容添加数据:直接在数组后面加入即可。
- 指定索引增/删元素:
- 索引增:主要是将指定索引开始的元素往后复制到数组中。
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++; }
- 索引减:主要是将删除元素的后面的元素复制上前一位填补空缺,最后一用null填补上去。
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); // 最后一位用null填补上去 elementData[--size] = null; // clear to let GC do its work return oldValue; }
- 删除元素后容量不会自动减少(因为它使用了null填充了),若希望减少容量则调用trimToSize()。
- 索引增:主要是将指定索引开始的元素往后复制到数组中。
- 解决线程不同步
- 在要求非同步的情况下,我们一般都是使用ArrayList来替代Vector的了。如果想要ArrayList实现同步,在创建时可以使用Collections的方法:List list = Collections.synchronizedList(new ArrayList(…));,就可以实现同步了。
三、API快速使用
- 创建
- ArrayList arr = new ArrayList(); 无参构造
- List list = Collections.synchronizedList(new ArrayList(…)); 线程同步化构造
- 增:add(数据);(arr.add(索引号,元素);在该位置插入元素,改元素后面的值往后面挪动一个位置。)
- 删:remove(索引号),注意:使用remove(Object o) 时,自定义类型时需要重写equals和hashCode方法;
- 查:get(索引号);返回元素值。
- 改:set(索引号,元素)。
- 长度:size()返回int长度值。
- 排序:sort(),传递的Comparator与Comparable自行查阅(如实现Comparable接口后,重写compareTo()方法)。
- 遍历:
- 使用获取迭代器:it = iterator()方法获取迭代器。
- it.next获取元素。
- 使用增强for循环:for(T parameter,arr)
- 使用获取迭代器:it = iterator()方法获取迭代器。
后期笔记
踩坑一:不要一定认为集合操作比数组快。
在实际的应用中,有时候盲目的使用集合也会导致效率低。当数据需要不断的删除、修改时,底层实际上是在不断的创建数组,复制数组的操作,在大数据量时非常影响效率,在数据量小时增删其实ArrayList还是可以的。
踩坑二:在大数据存储时,一般如果数据的大小基本确定,在创建集合时可以指定集合的大小(创建时指定容量的好处)。
当指定了一定的大小后,在存储数据时就可以不用再次扩容来创建更大的集合存储,指定了足够大的初始容量可以减少一些反复创建的操作。