前言:ArrayList是java常见的集合,相信大家已经在很多场合使用过,他的优点是查询速度快,插入删除速度慢,这里我们一起来详细看一下ArrayList的设计。
1 arrayList类关系
查看arrayList的类关系,就可以知道他继承了 AbstractList, AbstractList是一个抽象类,他继承了 AbstractCollection,并且实现了List
2 那么我们从入口开始说
2.1 这里我们new了一个ArrayList的对象,点进去查看他的构造方法可以发现
List<String> list = new ArrayList<>();
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
他把一个空的对象数组赋给了当前数组,所以这个构造函数的作用就是产生了一个空的对象数组,这里就可以得知ArrayList是底层为数组的集合,他也具有数组的所有特性。
2.2 我们先从第一个add方法开始看,add方法是List接口就定义的,这里ArrayList做了实现。
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
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);
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}
上面的方法主要是取最新的数组长度,分为三步
1 将当前的数组长度往右移1位,加上之前的长度得到最新数组长度
2 如果得到的数组长度仍然放不上要添加的元素,则取最小应该的长度做为最新长度
3 如果数组长度达到最长,则
private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
,然后调用Arrays.copyOf进行扩容,原理就是复制当前数组到新的数组中。
最后将元素放到数组中
以上就是将元素放入arrayList所做的动作。
2.3 我们来看一下indexOf的 api
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*/
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
上诉的方法就很简单了 如果参数是null则 循环==null来判断,如果不为null 则调用equals的方法的比较,具体为啥这么区分大家可以自行百度一下equals的方法, 2.4 接下来我们看一下get()方法,大家都知道里面参数的 集合的下标
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
/**
* Checks if the given index is in range. If not, throws an appropriate
* runtime exception. This method does *not* check if the index is
* negative: It is always used immediately prior to an array access,
* which throws an ArrayIndexOutOfBoundsException if index is negative.
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
大家可以看到 在从数组中拿元素的时候进行了一次安全检查 调用了rangeCheck的方法,如果超过了数组元素的长度,就报索引越界的错误,这就是为啥我们程序中经常遇到的错误了.,检查完毕,再从数组中拿数据2.5 我门接着看remove的方法
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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;
}
上诉方法 ,我们依然检查了索引越界的问题,接下来调用E oldValue = elementData(index);
获取要删除的元素,int numMoved = size - index - 1;
这里主要是计算一下要删除oldValue 这个元素的后面所有元素的个数是多少,比如 现在一个有0 1 2 3 4 一个5个元素,现在要删除2 元素,那么计算出来2后面有3,4一共俩个元素
,目地是为了移动3 4 的下标,这也就是说明了为啥ArrayList的插入删除效率慢,因为他要维护后面所有元素的索引。
将后面的元素向前移动一位,将要删除的元素放到数组最后一位,并且将数组长度减少一位,并且赋值为null,让GC收集器去删除
2.6 其他的方法大家可以去看下ArrayList了 比较简单了。
但是ArrayList的设计为线程不安全的,这里引用别的博客的表述
概要介绍
首先说一下什么是线程不安全:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。 如图,List接口下面有两个实现,一个是ArrayList,另外一个是vector。 从源码的角度来看,因为Vector的方法前加了,synchronized 关键字,也就是同步的意思,sun公司希望Vector是线程安全的,而希望arraylist是高效的,缺点就是另外的优点。 说下原理(百度的,很好理解): 一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成:
1. 在 Items[Size] 的位置存放此元素;
2. 增大 Size 的值。
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。
那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。
示例程序:
细心的朋友会发现transient Object[] elementData;
数组是被transient修饰的,目地是为了当数组长度为10时 元素为5时 进行序列化的时候只会序列化数组为5的对象,所以这里反序列化的时候是不安全的,没有意义的,所以声明为不参与序列化