由JDK源码学习ArrayList
ArrayList是实现了List接口的动态数组.与java中的数组相比,它的容量能动态增长.ArrayList的三大特点:
① 底层采用数组结构
② 有序
③ 非同步
下面我们从ArrayList的增加元素、获取元素、删除元素三个方面来学习ArrayList。
ArrayList添加元素
因为ArrayList是采用数组实现的,其源代码比较简单.首先我们来看ArrayList的add(E e).以下代码版本是jdk7。
public boolean add(E e) {
// 检查数组容量
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// 计算数组长度
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
// 模数自增,fail-fast机制
modCount++;
// 如果超过当前数组的长度,调用grow()方法增加数组长度
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 新的容量=oldCapacity+oldCapacity/2
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);
}
从上面的代码可以看到,add()方法关键点在于数组容量的处理,添加元素只是将在elementData[size++]处保存e的引用.从grow()方法中我们看到,数组的动态增长时的新长度=原长度+原长度/2.这里和jdk6中有点区别,具体可以查看下jdk6的源码.
ArrayList中还可以添加元素到指定位置.add(index,element):
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
// 将index之后的元素往后挪一个位置,腾出elementData[i]的位置
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
代码中看到,add(index,element)方法会造成element[index+1]到element[size]的元素往后挪动,极端情况下(size+1>element.length),要新建数组并且挪动所有元素.这会带来额外的开销,所以,如果不是特别需要,建议使用add(e)方法添加元素.
ArrayList获取元素
ArrayList底层是采用数组存储,所以最简单也最快的获取方式是通过脚标访问.
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
接下来,我们看下ArrayList的遍历.ArrayList有三种遍历方式:脚标访问,foreach循环遍历,迭代器遍历.下面我们来对比下三种方式的效率.
public class ArrayListDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
for(int i = 0;i<10000000;++i){
list.add(i);
}
System.out.println("access by index spend time : "+printListByIndex(list));
System.out.println("access by for spend time : "+printListByFor(list));
System.out.println("access by iterator spend time : "+printListByIterator(list));
}
public static long printListByIndex(List list) {
if(checkList(list)){
throw new IllegalArgumentException("list is null or list.size=0");
}
long startTime = System.currentTimeMillis();
for(int i=0;i<list.size();++i){
list.get(i);
}
long endTime = System.currentTimeMillis();
return endTime - startTime;
}
public static long printListByFor(List list) {
if(checkList(list)){
throw new IllegalArgumentException("list is null or list.size=0");
}
long startTime = System.currentTimeMillis();
for(Object obj : list){
//
}
long endTime = System.currentTimeMillis();
return endTime - startTime;
}
public static long printListByIterator(List list) {
if(checkList(list)){
throw new IllegalArgumentException("list is null or list.size=0");
}
long startTime = System.currentTimeMillis();
Iterator iterator = list.iterator();
while(iterator.hasNext()){
//
iterator.next();
}
long endTime = System.currentTimeMillis();
return endTime - startTime;
}
public static boolean checkList(List list){
return null==list || list.size()<1;
}
}
运行结果如图:

ps:每次运行的结果可能不同,但在我的几次试验中,用脚标访问的方式是最快的,使用foreach循环是相对而言最慢的.
由上面的运行结果,建议遍历ArrayList时使用脚标遍历,这样效率最高.
ArrayList删除元素
public E remove(int index) {
//检查index合法性
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
// 如果移除最后一个元素,则elementData[--size]==null,
// 如果不是最后一个元素,则将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;
}
由上面的代码,可以知道,如果删除的元素不是最后一个元素,会造成元素的移动.这也会带来额外的开销.
由上面的学习和分析,我们可以得出如下结论:ArrayList底层采用的数组结构,对get(index)、set(index,element)操作具备很好的性能,但频繁的增删会影响ArrayList的性能(数组中元素位置移动的开销或者新建数组转移元素带来的开销).所以ArrayList并不适合频繁增删元素的应用场景,另外,在初始化ArrayList时,如果能够预估ArrayList容量来设置初始容量,会减少ArrayList转移元素时的开销,从而提升应用程序的性能.
以上就是本人对ArrayList源码的学习,欢迎大家一起交流讨论!
由JDK源码学习ArrayList的更多相关文章
- 从JDK源码学习Arraylist
从今天开始从源码去学习一些Java的常用数据结构,打好基础:) Arraylist源码阅读: jdk版本:1.8.0 首先看其构造方法: 构造方法一: 第一种支持初始化容量大小,其中声明一个对象数组, ...
- JDK源码学习系列04----ArrayList
JDK源码学习系列04----ArrayList 1. ...
- JDK源码学习--String篇(二) 关于String采用final修饰的思考
JDK源码学习String篇中,有一处错误,String类用final[不能被改变的]修饰,而我却写成静态的,感谢CTO-淼淼的指正. 风一样的码农提出的String为何采用final的设计,阅读JD ...
- JDK源码学习系列05----LinkedList
JDK源码学习系列05----LinkedList 1.LinkedList简介 LinkedList是基于双向链表实 ...
- JDK源码学习系列03----StringBuffer+StringBuilder
JDK源码学习系列03----StringBuffer+StringBuilder 由于前面学习了StringBuffer和StringBuilder的父类A ...
- JDK源码学习系列02----AbstractStringBuilder
JDK源码学习系列02----AbstractStringBuilder 因为看StringBuffer 和 StringBuilder 的源码时发现两者都继承了AbstractStringBuil ...
- JDK源码学习系列01----String
JDK源码学习系列01----String 写在最前面: 这是我JDK源码学习系列的第一篇博文,我知道 ...
- JDK源码学习笔记——LinkedHashMap
HashMap有一个问题,就是迭代HashMap的顺序并不是HashMap放置的顺序,也就是无序. LinkedHashMap保证了元素迭代的顺序.该迭代顺序可以是插入顺序或者是访问顺序.通过维护一个 ...
- JDK1.8源码学习-ArrayList
JDK1.8源码学习-ArrayList 目录 一.ArrayList简介 为了弥补普通数组无法自动扩容的不足,Java提供了集合类,其中ArrayList对数组进行了封装,使其可以自动的扩容或缩小长 ...
随机推荐
- A/B_test改变新旧网页 观察用户的引流效果
代码处:https://github.com/xubin97/Data-analysis_exp2 分析A/B测试结果 目录 简介 I - 概率 II - A/B 测试 简介 首先这个项目数据来自某公 ...
- 微信授权获取code(微信支付)
摘要:最近在做h5支付,然后发现一个问题,微信自带浏览器不支持h5支付,然后后台又做了一个微信支付的接口,然后要传code参数,代码写好总结后,就发到这里记录一下: 因为有两个支付接口,所以首先判断打 ...
- H5微信自定义分享链接(设置标题+简介+图片)
起源:最近公司在做招募广告的html5页面,然后做出来后,产品提出一个问题,需要分享出去的链接是卡片形式,内容也要自己定义,这下就难到我了,因为是第一次遇到这种需求,果断百度,然而,我就像大家一样,看 ...
- Ruby(2): 基本语法上
表达式和变量: 这两点和其他主流的编程语言基本没有差别,这里直接跳过. 需要注意的是 ruby中 x=x+1 可以写成 x+=1 但是不支持 x++ , x-- 等一元运算符 比较运算符和表达式: ...
- i.mx6 Android5.1.1 初始化流程之init进程(未完成)
概述: 接在i.mx6 Android5.1.1 初始化流程之框架之后 参考资料:http://blog.csdn.net/mr_raptor/article/category/799879 相关源码 ...
- [C#]跨模块的可选参数与常量注意事项
假设某个DLL里有这么一个类: // Lib.dll public class Lib { public const string VERSION = "1.0"; public ...
- 记一次tomcat自动退出问题
问题 环境: centos/tomcat8/jdk1.8 最近遇到部署在服务器的tomcat总是过一段时间就自动结束进程 ; 通过监控tomcat 日志文件(tail -f ./logs/catali ...
- 撩课-Web大前端每天5道面试题-Day15
1.请描述一下 cookies,sessionStorage 和 localStorage 的区别? cookie是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过 ...
- groovy闭包科里化参数
科里化闭包:带有预先绑定形参的闭包.在预先绑定一个形参之后,调用闭包时就不必为这个形参提供实参了.有助于去掉方法调用中的冗余重复. 使用curry方法科里化任意多个参数 使用rcurry方法科里化后面 ...
- sql中,In和where的区别
SQL 语句中In 和 Where 的含义不同.应用解释如下: 1.如需有条件地从表中选取.删除.更新数据时,使用Where:2.In只作为Where条件子句下的一个运算符,除了In之外还有Betwe ...