ArrayList详解,源码解读

来源:互联网 发布:好运通超市软件 编辑:程序博客网 时间:2024/05/22 13:38

大家好,最近开始我要写博客了,欢迎大家查阅探讨,欢迎评论,希望打架大家一起共同进步!
我准备先从集合框架开始入手,对其中经常用的集合类进行讲解。本节讲解ArrayList。
下面为目录:

  • List接口
      • ArrayList
        • 1数据结构
        • 2构造方法
        • 3存储数据
        • 4方法源码讲解
        • 5ArrayList的扩容机制
        • 6Fail-Fast机制
        • 7内存结构

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机制。

7、内存结构:

这里写图片描述

原创粉丝点击