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的更多相关文章

  1. 从JDK源码学习Arraylist

    从今天开始从源码去学习一些Java的常用数据结构,打好基础:) Arraylist源码阅读: jdk版本:1.8.0 首先看其构造方法: 构造方法一: 第一种支持初始化容量大小,其中声明一个对象数组, ...

  2. JDK源码学习系列04----ArrayList

                                                                             JDK源码学习系列04----ArrayList 1. ...

  3. JDK源码学习--String篇(二) 关于String采用final修饰的思考

    JDK源码学习String篇中,有一处错误,String类用final[不能被改变的]修饰,而我却写成静态的,感谢CTO-淼淼的指正. 风一样的码农提出的String为何采用final的设计,阅读JD ...

  4. JDK源码学习系列05----LinkedList

                                             JDK源码学习系列05----LinkedList 1.LinkedList简介 LinkedList是基于双向链表实 ...

  5. JDK源码学习系列03----StringBuffer+StringBuilder

                         JDK源码学习系列03----StringBuffer+StringBuilder 由于前面学习了StringBuffer和StringBuilder的父类A ...

  6. JDK源码学习系列02----AbstractStringBuilder

     JDK源码学习系列02----AbstractStringBuilder 因为看StringBuffer 和 StringBuilder 的源码时发现两者都继承了AbstractStringBuil ...

  7. JDK源码学习系列01----String

                                                     JDK源码学习系列01----String 写在最前面: 这是我JDK源码学习系列的第一篇博文,我知道 ...

  8. JDK源码学习笔记——LinkedHashMap

    HashMap有一个问题,就是迭代HashMap的顺序并不是HashMap放置的顺序,也就是无序. LinkedHashMap保证了元素迭代的顺序.该迭代顺序可以是插入顺序或者是访问顺序.通过维护一个 ...

  9. JDK1.8源码学习-ArrayList

    JDK1.8源码学习-ArrayList 目录 一.ArrayList简介 为了弥补普通数组无法自动扩容的不足,Java提供了集合类,其中ArrayList对数组进行了封装,使其可以自动的扩容或缩小长 ...

随机推荐

  1. unity多线程

    多线程概念 多线程,是指实现多个线程并发执行的技术,合理利用多线程可以提升程序的性能,在unity中,一般是避免使用多线程的,unity对多线程的支持并不友好. 协程概念 协程,是指在主线程运行时开启 ...

  2. Delphi下OpenGL2d绘图(03)-画线

    一.前言 画线与画点基本上代码是相同.区别在于glBegin()的参数.绘制的框架代码可以使用 Delphi下OpenGL2d绘图(01)-初始化 中的代码.修改的部份为 Draw 函数的内容. 二. ...

  3. tomcat的8088端口被占用

    打开Dos:windows->输入cmd(想必这个都会) 在黑窗口中输入指令:netstat -ano | findstr 8080       指令的意思是找出占用8080端口的进程pid 再 ...

  4. guava文档API制作成chm文件

    将HTML制作成CHM.EXE需要用到一个小工具“HUGECHM”,将HTML打包成CHM文件 1.下载guava的最新的版本,网址:https://github.com/google/guava/w ...

  5. 网页布局之Div

    div(division分区) 它是一个块标签,主要用来把网页中相关的内容组织到一起 你可以把网页的头部放到一个标签中,主体部分放到另一个标签中 使用class类名描述div内容 要想区分每个div, ...

  6. Centos 7 ip地址

    vim /etc/sysconfig/network-scripts/ifcfg-ens33 HWADDR="00:15:5D:07:F1:02" TYPE="Ether ...

  7. 程序员必备技能:代码审查 (Google牛人谈Code Review)

    在上一篇博客里我暗示自己将不在为Google工作. 我还没有决定好去哪儿-有几个非常不错的工作机会让我选择.鉴于这段时间内我不受雇于任何公司,我想我可以写点和专业相关的东西,这些东西很有趣,但是如果我 ...

  8. 深入辨析jvm内存区域

    Java内存区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域: 程序计数器.虚拟机栈.本地方法栈.Java堆.方法区(运行时常量池).直接内存 程序计数器 当 ...

  9. Oracle中常用的命令,随着学习进度总结

    原创作品,欢迎转载,转载请在文章显眼位置注明出处:https://www.cnblogs.com/sunshine5683/p/10016569.html 开始之前先注意:在linux中切换到sqlp ...

  10. delphi之读写文件的三种方式

    一.Tstrings.Tstringlist procedure TForm1.Button2Click(Sender: TObject); var strlist: TStringList; pat ...