赞
踩
在一次的笔试过程中,我遇到一个题目,ArrayList list = new ArrayList(20);这行代码ArrayList底层会扩容几次,这个时候我就懵了,因为我对ArrayList的底层不太理解。虽然我们不理解,但是没有关系,我们把它给学起来就可以了。
说ArrayList之前,我们先把ArrayList拆开来,就说一下Array和List。
Array
我们可以先来说一说数组,Array数组,是一种基于索引的数据结构,使用索引来搜索和获取数据是很快的,Array检索和获取数据的时间复杂度是O(1),但是要删除数据的开销却很大,因为删除数据之后需要对数组的数据进行重新排列,删数据之后,后面的元素需要移动到前面。
而我们的数组有一个缺点,就说初始化时,必须指定长度,否则就会报错。
List
然后是我们的List,List是Collection的子接口,是一个有序、可重复的集合,List在Java中有两种实现方式,一种是LinkedList,而另一种就是我们的ArrayList,ArrayList是基于索引结构的实现。通常如果我们使用的话,如果我们在不明确插入多少条数据的情况下,那么使用数组就很尴尬了,因为你不知道初始化数组大小为多少。ArrayList可以使用默认大小,当元素达到一定个数后,会实现自动扩容,我们可以这样理解,数组是定死的,而ArrayList是动态的数组。
我们在学习Java的时候,我们都需要深入底层去学习,这有利于我们的提升,我们来看一下ArrayList的底层原理。
在ArrayList底层源码中有几个我们需要注意的属性:
我们先来看一下几个构造器
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
从源码中我们可以看出,当我们直接创建ArrayList,elementData被赋予了默认空容量的Object[ ]数组,所以此时ArrayList数组是空的、固定长度的,也就是说其容量此时是0,元素个数size为默认值0。
/** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ 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); } }
从该构造器可以看出:
从这里我们可以看出:如果我们在创建ArraylList实例过程中我们使用初始化容量的构造器,那么,它底层只是直接在堆上new 一个Object类型的数组,大小为initialCapacity,并没有进行扩容。
笔试题:ArrayList list = new ArrayList(20);
底层扩容了几次?
答案:0次。
下面的构造器感兴趣的自己去看看源码,这里我们来说一个另一个重要的方法:add(Element e);
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的一个方法:add()。
add():从底层源码可以看出,add()首次扩容会从0扩容成10,再次扩容到上次扩容的1.5倍,比如0,10,15,22,33…
add(),添加元素到集合的尾部,如果要增加的数据量很大的话,我们需要调用一下ensureCapicity(int minSize)。
ensureCapicity(int minSize)
ensureCapacity(int n)方法可以对ArrayList底层的数组进行扩容。
使用它是因为它可以一次性将我们的ArraylIst扩容到指定的容量,而如果我们需要加入大量元素而不进行指定,那么可能ArrayList会进行多次扩容,这样子会消耗很多性能的。而使用ensureCapicity()方法的作用是预先设定ArrayList的大小,这样我们可以大大提高初始化速度和性能了。我们来看一个例子。
public class EnsureCapacityTest { public static void main(String[] args) { ArrayList<Object> list = new ArrayList<Object>(); final int N =10000000; long startTime = System.currentTimeMillis(); for(int i = 0;i<N;i++) { list.add(i); } long endTime = System.currentTimeMillis(); System.out.println(endTime-startTime); list = new ArrayList<Object>(); long startTime1 = System.currentTimeMillis(); list.ensureCapacity(N); for(int i = 0;i<N;i++) { list.add(i); } long endTime1 = System.currentTimeMillis(); System.out.println(endTime1-startTime1); } }
结果:
4747
233
我们来说一个著名的异常:ConcurrentModificationException并发修改异常。我们来写一个ArrayList并发修改异常的例子,并引出著名的fail-fast机制。
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
if (next.equals(1)) {
list.remove(1);
}
}
异常信息如下:
关于Fail-Fast机制和ConcurrentModificationException具体见我另一篇文章:Fail-Fast机制和ConcurrentModificationException并发修改异常。
在线程不安全的情况下,如果我们在遍历集合的同时,需要修改集合中的元素,那么在绝大多数情况下就很可能会出现ConcurrentModificationException并发修改异常 。那么我们需要如何解决快速失败机制中的并发修改异常呢?
这个其实很简单的,我们只需要将java.util包下的类改成java.util.concurrent包下的类就可以了,具体的话:
ArrayList ---- CopyOnWriteArrayList
HashMap — ConcurrentHashMap
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。