持续更新中,未完坑。。。。
------------------------------------------------
先来Arraylist看看有什么属性
private static final long serialVersionUID = 8683452581122892189L; /** * 默认初始值大小. */ private static final int DEFAULT_CAPACITY = 10; /** * 空数组 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 默认初始值的空数组 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * ArrayList基于此数组实现,默认大小为DEAFULT_CAPACITY也就是10 */ transient Object[] elementData; // non-private to simplify nested class access /** * ArrayList实际元素数量 */ private int size;
下面分析下add()函数源码
第一步:ensureCapacityInternal(size+1);是判断数组是否需要扩容,如果需要则扩容
第二步:elementData[size++] = e;直接进行赋值
先分析第一步,我们跟进去
1.先分析calculateCapacity()
先判断elementData数组是否为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,那么这个很长的变量是啥呢?我们看一下
这里注释写的很清楚,可以简单理解为是一个空的数组。
就是说如果elementData为空则从 (DEFAULT_CAPACITY, minCapacity) 选择一个最大的然后返回。
(通过源码可以看到,这里的DEFAULT_CAPACITY默认大小为10)
如果elementData不为空,则直接返回minCapacity。calculateCapacity()分析完毕。
2.下面分析ensureExplicitCapacity()
这里的modCount就是modified count,主要功能是记录每次修改的次数,不是本次分析重点,这里不详谈。
然后判断minCapacity是否大于当前数组的长度,如果大于,则进行扩容操作
3.这个grow()函数是扩容的核心函数,虽说是核心函数,但也不难理解。下面开始分析
private void grow(int minCapacity) { // 把数组长度赋值给oldCapacity int oldCapacity = elementData.length; // newCapacity等于旧的长度 int newCapacity = oldCapacity + (oldCapacity >> 1); // 如果newCapacity小于minCapacity则把minCapacity赋值给它。 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果newCapacity大于MAX_ARRAY_SIZE则进入hugeCapcity(), if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 修改数组为新的长度,然后覆盖掉原先的数组 elementData = Arrays.copyOf(elementData, newCapacity); }
我们看一下这个MAX_ARRAY-SIZE是啥意思
简单说就是数组长度的最大值
但是这里为什么要减8呢?
注释中说一些虚拟机会保存header words在数组里。
分配更大的数组时可能会超过虚拟机的最大限制导致内存溢出。
但理解还是不清楚,所以Google了一下,在StackOverFlow找到了一些答案:https://stackoverflow.com/questions/35756277/why-the-maximum-array-size-of-arraylist-is-integer-max-value-8
有兴趣的可以自己去看,我摘取一段简单说就是数组本身需要8bytes的大小来存储数据,为了防止溢出,所以要减8。
4.然后分析hugeCapacity()
如果小于零,则抛出OutOfMemoryError()异常
如果minCapacity 大于 MAX_ARRAY_SIZE 则返回Integer.MAX_VALUE否则返回MAX_ARRAY_SIZE
5.最后通过copyOf函数把数组修改为新的大小然后覆盖掉以前的旧数组
第二步很简单
就是简单的赋值操作,但不是原子性,可以简单分为以下两步
elementData[size] = e; size++;
至此add()函数分析完毕。
总结:
1.ArrayList底层是还是数组,只是增加了动态扩容的功能,因为底层是数组,所以有数组的特性:访问快,增加\删除效率低。
2.ArrayList是线程不安全的。
3.ArrayList每次扩容,为原来容量的1.5倍。
下面是一些常见问题:
1、为什么ArrayList是不安全的
先上一段示例代码
public static void main(String[] args) throws InterruptedException { List<Integer> list = new ArrayList<>(); for (int i = 0; i < 100; i++) { new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 100; j++) { list.add(1);// 多执行几次会出现ArrayIndexOutOfBoundsException异常 } } }).start(); } Thread.sleep(100);// 等待子线程完全执行完毕 for (int i = 0; i < list.size(); i++) { if (list.get(i) == null) { System.out.println("i:" + i + ",数据:" + list.get(i)); } } System.out.println("list.size():" + list.size()); }
运行结果如图:
这里有三个问题
1.为什么会出现ArrayIndexOutOfBoundsException的异常?
2.为什么有的数据为null?
3.为什么最后list.size()会少于理想中的情况?
下面开始分析
第一个问题:为什么会出现ArrayIndexOutOfBoundsException的异常?
上面已经说过,add()函数有两步,这个错误是由第一步ensureCapacityInternal(size+1);导致的
假如说当前size为9
1.线程A进来,读取size为9,而当前elementData大小为10,判断不需要扩容
2.这时候B也进来了,读取size也是为9,也是判断为不需要扩容
3.线程A开始进行设置值操作, elementData[9++] = e 操作,赋值成功,然后size变为10。
4.线程B也开始进行赋值操作,这时候size已经变为10,所以它尝试设置elementData[10] = e,但是elementData没有进行过扩容,它的下标最大为9。于是此时会报出一个数组越界的异常ArrayIndexOutOfBoundsException。
第二个问题:为什么有的数据为null?
这是由第二步elementData[size++] = e;导致的
第二步不是原子操作,可以大致分为以下两个操作
elementData[size] = e; size++;
举个例子:假如说当前size为0
1.线程A读取到size为0,进行赋值为A
2.这时候线程B进来了,然后也进行赋值为B。
3.然后线程A对size+1,size=1
4.线程B对size+1,这时size=2
理想的情况应该是size[0]=A;size[1]=B。但此时size[0]=B;size[1]=null,而此时数组下标已经为2,所以下次添加元素时会从2开始,所以如果后期没有手动修改,size[1]始终为null。
第三个问题,为什么最后list.size()会少于理想中的情况?
因为在java中变量递增不是原子性的,所以size++本身也是线程不安全的
(Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store和write。)
这里的size++;其实可以分为三个独立的操作
1.读取size的值
2.将size值加1
3.然后将计算结果写回size。
递增的操作在多线程下就会出问题,举个例子
1.线程A读取size为1,将size值加1,然后把结果写回,这时size=2
2.这是线程B进来,读取size也是为1,然后size值加1,把结果写回,这时size还是2
所以导致递增失败,也就是会导致list的长度少于理想情况。
ArrayList, LinkedList, Vector的区别是什么?
- ArrayList: 内部采用数组存储元素,支持高效随机访问,支持动态调整大小
- LinkedList: 内部采用链表来存储元素,支持快速插入/删除元素,但不支持高效地随机访问
- Vector: 可以看作线程安全版的ArrayList