1. ArrayList的底层数据结构
List接口的可调整大小的数组实现。
transient Object[] elementData;
特点:
- 增删慢:每次增删非末尾元素,都要调整数组大小,拷贝及移动元素位置。
- 查询快:数组在内存中是一块连续的空间,因此可以根据地址+索引的方式快速获取对应位置的元素。
2.ArrayList源码分析
2.1 属性介绍
属性名 | 类型 | 描述 |
---|---|---|
DEFAULT_CAPACITY | int | 初始化容量,默认为10 |
EMPTY_ELEMENTDATA | Object[] | 共享空数组实例 |
DEFAULTCAPACITY_EMPTY_ELEMENTDATA | Object[] | 将它与EMPTY_ELEMENTDATA区分开来,以便知道在添加第一个元素时应该填充多少。 |
size | int | ArrayList数据个数 |
2.2构造方法
public ArrayList(Collection<? extends E> c) {
//将集合转换为数组
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//如果elementData不是Object[]的话,就重新拷贝成Object[]
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果集合长度为0,就替换为空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
2.3 添加方法
方法名 | 描述 |
---|---|
public boolean add(E e) | 将指定的元素追加到此列表的末尾。 |
public void add(int index, E element) | 在此列表中的指定位置插入指定的元素。 |
public boolean addAll(Collection<? extends E> c) | 按指定集合的Iterator返回的顺序,将指定集合中的所有元素追加到此列表的末尾。 |
public boolean addAll(int index, Collection<? extends E> c) | 从指定的位置开始, 将指定集合中的所有元素插入到此列表中。 |
2.3.1 add(E e)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确保内部容量比元数组长度大
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//确保最小容量不会小于10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//新的容量为原数组长度的1.5倍
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);
}
2.3.2 add(int index, E element)
public void add(int index, E element) {
//如果index>size或index<0,则抛出IndexOutOfBoundsException
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
//将index后的数据整体后移一位并拷贝到原数组中
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
2.3.3 addAll(Collection<? extends E> c)
public boolean addAll(Collection<? extends E> c) {
//将集合c转换为数组a
Object[] a = c.toArray();
//计算数组a的长度
int numNew = a.length;
//校验以及扩容
ensureCapacityInternal(size + numNew); // Increments modCount
//将数组a拷贝到list后面
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
2.3.4 addAll(int index, Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c) {
//索引校验
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
//计算需要移动元素的个数
int numMoved = size - index;
if (numMoved > 0)
//将index后的元素整体后移numNew位
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//将数组a拷贝到list集合的index后
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
2.4 删除方法
方法名 | 描述 |
---|---|
public E remove(int index) | 删除指定位置的元素 |
public boolean remove(Object o) | 如果存在,则删除第一个出现的此元素 |
protected void removeRange(int fromIndex, int toIndex) | 从这个列表中删除所有索引在 fromIndex (含)和 toIndex 之间的元素。 |
public boolean removeAll(Collection<?> c) | 从此列表中删除指定集合中包含的所有元素 |
2.4.1 remove(int index)
public E remove(int index) {
//索引校验
rangeCheck(index);
modCount++;
//得到该位置的值
E oldValue = elementData(index);
//计算机集合需要移动元素的个数
int numMoved = size - index - 1;
if (numMoved > 0)
//将index后的元素整体往前移一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将最后一个元素置为null,便于垃圾回收
elementData[--size] = null; // clear to let GC do its work
//返回被删除的元素
return oldValue;
}
2.4.2 remove(Object o)
public boolean remove(Object o) {
if (o == null) {
//遍历集合
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//删除第一个null
fastRemove(index);
return true;
}
} else {
//遍历集合
for (int index = 0; index < size; index++)
//当找到第一个与o相同的元素时,就删除
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
//即没有找到该元素
return false;
}
2.5修改方法
public E set(int index, E element)
根据索引修改集合元素
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
2.6 获取方法
public E get(int index)
根据索引获取元素
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
3.常见面试题
3.1 ArrayList频繁扩容导致添加性能急剧下降,如何处理?
- 案例
public class Test01 {
public static void main(String[] args) {
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("PHP");
list.add("Java");
long startTime = System.currentTimeMillis();
//需求:还需要添加10W条数据
for (int i = 0; i < 100000; i++) {
list.add(i+"");
}
long endTime = System.currentTimeMillis();
System.out.println("未指定容量: "+ (endTime - startTime));
}
}
结果
扩容次数太多导致性能低下。
- 解决方案
public class Test01 {
public static void main(String[] args) {
//创建集合的时候指定足够大的容量
List<String> list = new ArrayList<String>(100000);
//添加元素
list.add("hello");
list.add("PHP");
list.add("Java");
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
list.add(i+"");
}
long endTime = System.currentTimeMillis();
System.out.println("指定容量: "+ (endTime - startTime));
}
}
结果
**注意:**这种优化方式只针对特定的场景,如果添加的元素是少量的、未知的,不推荐使用
3.2 如何复制某个ArrayList到另一个ArrayList中去?
- 使用clone()方法,比如ArrayList newArray = oldArray.clone();
- 使用ArrayList构造方法,比如:ArrayList myObject = new ArrayList(myTempObject);
- 使用Collection的copy方法。
3.3 ArrayList 和 LinkList区别?
3.4 什么情况下你会使用ArrayList?什么时候你会选择LinkedList?
多数情况下,当你遇到访问元素比插入或者是删除元素更加频繁的时候,你应该使用ArrayList。另外一方面,当你在某个特别的索引中,插入或者是删除元素更加频繁,或者你压根就不需要访问元素的时候,你会选择LinkedList。这里的主要原因是,在ArrayList中访问元素的最糟糕的时间复杂度是”1″,而在LinkedList中可能就是”n”了。在ArrayList中增加或者删除某个元素,通常会调用System.arraycopy方法,这是一种极为消耗资源的操作,因此,在频繁的插入或者是删除元素的情况下,LinkedList的性能会更加好一点。