ArrayList核心知识总结
本文主要从源码的角度出发,对ArrayList体系进行总结。
一、有?过ArrayList吗?它是做什么用的?
ArrayList就是数组列表,底层是数组 Object[] elementData,ArrayList在装载基本数据类型时,实际装载的是对应的包装类。 与ArrayList类似的还有LinkedList,他们俩相?:
- ArrayList:查找和访问元素的速度快,新增、删除的速度慢。线程不安全,使?频率?。
- LinkedList:查找和访问元素的速度慢,新增、删除的速度快。
二、ArrayList线程不安全,为什么还要去用?
实际开发中有以下?种场景:
- 频繁增删:使?LinkedList,但是涉及到频繁增删的场景不多
- 追求线程安全:使?Vector。
- 普通的?来查询:使?ArrayList,使?的场景最多。
根据数据结构的特性,三者难以全包含,只能在相互之间做取舍。
三、ArrayList底层是数组,那是怎么实现不断扩容的?
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
使?ArrayList空参的构造器创建集合时,数组并没有创建。当集合中添加第?个元素时,数组被创建,初始化容量为10.
有参构造创建时,如果指定了容量则会创建出指定容量??的数组。如果指定容量为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);
}
}
四、ArrayList(int initialCapacity)会不会初始化数组大小?
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);
}
}
会初始化??,但如果通过ArrayList的size()?法进?判断时结果依然为0,因为只有在添加元素时才会对ArrayList的size属性+1
private int size;
五、ArrayList底层是?数组实现,但数组长度是有限的,如何实现扩容?
当新增元素,ArrayList放不下该元素时,触发扩容。 扩容的容量将会是原容量的1/2,也就是新容量是旧容量的1.5倍。 确定新容量确定的源码:
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
执?扩容时使?系统类System的数组复制?法arraycopy()进?扩容。 扩容的源码:
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(),
newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
六、ArrayList1.7之前和1.7及以后的区别?
1.7之前ArrayList在初始化的时候直接调?this(10),初始化容量为10的数组。在1.7及以后,只有第?次执?add?法向集合添加元素时才会创建容量为10的数组。
七、为什么ArrayList增删?较慢,增删是如何做的?
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}
从源码?看到,是将要添加的元素位置index之后的已有元素全部拷?到添加到原数组index+1处,然后再把新的数据加?。
八、ArrayList插?和删除数据?定慢吗?
不?定,取决于删除的数据离数组末端有多远,如果离末端较近,则性能不?定差。
九、ArrayList如何删除数据?
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;
}
ArrayList删除数据时同样使?拷?数组的?式,将要删除的位置之后的所有元素拷到当前位置,最后再对最后?个位置的数据设置为null,交给gc来回收。这种删除,其实就是覆盖,如 果数据量?,那么效率很低。
十、ArrayList适合做队列吗?
队列需要遵循先进先出的原则,如果从ArrayList的数组头部?队列,数组尾部出队列,那么对于?队列时的操作,会涉及?数据量的数组拷?,?分耗性能。队头队尾反?反也是?样,因此ArrayList不适合做队列。
十一、数组适合做队列吗?
ArrayBlockingQueue环形队列就是?数组来实现的。ArrayBlockingQueue的存和取操作的索引是在当索引值等于容量值时,将索引值设置为0实现环形队列的效果,因此在这种情况下,数组适合做队列。
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
十二、ArrayList和LinkedList两者的遍历性能孰优孰劣?
ArrayList的遍历性能明显要?LinkedList好,因为ArrayList存储的数据在内存中时连续的,CPU内部缓存结构会缓存连续的内存?段,可以?幅降低读取内存的性能开销。
十三、ArrayList和LinkedList的区别?
ArrayList: 基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制:因为数组长度固定,超出长度存储数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能,甚至超过LinkedList(需要创建大量的node对象)。
LinkedList: 基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询:需要逐一遍历。遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时需要对list重新进行遍历,性能消耗极大。
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexOf对list进行了遍历,当结果为空时会遍历整个列表。
|