ArrayList
1. ArrayList的无参构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
知识点1: 底层是Object数组
知识点2: 通过无参构造创建的ArrayList,其底层存储结构是一个缓存在方法区的空数组
知识点3: 为了避免我们反复的创建无用数组,所有通过无参构造new出来的ArrayList底层数组都指向缓存在方法区里的Object[]数组
知识点4: 对于无参构造
- JDK1.6中,ArrayList初始化容量为10
- JDK1.7中,初始化容量为EMPTY_ELEMENTDATA 为0
- JDK1.8中,初始化容量为DEFAULTCAPACITY_EMPTY_ELEMENTDATA 也是0
2. 添加元素和扩容机制
private static final int DEFAULT_CAPACITY = 10;//默认容量
public boolean add(E e) {
//1.确保当前数组的容量足够
ensureCapacityInternal(size + 1); // Increments modCount!!
//2.将新元素添加到[size++]的位置
elementData[size++] = e;
return true;
}
扩容:
知识点1: 初次添加
- 当原始状态为 缓存在方法区的空数组时,此时添加元素会将数组容量初始化为10;
知识点2: 扩容条件
- 当size + 1大于现有的数组长度elementData.length,就执行grow(mincapacity)
知识点3: 扩容倍数
- oldCapacity + (oldCapacity >> 1),新容量为旧容量的1.5倍
3. ArrayList删除元素
3.1 按索引删除
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);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
知识点: 当我们用下标方式去删除元素时,如果删除的是最后一个元素,不会触发数组底层的复制,时间复杂度为O(1)。如果删除第i的元素,会触发底层数组复制n-i次,根据最坏情况,时间复杂度为O(n)。
3.2 按元素删除
public boolean remove(Object o) {
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;
}
知识点: 用对象的方式来删除元素,只是想告诉大家,这种删除方式是用equals方法来查找元素的下标进而删除的,实际工作中很少遇到需要new一个对象去删除的情况。不建议一上来就重写equals方法,除非你有特殊的需求。
4. 插入元素
按照下标把元素添加到指定位置,想必大家都知道,我们直接上源码:
往ArrayLIst这个对象里添加的时间复杂度:
- 如果我们不指定位置直接添加元素时(add(E element)),元素会默认会添加在最后,不会触发底层数组的复制,不考虑底层数组自动扩容的话,时间复杂度为O(1)
- 在指定位置添加元素(add(int index, E element)),需要复制底层数组,根据最坏打算,时间复杂度是O(n)。
5. 按索引get元素
很简单,读取元素和数组长度无关,直接从底层数组里去拿元素
6.按索引set元素
7.总结
-
在ArrayList中,底层数组存/取元素效率非常的高(get/set),时间复杂度是O(1)
-
而 查找、插入、删除 元素效率似乎不太高,时间复杂度为O(n)。
-
插入和删除的高效解决方案: 当我们ArrayLIst里有大量数据时,这时候去频繁插入/删除元素会触发底层数组频繁拷贝,效率不高,还会造成内存空间的浪费,这个问题在另一个类:LinkedList里有解决方案,请期待后续文章讲解。
-
查找的高效解决方案: 查找元素效率不高,在HashMap里有解决方案,请关注后续文章。
Vector
常见面试题:Arraylist与Vector的区别是什么?
首先我们给出标准答案:
1、Vector是线程安全的,ArrayList不是线程安全的。
2、ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。
3、Vectir无参构造new的初始容量为10,ArrayList为空数组
相同点:
底层存储结构相同,都是数组,我们称为动态数组。
1.底层存储结构
实现了List接口,底层和ArrayList一样,都是数组来实现的。
2. 线程安全
-
只要是关键性的操作,方法前面都加了synchronized关键字,来保证线程的安全性。当执行synchronized修饰的方法前,系统会对该方法加一把锁,方法执行完成后释放锁,加锁和释放锁的这个过程,在系统中是有开销的,因此,在单线程的环境中,Vector效率要差很多。(多线程环境不允许用ArrayList,需要做处理)。
-
和ArrayList和Vector一样,同样的类似关系的类还有HashMap和HashTable,StringBuilder和StringBuffer,后者是前者线程安全版本的实现。
3. 初始化
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector() {
this(10);
}
public Vector(Collection<? extends E> c) {
elementData = c.toArray();
elementCount = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
默认无参构造初始化容量为10个;
4.扩容机制
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
2倍扩容