简介: ArrayList 他的底层数据结构是以数组的方式进行组织数据的,且通过一定的算 法逻辑动态扩容数组的长度,或可理解 ArrayList 底层是一个动态数组。与 Java 中的原生的数组相比,它的容量是能动态增长的。
继承结构
AbstractCollection:
实现了 Collection 中大量的函数,除了特定的几个函数 iterator()和 size()之外 的函数
List:
有序队列接口,提供了一些通过下标访问元素的函数 List 是有序的队列,List 中的每一个元素都有一个索引;第一个元素的索引值是 0,往后的元素的索引值依次+1
RandomAccess:
RandmoAccess 接口,即提供了随机访问功能。RandmoAccess 是 java 中用 来被 List 实现,为 List 提供快速访问功能的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问
AbstractList:
该接口继承于 AbstractCollection,并且实现 List 接口的抽象类。 它实现了 List 中除 size()、get(int location)之外的函数。 AbstractList 的主要作用:它实现了 List 接口中的大部分函数和 AbstractCollection 相比,AbstractList 抽象类中,实现了 iterator()接口
ArrayList
ArrayList 包含了两个重要的对象:elementData 和 size。
elementData:
是Object[]类型的数组,它保存了添加到 ArrayList 中的元素。如果通过不含参数的构造函数 ArrayList()来创建 ArrayList,则 elementData 的容量默认是 10
。
size :
则是动态数组的实际大小。
(动态)数组的数据结构特点总结:
1.内存需要连续的空间保证
2.顺序添加操作涉及到动态扩容问题
3.除尾部位置元素的添加,删除操作都涉及到位置移动操作
4.随机查找效率快(下标搜寻)
ArrayList 源码设计者的优雅之道
上边说到elementData 的容量默认是 10
。实际上,ArrayList 的无参构造并没有完成对 elementData 数组对象的构造。
而DEFAULTCAPACITY_EMPTY_ELEMENTDATA
是什么呢?只是一个空的数组,所以说无参构造并没有完成对 elementData 数组对象的构造
那么elementData
是什么时候才会完成构造的呢?注意看对elementData的这句描述:译为:将在添加第一个元素时扩展为DEFAULT_CAPACITY。
看DEFAULT_CAPACITY
的定义:
那意思就是在第一次add的时候才会构造一个默认长度为10 的数组。
那咱们就去add方法中看一看到底是不是这个样子的(大家可以打开源码跟着步伐一起走,温馨提示:idea中alt+7打开本类构造):
1.找到add()
方法:
里面调用了ensureCapacityInternal(int minCapacity)方法。注意传入的是size+1,上边说了size就是动态数组的长度,那么第一次进来现在size肯定是0
2.进入ensureCapacityInternal(int minCapacity)
方法:
这里又调了ensureExplicitCapacity(int minCapacity)方法,传入的是一个calculateCapacity(Object[] elementData, int minCapacity)方法的返回值,那么下一步
3.进入calculateCapacity(Object[] elementData, int minCapacity)
看看返回的是什么
注意:这里传进来的两个参数elementData和minCapacity
elementData:上边说了,就是存放元素的数组嘛
minCapacity:就是再上一步中add方法传过来的size嘛,这里minCapacity就是0
看if判断的条件:elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
肯定是true
嘛,不知道为啥的往上翻到ArrayList 的无参构造
进入if判断:return Math.max(DEFAULT_CAPACITY, minCapacity);
返回DEFAULT_CAPACITY和minCapacity这两个数中的最大值
DEFAULT_CAPACITY:为10(往上翻)
minCapacity:为0
就是返回了10呗。
4.return到ensureCapacityInternal(int minCapacity)
方法,之后进入ensureExplicitCapacity(int minCapacity)方法中传入10
modCount++;是什么呢?后边会说到的。这里简单说下就是用于FailFast机制检测。这里下不管,继续往下看
if判断条件:minCapacity - elementData.length > 0
为true吧,minCapacity为10,elementData现在还为空所以length为0
进入if语句:调grow(int minCapacity)方法,传入10
5.进入grow(int minCapacity)方法
自上往下解释:
oldCapacity = elementData.length即为0
newCapacity = oldCapacity + (oldCapacity >> 1);就是oldCapacity+oldCapacity 右移一位即oldCapacity+oldCapacity /2。即newCapacity = 0+0/2即为0
第一个if判断:肯定为true嘛,0-10<0没异议。newCapacity = minCapacity
即newCapacity重新赋值为10了。
第二个if判断条件newCapacity - MAX_ARRAY_SIZE > 0
。MAX_ARRAY_SIZE是什么呢?看下图的下图ArrayList类中定义为Integer.MAX_VALUE - 8;啥意思?就是二十多亿减8。
那么10-20多亿>0肯定不满足的。所以直接跳过此条if语句,看下边
elementData = Arrays.copyOf(elementData, newCapacity);就是拷贝数组重新赋给elementData。
那么elementData就是在这里被初始化为长度10的数组的。
看jdk官方api文档中:
总结:我们可以通过源码分析和断点跟踪的方式.发现最终,他的初始化是在真正的第 一次添加元素时才会完成数据对象的构建.跟我们的截图中的注释解释是一模一 样的,那这样做的优雅之处在哪里呢?
ArrayList 对象 new 的过程并不会实际的创建 10 长度的 Object[]用于对象的 存储,如果后续并没有使用到 Arraylist 对象进行操作的话,这部分的内存就是浪 费的.只有当程序第一次使用 ArrayList 添加元素时,才会完成通过 grow 函数完成对象数组的创建.主要的意义是防止内存的浪费。
集合迭代器获取元素优雅之道
List list = new ArrayList();
//常见的方式
Iterator iterator = list.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
}
//优雅的方式
for (Iterator tempiterator = list.iterator();tempiterator.hasNext();){
Object obj = tempiterator.next();
}
上诉的优雅之处在于 tempIterator 对象存在于 for 所在的作用域范围之内.在 for
范围结束之后随时可以完成对该对象的垃圾回收
FailFast 机制
简介:快速失败机制,是 java 集合类应对并发访问在对集合进行迭代过程中,内部对象结构发生变化 一种防护措施。
这种错误检测的机制为这种有可能发生错误 , 通过抛出java.util.ConcurrentModificationException
。
简单点说:就是你在遍历一个集合的时候对它进行添加或删除的操作时就会抛这个异常,就是一种防护措施,不让你在遍历集合的时候做修改操作。
例如下面一段代码,就会抛出ConcurrentModificationException
public static void main(String[] args) {
List list = new ArrayList();
list.add("aaa");
for (Iterator tempiterator = list.iterator();tempiterator.hasNext();){
list.add("bbb");
Object obj = tempiterator.next();
}
}
那么它是如何做到 FailFast 检测的呢?
1.这里就拿ArrayList类来看,ArrayList中有一个叫Itr的内部类实现了Iterator接口。找到这个类,看到
int expectedModCount = modCount;
modCount是不是上文中ArrayList做添加操作的时候看到过:modCount++
这个操作,这里就是把modCount的值赋值给expectedModCount 。
2.找到类中的迭代方法next();
进来第一步就看到调用了checkForComodification()方法,看名字就知道这是一个检验的方法
3.进入checkForComodification()方法
这里只是做了判断,如果modCount != expectedModCount
就会抛出ConcurrentModificationException
异常。
因为在创建迭代器的时候,modCount的值就赋值给expectedModCount了,按道理来讲它们两个应该是相等的对吧。但是我们要知道,在对集合做添加和删除时都会对modCount进行++的操作所以说,如果我们在迭代的时候对集合进行了增删的操作就会使modCount != expectedModCount
触发FailFast机制,导致抛出异常。
总结:modCount就相当于一个版本号。你在每次对集合进行增删的操作时都会对这个版本号进行升一级的操作。你在迭代的时候检验当前版本号和创建迭代器时的版本号不一致时就会报错。
ArrayList 中动态扩容是如何实现的
假设现在集合中有10个容量已经满了,现在来添加第11个元素
其实上边add方法已经见到了。咱们一起在梳理一遍:
1.找到add方法
size:当前集合的大小也就是10。
调ensureCapacityInternal(int minCapacity)方法传入size+1
也就是11
2.进入ensureCapacityInternal(int minCapacity)方法
minCapacity的值为11
下一步直接进入ensureExplicitCapacity(int minCapacity)方法了;calculateCapacity方法详见上边的,就是返回了数组长度和minCapacity两数的最大值,当前数组的长度为10,也就是返回11嘛
3.调用ensureExplicitCapacity(int minCapacity)方法传入11
modCount++:上边已经说过了吧,用于FailFast 机制
if条件肯定满足吧:11-10>0
没毛病
4.进入grow(int minCapacity)方法,传入11
注意看:oldCapacity值为10。newCapacity的值为10+10/2=15吧。(右移一位就是除以2)
第一个if条件不满足吧,第二个也不满足吧。直接数组拷贝,重新赋值给elementData,现在elementData数组是不是就扩容到15了,就可以添加第11个元素了。