您的位置:首页 > 其它

ArrayList源码分析

2019-03-17 21:02 71 查看

一、ArrayList简介

ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。

它继承于 AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。

在我们学数据结构的时候就知道了线性表的顺序存储,插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1)

ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。

ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。

ArrayList 实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆。

ArrayList 实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。

和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。

二、源码分析

一、属性:

  1. 默认初始容量大小
private static final int DEFAULT_CAPACITY = 10;
  1. 空数组(用于空实例)。
private static final Object[] EMPTY_ELEMENTDATA = {};
  1. 用于默认大小空实例的共享空数组实例。我们把它从
    EMPTY_ELEMENTDATA
    数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  1. elementData
    数组用来存储ArrayList中的元素,从这个可以看出,ArrayList是底层是借组于数组来实现的。
transient Object[] elementData;
  1. size
    用来记录ArrayList中存储的元素的个数。
private int size;

二、构造方法:

  1. 带初始容量参数的构造函数。(用户自己指定容量)
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//创建initialCapacity大小的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//创建空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
  1. 默认构造函数,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
  1. 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray 可能返回的不是Object类型的数组所以加上下面的语句用于判断,
//这里用到了反射里面的getClass()方法
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}

上面将容器Collection转化为数组赋给数组elementData,还对Collection转化是否转化为了Object[]进行了检查判断。如果Collection为空,则就将空的常量数组对象EMPTY_ELEMENTDATA赋给了elementData;

三、add方法:

  1. add(E e)
    :将元素加入到List的末尾。
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}

上面代码调用了另一个

add
方法

private void add(E e, Object[] elementData, int s) {
//s为size,当size等于数组长度时,数组扩容
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
  1. add(int index, E element)
    :将
    element
    插入
    index
    位置
public void add(int index, E element) {
//对index进行界限检查
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
//当size等于数组长度时,数组扩容
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
//arraycopy()这个实现数组之间复制的方法一定要看一下,下面就用到了arraycopy()方法实现数组自己复制自己
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = element;
size = s + 1;
}
  1. addAll(Collection<? extends E> c)
    :按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
modCount++;
int numNew = a.length;
if (numNew == 0)
return false;
Object[] elementData;
final int s;
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);
System.arraycopy(a, 0, elementData, s, numNew);
size = s + numNew;
return true;
}

四、扩容核心方法:

ArrayList的扩容机制提高了性能,如果每次只扩充一个,那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。

  1. 要分配的最大数组大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  1. 最少扩容为
    size+1
private Object[] grow() {
return grow(size + 1);
}
  1. 把数组复制到扩容后的数组
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
  1. 扩容核心
private int newCapacity(int minCapacity) {
// oldCapacity为旧容量,newCapacity为新容量
int oldCapacity = elementData.length;
//将oldCapacity 右移一位,其效果相当于oldCapacity /2,
//我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
int newCapacity = oldCapacity + (oldCapacity >> 1);
//然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
if (newCapacity - minCapacity <= 0) {
//数组是DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,那第一次增加元素时扩容为10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
//再检查新容量是否超出了ArrayList所定义的最大容量,
//若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,
//如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}

//比较minCapacity和 MAX_ARRAY_SIZE
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE)
? Integer.MAX_VALUE
: MAX_ARRAY_SIZE;
}

四、remove方法

1.

remove(int index)
:删除index的元素

public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;

@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);

return oldValue;
}

上面的代码用到了

fastRemove
,将值

private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;// clear to let GC do its work
}
  1. remove(Object o)
    :移除指定元素
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
//如果为null,遍历与null比较
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
//如果不为空,遍历与o比较
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
  1. removeAll(Collection<?> c)
    :从此列表中删除指定集合中包含的所有元素。
public boolean removeAll(Collection<?> c) {
return batchRemove(c, false, 0, size);
}

五、get方法

get(int index)
:获得index出的值

public E get(int index) {
//边界检查
Objects.checkIndex(index, size);
return elementData(index);
}

六、set方法

set(int index, E element)
:将
element
复制给
index

public E set(int index, E element) {
Objects.checkIndex(index, size);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}

七、clear方法

clear()
:清除ArrayList中所有的元素。直接将数组中的所有元素设置为null即可,这样便于垃圾回收。

public void clear() {
modCount++;
final Object[] es = elementData;
for (int to = size, i = size = 0; i < to; i++)
es[i] = null;
}

三、几个问题

一、System.arraycopy()和Arrays.copyOf()方法

联系: 看两者源代码可以发现copyOf()内部调用了System.arraycopy()方法

区别:

  1. arraycopy()需要目标数组,将原数组拷贝到你自己定义的数组里,而且可以选择拷贝的起点和长度以及放入新数组中的位置

  2. copyOf()是系统自动在内部新建一个数组,并返回该数组。

二、ArrayList的交集,差集,并集,去重并集

  1. 交集:

    list2.retainAll(list1)
    ,此时
    list2
    中只包含之前
    list1
    list2
    共有的元素(交集),如果
    lis2
    中的元素有被修改过,会返回
    true
    ,否则返回
    false

  2. 差集:

    list2.removeAll(list1);
    ,此时
    list2
    中只包含之前属于
    list2
    而不属于
    list1
    的元素,如果
    lis2
    中的元素有被修改过,会返回
    true
    ,否则返回
    false

  3. 并集:

    list2.addAll(list1);
    ,此时
    list2
    中包含之前
    list1
    list2
    中的元素,如果
    lis2
    中的元素有被修改过,会返回
    true
    ,否则返回
    false

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: