本篇分析ArrayList的源码,在分析之前先跟大家谈一谈数组。数组可能是我们最早接触到的数据结构之一,它是在内存中划分出一块连续的地址空间用来进行元素的存储,由于它直接操作内存,所以数组的性能要比集合类更好一些,这是使用数组的一大优势。但是我们知道数组存在致命的缺陷,就是在初始化时必须指定数组大小,并且在后续操作中不能再更改数组的大小。在实际情况中我们遇到更多的是一开始并不知道要存放多少元素,而是希望容器能够自动的扩展它自身的容量以便能够存放更多的元素。ArrayList就能够很好的满足这样的需求,它能够自动扩展大小以适应存储元素的不断增加。它的底层是基于数组实现的,因此它具有数组的一些特点,例如查找修改快而插入删除慢。本篇我们将深入源码看看它是怎样对数组进行封装的。首先看看它的成员变量和三个主要的构造器。

 //默认初始化容量
private static final int DEFAULT_CAPACITY = 10; //空对象数组
private static final Object[] EMPTY_ELEMENTDATA = {}; //对象数组
private transient Object[] elementData; //集合元素个数
private int size; //传入初始容量的构造方法
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0) {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
//新建指定容量的Object类型数组
this.elementData = new Object[initialCapacity];
} //不带参数的构造方法
public ArrayList() {
super();
//将空的数组实例传给elementData
this.elementData = EMPTY_ELEMENTDATA;
} //传入外部集合的构造方法
public ArrayList(Collection<? extends E> c) {
//持有传入集合的内部数组的引用
elementData = c.toArray();
//更新集合元素个数大小
size = elementData.length;
//判断引用的数组类型, 并将引用转换成Object数组引用
if (elementData.getClass() != Object[].class) {
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
}

可以看到ArrayList的内部存储结构就是一个Object类型的数组,因此它可以存放任意类型的元素。在构造ArrayList的时候,如果传入初始大小那么它将新建一个指定容量的Object数组,如果不设置初始大小那么它将不会分配内存空间而是使用空的对象数组,在实际要放入元素时再进行内存分配。下面再看看它的增删改查方法。

 //增(添加)
public boolean add(E e) {
//添加前先检查是否需要拓展数组, 此时数组长度最小为size+1
ensureCapacityInternal(size + 1);
//将元素添加到数组末尾
elementData[size++] = e;
return true;
} //增(插入)
public void add(int index, E element) {
//插入位置范围检查
rangeCheckForAdd(index);
//检查是否需要扩容
ensureCapacityInternal(size + 1);
//挪动插入位置后面的元素
System.arraycopy(elementData, index, elementData, index + 1, size - index);
//在要插入的位置赋上新值
elementData[index] = element;
size++;
} //删
public E remove(int index) {
//index不能大于size
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0) {
//将index后面的元素向前挪动一位
System.arraycopy(elementData, index+1, elementData, index, numMoved);
}
//置空引用
elementData[--size] = null;
return oldValue;
} //改
public E set(int index, E element) {
//index不能大于size
rangeCheck(index);
E oldValue = elementData(index);
//替换成新元素
elementData[index] = element;
return oldValue;
} //查
public E get(int index) {
//index不能大于size
rangeCheck(index);
//返回指定位置元素
return elementData(index);
}
每次添加一个元素到集合中都会先检查容量是否足够,否则就进行扩容,扩容的细节下面会讲到。我们先看具体增删改查要注意的地方。
增(添加):仅是将这个元素添加到末尾。操作快速。
增(插入):由于需要移动插入位置后面的元素,并且涉及数组的复制,所以操作较慢。
删:由于需要将删除位置后面的元素向前挪动,也会设计数组复制,所以操作较慢。
改:直接对指定位置元素进行修改,不涉及元素挪动和数组复制,操作快速。
查:直接返回指定下标的数组元素,操作快速。
通过源码看到,由于查找和修改直接定位到数组下标,不涉及元素挪动和数组复制所以较快,而插入删除由于要挪动元素,涉及到数组复制,操作较慢。并且每次添加操作还可能进行数组扩容,也会影响到性能。下面我们看看ArrayList是怎样动态扩容的。
 private void ensureCapacityInternal(int minCapacity) {
//如果此时还是空数组
if (elementData == EMPTY_ELEMENTDATA) {
//和默认容量比较, 取较大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//数组已经初始化过就执行这一步
ensureExplicitCapacity(minCapacity);
} private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果最小容量大于数组长度就扩增数组
if (minCapacity - elementData.length > 0) {
grow(minCapacity);
}
} //集合最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //增加数组长度
private void grow(int minCapacity) {
//获取数组原先的容量
int oldCapacity = elementData.length;
//新数组的容量, 在原来的基础上增加一半
int newCapacity = oldCapacity + (oldCapacity >> 1);
//检验新的容量是否小于最小容量
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
//检验新的容量是否超过最大数组容量
if (newCapacity - MAX_ARRAY_SIZE > 0) {
newCapacity = hugeCapacity(minCapacity);
}
//拷贝原来的数组到新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
每次添加元素前会调用ensureCapacityInternal这个方法进行集合容量检查。在这个方法内部会检查当前集合的内部数组是否还是个空数组,如果是就新建默认大小为10的Object数组。如果不是则证明当前集合已经被初始化过,那么就调用ensureExplicitCapacity方法检查当前数组的容量是否满足这个最小所需容量,不满足的话就调用grow方法进行扩容。在grow方法内部可以看到,每次扩容都是增加原来数组长度的一半,扩容实际上是新建一个容量更大的数组,将原先数组的元素全部复制到新的数组上,然后再抛弃原先的数组转而使用新的数组。至此,我们对ArrayList中比较常用的方法做了分析,其中有些值得注意的要点:
1. ArrayList底层实现是基于数组的,因此对指定下标的查找和修改比较快,但是删除和插入操作比较慢。
2. 构造ArrayList时尽量指定容量,减少扩容时带来的数组复制操作,如果不知道大小可以赋值为默认容量10。
3. 每次添加元素之前会检查是否需要扩容,每次扩容都是增加原有容量的一半。
4. 每次对下标的操作都会进行安全性检查,如果出现数组越界就立即抛出异常。
5. ArrayList的所有方法都没有进行同步,因此它不是线程安全的。
6. 以上分析基于JDK1.7,其他版本会有些出入,因此不能一概而论。

Java集合系列[1]----ArrayList源码分析的更多相关文章

  1. java集合系列之ArrayList源码分析

    java集合系列之ArrayList源码分析(基于jdk1.8) ArrayList简介 ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快, ...

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

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

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

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

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

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

  5. Java集合系列[3]----HashMap源码分析

    前面我们已经分析了ArrayList和LinkedList这两个集合,我们知道ArrayList是基于数组实现的,LinkedList是基于链表实现的.它们各自有自己的优劣势,例如ArrayList在 ...

  6. Java集合系列[2]----LinkedList源码分析

    上篇我们分析了ArrayList的底层实现,知道了ArrayList底层是基于数组实现的,因此具有查找修改快而插入删除慢的特点.本篇介绍的LinkedList是List接口的另一种实现,它的底层是基于 ...

  7. java多线程系列(九)---ArrayBlockingQueue源码分析

    java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...

  8. Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式

    在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...

  9. Java并发系列[3]----AbstractQueuedSynchronizer源码分析之共享模式

    通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...

随机推荐

  1. Intellij IDEA安装golang插件

    原文作者:Jianan - qinxiandiqi@foxmail.com 原文地址:http://blog.csdn.net/qinxiandiqi/article/details/50319953 ...

  2. netty开发教程(一)

    Netty介绍 Netty is an asynchronous event-driven network application framework  for rapid development o ...

  3. 【cogs 597】【dp】交错匹配

    597. 交错匹配 ★☆ 输入文件:crossa.in 输出文件:crossa.out 简单对照 时间限制:1 s 内存限制:128 MB [问题描写叙述] 有两行自然数. UP[1..N] . DO ...

  4. 【Java入门提高篇】Day7 Java内部类——局部内部类

    今天介绍第二种内部类--局部内部类. 局部内部类是什么?顾名思义,那就是定义在局部内部的类(逃).开玩笑的,局部内部类就是定义在代码块.方法体内.作用域(使用花括号"{}"括起来的 ...

  5. 知乎APP---案例分析

    产品: 这次我选择用来做案例分析的是--知乎. 知乎可以说是中文互联网最大的知识社交平台,拥有认真.专业和友善的独特气氛,分享用户间彼此的专业知识.经验和见解.因而在日常生活中,我用知乎搜索答案的概率 ...

  6. 自学Zabbix3.7.2-事件Event-来源与分类

    一.zabbix 事件从字面理解,就是发生了一个事情就算是一个事件.就在trigger的文章内,我们已经有用到事件,这个事件要讲概念真心不知道怎么说,就拿trigger事件来说,如果trigger从当 ...

  7. Python编程和 Lua编程的比较

    Python编程和 Lua编程的比较 2016.4.21 定义函数: python: def functionname( parameters ): "函数_文档字符串" func ...

  8. 「mysql优化专题」这大概是一篇最好的mysql优化入门文章(1)

    优化,一直是面试最常问的一个问题.因为从优化的角度,优化的思路,完全可以看出一个人的技术积累.那么,关于系统优化,假设这么个场景,用户反映系统太卡(其实就是高并发),那么我们怎么优化? 如果请求过多, ...

  9. XSS注入,js脚本注入后台

    曾经一度流行sql注入,由于现在技术的更新,已经看不到这问题了,但是又出来新的安全问题,XSS攻击,他的原理就是在前端提交表单的时候,在input标签当中输入js脚本,通过js脚本注入后台,请看下图. ...

  10. Lucene实现索引和查询

    0引言 随着万维网的发展和大数据时代的到来,每天都有大量的数字化信息在生产.存储.传递和转化,如何从大量的信息中以一定的方式找到满足自己需求的信息,使之有序化并加以利用成为一大难题.全文检索技术是现如 ...