大家好,最近开始我要写博客了,欢迎大家查阅探讨,欢迎评论,希望打架大家一起共同进步!
我准备先从集合框架开始入手,对其中经常用的集合类进行讲解。本节讲解ArrayList。
下面为目录:
List接口
List接口为Collection直接接口。List所代表的是有序的Collection,即它用某种特定的插入顺序来维护元素顺序。存储有序的,可以重复 、可以为 null 的元素.
新增的方法:
删除:remove(int index)
改:set(int index,Object obj)、List subList(int fromIndex, int toIndex) (返回左闭右开子集合)
查:get(int index)、int indexOf(Object obj)、int lastIndexOf(Object obj)、
增:add(int index,Object obj)
ArrayList
首先我们来看ArrayList,它是线性表用顺序存储结构(具体为数组)实现
线性表中的顺序表
下面是具体内容:
1、数据结构
底层使用数组(内部是用Object[]实现的) ArrayList是List接口的(底层使用数组)可变数组的实现。其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。源码定义如下:
2、构造方法
ArrayList提供了三种方式的构造器:
第一种,构造一个指定初始容量的空列表
第二种 构造一个默认初始容量为10的空列表
第三种 根据集合的迭代器返回的顺序构造包含指定集合元素的列表。
3、存储数据
ArrayList提供了5种存储数据的方法:
set(int index, E element)(替换)
add(E e)(会添加到尾部)
add(int index, E element)(元素插入到指定位置)
addAll(Collection
4、方法源码讲解
下面介绍一些方法的源码,还有需要探讨的方法,请跟我联系
set(int index, E element)(替换)
源码解读:
先是检查索引是否在size大小范围内
取得替换的值作为返回值
再完成替换
然后返回替换的值
get(int index) 读取
add(int index,Object obj) 增加 元素插入到指定位置
检查索引是否在范围内
然后如果大小不够,+1扩容,底层是数组复制(所以如果插入大量值,一定要指定初始容量,不然很费内存)
插入,size大小+1
List subList(int fromIndex, int toIndex) (返回左闭右开子集合)
subListRangeCheck方法是判断fromIndex、toIndex是否合法,如果合法就直接返回一个subList对象,注意在产生该new该对象的时候传递了一个参数 this ,该参数非常重要,因为他代表着原始list。
可以从SubList源码看出,所有操作都是操作parentList
即subList返回的SubList同样也是AbstractList的子类,同时它的方法如get、set、add、remove等都是在原列表上面做操作,它并没有像subString一样生成一个新的对象。所以subList返回的只是原列表的一个视图,它所有的操作最终都会作用在*原列表*上。
subList生成子列表后,不要试图去操作原列表,否则会造成子列表的不稳定而产生异常
List<Integer> list3 = new ArrayList<Integer>();
list3.add(2);
list3.add(3);
// 通过subList生成一个与list1一样的列表list3
List<Integer> list4 = list3.subList(0, list3.size());
// 修改list3
list3.add(3);
System.out.println("list3'size:" + list3.size());
System.out.println("list4'size:" + list4.size());
结果为
false
true
fail-fast机制
:list3就抛出ConcurrentModificationException异常
我们来看SubList的size方法,size方法首先会通过checkForComodification()验证,然后再返回this.size
该方法表明当原列表的modCount与this.modCount不相等时就会抛出ConcurrentModificationException。
同时我们知道modCount 在new的过程中 “继承”了原列表modCount,只有在修改该列表(子列表)时才会修改该值(先表现在原列表后作用于子列表)。而在该实例中我们是操作原列表,原列表的modCount当然不会反应在子列表的modCount上啦,所以才会抛出该异常。
对于子列表视图,它是动态生成的,生成之后就不要操作原列表了,否则必然都导致视图的不稳定而抛出异常。最好的办法就是将原列表设置为只读状态,要操作就操作子列表.
下面是检查修改的源码:
推荐使用subList处理局部列表
在开发过程中我们一定会遇到这样一个问题:获取一堆数据后,需要删除某段数据。例如,有一个列表存在1000条记录,我们需要删除100-200位置处的数据,可能我们会这样处理:
for(int i = 0 ; i < list1.size() ; i++){
if(i >= 100 && i <= 200){
list1.remove(i);
当然这段代码存在问题,list remove之后后面的元素会填充上来,所以需要对i进行简单的处理,当然这个不是这里讨论的问题。
这个应该是我们大部分人的处理方式吧,其实还有更好的方法,利用subList,子列表的操作都会反映在原列表上。所以下面一行代码全部搞定:
list1.subList(100, 200).clear();
简单而不失华丽!!!!!
5、ArrayList的扩容机制
ArrayList的扩容机制是比较消耗资源的。
源码解读:
//当前需要的长度超过了数组长度,进行扩容处理
//新的容量 = 旧容量 * 1.5 + 1
hugeCapacity(minCapcity)方法是确保最大容量为一定,如果大于最大,就等于最大。
最大值为: Integer.MAX_VALUE-8
//数组拷贝,生成新的数组
ArrayList每次新增一个元素,就会检测ArrayList的当前容量是否已经到达临界点,如果到达临界点则会扩容1.5倍。
然而ArrayList的扩容以及数组的拷贝生成新的数组是相当耗资源的。
所以若我们事先已知集合的使用场景,知道集合的大概范围,我们最好是指定初始化容量,
这样对资源的利用会更加好,尤其是大数据量的前提下,效率的提升和资源的利用会显得更加具有优势。请为集合指定初始容量
6、Fail-Fast机制
Fail-Fast机制 , “快速失败”也就是fail-fast,它是Java集合的一种错误检测机制
在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒在将来不确定的时间发生任意不确定行为的风险。
从上面的源代码我们可以看出,ArrayList中无论add、remove、clear方法只要是涉及了改变ArrayList元素的个数的方法都会导致modCount的改变。所以我们这里可以初步判断由于expectedModCount 得值与modCount的改变不同步,导致两者之间不等从而产生fail-fast机制。知道产生fail-fast产生的根本原因了,我们可以有如下场景:
有两个线程(线程A,线程B),其中线程A负责遍历list、线程B修改list。线程A在遍历list过程的某个时候(此时expectedModCount = modCount=N),线程启动,同时线程B增加一个元素,这是modCount的值发生改变(modCount + 1 = N + 1)。线程A继续遍历执行next方法时,通告checkForComodification方法发现expectedModCount = N ,而modCount = N + 1,两者不等,这时就抛出ConcurrentModificationException 异常,从而产生fail-fast机制。