转载请注明出处:http://www.cnblogs.com/qm-article/p/8833831.html

一、介绍

   对于ArrayList,可以说诸位绝不陌生,可以说是在诸多集合中运用的最多一个类之一,那么它是怎样构成,怎样实现的呢,相信很多人都知道数组构成的,没毛病,如果遇到面试的时候,估计还会问,它的默认大小是多少?它是怎样扩容的?它的一个属性modCount有啥作用?线程安全性怎么样?。。。。等待一系列问题。下面就围绕着对该集合的运用来展开,如增删改查,这四个方面。在介绍之前,先来看看该集合的性质

1、里面的元素是有序的(指的是添加的顺序和排列的顺序是一致的)

2、可以添加重复元素

3、增删慢,查询快

4、内部采用了Object数组来存储元素,size为元素个数

5、有线程安全问题

二、增加

先来贴下增加元素的源码

     public boolean add(E e) {

         ensureCapacityInternal(size + 1);  // Increments modCount!!
elementData[size++] = e;
return true;
}

添加某个元素,在使用默认构造函数的时候,且是第一次添加元素,第三行代码会触发初始化操作,即对数组扩容到默认大小10,点进去看,如下

 private void ensureCapacityInternal(int minCapacity) {
//若使用的默认构造函数,且是第一次添加,则返回true,此时开始初始化话
if (elementData == 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);
} 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、先判断有没有初始化,没有则初始化,有则跳过这步骤

2、检查要扩容的大小和数组的长度哪个大,如果数组长度大,则还没到需要扩容的时候即跳过,进行添加元素同时size+1,反之,则进行下一步grow方法(这里说明下关于size和数组的大小,这两个是不同的概念,这两者关系必满足size<=length,size指的是数组里的元素个数,length指的是数组长度)

3、grow(),该方法是ArrayList扩容的关键方法,在它里面有这几个逻辑判断(注意此时意味着minCapacity超过了数组的长度length)

1、获取数组长度,为扩容大小提供值

     2、默认扩大为原数组长度的1.5倍(这里面有精度运算,不一定真的恰好是1.5倍),你扩容后的长度newCapacity它的值为原数组长度+该长度向左移1个长度

(即除以2),如,原来是10,则扩容后为10+10>>1=15。

       3、扩容后长度再去和传进来的参数minCapacity去比较大小,取最大值,将最大值赋值为扩容后的长度。

4、扩容后的长度再去和MAX_ARRAY_SIZE做上一步操作,该值为Integer.MAX_VALUE - 8,可以知道ArrayList里数组最大的长度就是

Integer.MAX_VALUE,这步在hugeCapacity里

      5、利用工具类Arrays去扩容(实际是先new了一个数组,再利用System类的元素复制将元素移动到新数组里,再返回,至于为啥不用for循环,因为这个类的

效率比for循环高,是一个本地方法)

以上就是扩容步骤,

扩容完之后,就开始添加元素了elementData[size++] = e;同时size增一。

对于ArrayList方法其它add重载方法,就不一一介绍了,扩容步骤都是一样的,

三、移除

同样,先贴下代码

 public E remove(int index) {
rangeCheck(index);//当index>=size会抛异常 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;
}

先去检查移除的位置的正确性,或许会有人问,怎么不去检查index是否大于0?其实继续往下看就知道了,在第五行代码,若为负数的话,则会直接抛异常ArrayIndexOutOfBoundsException。

若index在正常范围内,则去判断要不要移动元素,即index后面的所有元素都往前移动一步,最后将size-1这个位置的元素设为null,来帮助垃圾回收,返回旧值。。

对于它的重载函数,remove(Object o),代码如下,要注意的是,它移除的是从数组下标为0开始往后找的第一个元素,再利用fastRemove来进行移除(和上一个remove(int)比较就是少了index判断),最后返回true,移除成功,fasle移除失败

 public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

该集合还提供大规模的移除,不过用的不多(提供给子类和同包类用的)。贴下代码

 protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved); // clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}

具体原理和之前移除一个元素一样,不过多叙述

四、查找

前面的介绍说该集合查找快,让我们,它比增删快在哪?

 public E get(int index) {
rangeCheck(index); return elementData(index);
}

代码非常精简,检查了下index的正确性,然后直接返回,因为它是一个数组,可以直接定位下标。相比增删,少了移动元素,少了新创建数组。必然快,而对于LinkedList这个集合,却和ArrayList相反,关于LinkedList这个集合,在后面的博文中会出现,到时候会做一个比较。

五、修改

 public E set(int index, E element) {
rangeCheck(index); E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}

同样先检查index的正确性,然后直接改变原index位置的值,返回旧值。

六、modCount的作用

细心的人会发现,每一次的增删都有modCount这个变量的出现,并且每次都是+1,至于为什么进行“改查”modCount却不改变,这是因为,该集合是一个数组,如果进行增加或删除,则size必然改变,那么在遍历过程中,肯定找不到正确的结果或者直接数组越界异常,这都是我们不想要的,改查,只是改变元素值,而不是元素个数。在了解这个变量之前,先来了解下,快速失败(fail-fast)和安全失败(fail-safe)的两个概念.

fail-fast 快速失败,产生于遍历集合过程中,如某个线程对集合集合遍历时,有另外线程对该集合做修改操作,则会抛出ConcurrentModificationException异常,其作用是用来检测错误,并不一定发生。

 fail-safe 安全失败,在遍历集合,并不是直接在原集合中操作,而是先复制一个集合,在复制的集合中操作,这样就不会产生ConcurrentModificationException异常。缺点是改变后不能看到改变后的结果。

其中安全失败里提到的复制集合,并不是直接将原来的集合赋给新集合,如list_1=list_2,这样只是把内存中的引用地址给了list_1,如果list_1也进行增删操作,那么list_2也能得到相应的结果,即两个集合操作都是对同一个堆内存的地址里的数据进行操作,前面说的复制是另开辟一个堆内存。(切记)。

在了解这两个概念后,应该可以猜的出来,modCount的作用就是用来fail-fast的,具体体现在如下

查看ArrayList源码可知,它有一个内部类Itr(实现了Iterator),这个类的作用就是使ArrayList集合可以迭代操作,该类有这几个方法,如下图

 private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount; public boolean hasNext() {
return cursor != size;
} @SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
} public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

在建立该类的实例,首先会初始化几个成员变量,cursor和、astRet、expectedModCount,值依次为0,-1,modCount。

hasNext(),是用来判断集合有没有下一个元素,该代码是直接判断当前指针cursor和元素个数size是否相等。

next(),先会去检查modCount的值和expectedModCount值是否相等,如果不等,则直接抛出异常,看到这,或许大家就明白了modCount的作用了吧,modCount值要改变必然要对集合进行增或删操作。之后就取当前指针cursor位置的值。

remove(),该方法也是首先会去判断集合有没有被改变,之后进行相应操作。

七、最后

对于ArrayList的线程安全问题,可以采用工具类Collections,该类有一个方法synchronizedList(List),可保证线程安全,但效率可能有点低,

还有一个方法就是采用CopyOnWriteArrayList类来操作数据。

------------------------------------------------------------------------------------------------------------------------------------华丽的分界线---------------------------------------------------------------------------------------------------------------------------------------

以上就是个人对ArrayList集合的见解,若有不足或错误之处,还望指正,

集合之ArrayList的源码分析的更多相关文章

  1. List-ArrayList集合基础增强底层源码分析

    List集合基础增强底层源码分析 作者:Stanley 罗昊 [转载请注明出处和署名,谢谢!] 集合分为三个系列,分别为:List.set.map List系列 特点:元素有序可重复 有序指的是元素的 ...

  2. ArrayList迭代器源码分析

    集合的遍历 Java集合框架中容器有很多种类,如下图中: 对于有索引的List集合可以通过for循环遍历集合: List<String> list = new ArrayList<& ...

  3. ArrayList的源码分析

    在项目中经常会用到list集合来存储数据,而其中ArrayList是用的最多的的一个集合,这篇博文主要简单介绍ArrayList的源码分析,基于JDK1.7: 这里主要介绍 集合 的属性,构造器,和方 ...

  4. java集合系列之LinkedList源码分析

    java集合系列之LinkedList源码分析 LinkedList数据结构简介 LinkedList底层是通过双端双向链表实现的,其基本数据结构如下,每一个节点类为Node对象,每个Node节点包含 ...

  5. Java集合系列[4]----LinkedHashMap源码分析

    这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...

  6. List-LinkedList、set集合基础增强底层源码分析

    List-LinkedList 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 继上一章继续讲解,上章内容: List-ArreyLlist集合基础增强底层源码分析:https:// ...

  7. Java集合系列:-----------03ArrayList源码分析

    上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayLi ...

  8. ArrayList实现源码分析

    本文将以以下几个问题来探讨ArrayList的源码实现 1.ArrayList的大小是如何自动增加的 2.什么情况下你会使用ArrayList?什么时候你会选择LinkedList? 3.如何复制某个 ...

  9. Java集合:HashSet的源码分析

    Java集合---HashSet的源码分析   一.  HashSet概述: HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特别是它不保证该 ...

随机推荐

  1. MYSQL数据库学习十七 日志管理

    17.1 MySQL软件所支持的日志 MySQL所支持的日志文件里,除了二进制日志文件外,其他日志文件都是文本文件.默认情况下,MySQL只会启动错误日志文件,其他日志文件则需要手动启动. 二进制日志 ...

  2. JSON Web Token - 在Web应用间安全地传递信息

    转载自:http://blog.leapoahead.com/2015/09/06/understanding-jwt/ JSON Web Token(JWT)是一个非常轻巧的规范.这个规范允许我们使 ...

  3. 【译】Java、Kotlin、RN、Flutter 开发出来的 App 大小,你了解过吗?

    现在开发 App 的方式非常多,原生.ReactNative.Flutter 都是不错的选择.那你有没有关注过,使用不同的方式,编译生成的 Apk ,大小是否会有什么影响呢?本文就以一个最简单的 He ...

  4. 《UNIX网络编程 卷1:套接字联网API》读书笔记(一):网络编程简介

    概述 要编写通过计算机网络通信的程序,首先要确定这些程序相互通信所用的协议.大多数网络是按照划分成客户和服务器来组织的.本章及后续章节的焦点是TCP/IP协议族,也可称为网际协议族.下图为客户与服务器 ...

  5. 【HTTP协议】---HTTP协议详解

    HTTP协议详解 一.HTTP简介 1.HTTP协议,即超文本传输协议(Hypertext transfer protocol).是一种详细规定了浏览器和万维网(WWW = World Wide We ...

  6. 敏捷冲刺每日报告——Day5

    1.情况简述 Alpha阶段第一次Scrum Meeting 敏捷开发起止时间 2017.10.29 00:00 -- 2017.10.30 00:00 讨论时间地点 2017.10.29晚6:00, ...

  7. Python 实现双端队列 Deque

    操作 Deque() 创建一个空的双端队列 add_front(item) 从队头加入一个item元素 add_rear(item) 从队尾加入一个item元素 remove_front() 从队头删 ...

  8. iOS开发之Objective-C与JavaScript的交互

    UIWebView是iOS最常用的SDK之一,它有一个stringByEvaluatingJavaScriptFromString方法可以将javascript嵌入页面中,通过这个方法我们可以在iOS ...

  9. 乐动力APP案例

    第一部分 调研, 评测 下载软件并使用起来,描述最简单直观的个人第一次上手体验. 这款软件的主界面功能还是比较完善,里面有多个关于运动相关的数据,还有一些推荐健身教程,记录功能也十分不错,其中最难理解 ...

  10. Beta冲刺Day3

    项目进展 李明皇 今天解决的进度 完善了程序的运行逻辑(消息提示框等) 明天安排 前后端联动调试 林翔 今天解决的进度 向微信官方申请登录验证session以维护登录态 明天安排 继续完成维护登录态 ...