什么是ArrayList?


  ArrayList是存储一组数据的集合,底层也是基于数组的方式实现,实际上也是对数组元素的增删改查;它的主要特点是

  • 有序;(基于数组实现)
  • 随机访问速度快;(进行随机访问的时候,只需要遍历所有的数组)
  • 可以为null;(基于数组实现)
  • 元素可以重复;(基于数组实现)
  • 线程是不安全的;(对于多个线程访问时,没有进行同步操作,之所以不安全也是为它的性能而设计,如果需要创建一个安全的ArrayList集合可以通过 Collections.synchronizedList(list);实现同步)

  

ArrayList结构分析


ArrayList类结构如下图:

    

我们可以看到ArrayList实现了List、RandomAccess、Cloneable、java.io.Serializable四个接口和继承了一个AbstractList抽象类,下面我们就来进一步的分析它们各自的作用;

  • Iterable :实现此接口以便支持foreach语法。
  • Collection:包含了集合基本方法。
  • AbstractCollection:实现了Collection里面大部分方法。
  • List:继承自Collection接口,包含了对集合操作的一些方法。
  • AbstractList:继承自AbstractCollection实现List部分方法。
  • Serializable:序列化的接口。
  • RandomAccess :用来表明其支持快速随机访问。
  • Cloneable:标识其支持对象复制。

ArrayList源码分析


下面我们主要从ArrayList属性构造方法核心的方法来进行解析:

ArrayList定义了两个私有属性:

    /**
* 用于存储数据的数组
*/
private transient Object[] elementData; /**
* 数组元素的实际个数
*/
private int size;

第一个属性主要是存储元素的一个数组, 第二个属性是用于记录数组元素的实际个数,而不是数组的长度。

ArrayList定义了三个构造方法:

    /**
* 数组初始容量构造方法
*
* @param initialCapacity
* @throws IllegalArgumentException
*
*/
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)    //如果指定的初始容量为负数,将抛出参数异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];    //指定一个存储数组初始容量值为:initialCapacity
} /**
* 默认构造方法
*/
public ArrayList() {
this(10);    //默认给定初始容量为10
} /**
* 给定一个参数集合的构造方法
* @param c
* @throws NullPointerException
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();    //转为数组
size = elementData.length;    //给定size初始值 if (elementData.getClass() != Object[].class)    // 是否成功转化为Object类型数组
elementData = Arrays.copyOf(elementData, size, Object[].class);    // 不为Object数组的话就进行复制
}

ArrayList 核心方法分析

添加元素  add(E e)

    public boolean add(E e) {  // 添加元素
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

从源码中看到在添加元素时调用ensureCapacityInternal方法,这个方法呢就是为了保证存储数组的大小够用,不会导致数组越界,下面具体看下ensureCapacityInternal实现

    private void ensureCapacityInternal(int minCapacity) {
modCount++;    // 结构性修改加1
// overflow-conscious code
if (minCapacity - elementData.length > 0)    //添加元素时长度 - 存储数组长度是否大于0,如果大于0说明数组大小够用,小于0则需要对存储数组扩容
grow(minCapacity);
}

如果存储数组需要进扩容操作,那么就会调用grow方法,这个方法会产生一个新的数组重新赋值给存储数组,新的的数组长度是原数组的1.5倍。下面具体看下grow实现

    private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;    // 原来的容量
int newCapacity = oldCapacity + (oldCapacity >> 1);    // 新的容量扩展为原来容量的1.5倍
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);  // 创建一个新的容量数组赋值给存储数组
}

访问元素  get(index)

    public E get(int index) {
rangeCheck(index);    // 检验索引是否合法 return elementData(index);
}

   在根据索引获取元素时,首先进行索引值的判断,索引值大于实际size的大小则抛出异常,否则继续根据索引值获取存储数组中的对象,rangeCheck实现

    private void rangeCheck(int index) {  
if (index >= size)    // 索引值大于实际size的大小则抛出异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

   根据索引值获取存储数组中的对象,elementData实现

    E elementData(int index) {
return (E) elementData[index];    // 返回存储数组对应索引的值,向下转型(Object -> E)
}

修改元素  set(int index, E element)

    public E set(int index, E element) {
rangeCheck(index);  // 检验索引是否合法 E oldValue = elementData(index);   // 原始值
elementData[index] = element;     // 把存储数组索引为index的值改为新值
return oldValue;     // 返回原始值
}

插入元素  add(int index, E element)

    public void add(int index, E element) {
rangeCheckForAdd(index);  // 检验索引是否合法 ensureCapacityInternal(size + 1); // 需要扩容则扩容操作
      // 拷贝到从下标为index+1位置开始的新的elementData数组中。
    // 即将当前位于该位置的元素以及所有后续元素右移一个位置。
System.arraycopy(elementData, index, elementData, index + 1,    
size - index);
elementData[index] = element;
size++;    // 实际元素长度+1
}

    由于ArrayList 是个动态数组,插入元素时需要移动指针操作,所以插入元素效率低。

删除指定位置元素   remove(int index)

    public E remove(int index) {
rangeCheck(index);    // 检验索引是否合法 modCount++;    // 结构性修改加1
E oldValue = elementData(index);  // 获取原来的值 int numMoved = size - index - 1;  // 需要移动的元素的个数
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // 最后一个元素设置为空 return oldValue;    // 返回原始值
}

删除元素时同样需要移动指针操作,删除元素效率低。

删除元素时首先判断是否为最后一个元素,如果不是最后一个,就按指定的索引从存储数组删除,索引后面所有元素左移一个位置,并把最后一个元素设置为null以便GC进行回收。

总结


很多技术,不只是只会用就行,研究它底层实现其实是很有趣的一件事,同时技术能够得到一定的提升。下篇继续学习LinkedList

如果有不足之处请指出,谢谢!

ArrayList源码学习----JDK1.7的更多相关文章

  1. ArrayList源码分析--jdk1.8

    ArrayList概述   1. ArrayList是可以动态扩容和动态删除冗余容量的索引序列,基于数组实现的集合.  2. ArrayList支持随机访问.克隆.序列化,元素有序且可以重复.  3. ...

  2. Java基础 ArrayList源码分析 JDK1.8

    一.概述 本篇文章记录通过阅读JDK1.8 ArrayList源码,结合自身理解分析其实现原理. ArrayList容器类的使用频率十分频繁,它具有以下特性: 其本质是一个数组,因此它是有序集合 通过 ...

  3. Java API学习(一) ArrayList源码学习

    ArrayList在平常用的还挺多的,用起来十分舒服,顺手.这里来学习一下它的源码. 类定义 下面是类的定义: public class ArrayList<E> extends Abst ...

  4. Java集合-ArrayList源码解析-JDK1.8

    ◆ ArrayList简介 ◆ ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAcc ...

  5. ArrayList源码学习

    1.ArrayList:基于数据实现,允许出现空值和重复元素,当ArrayList中添加的元素数量大于底层数组容量是,会通过扩容机制重新生成一个更大的数组.(非线程安全) 2.源码分析 构造函数 /* ...

  6. 死磕Java之聊聊ArrayList源码(基于JDK1.8)

    工作快一年了,近期打算研究一下JDK的源码,也就因此有了死磕java系列 ArrayList 是一个数组队列,相当于动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractLis ...

  7. ArrayList源码阅读----JDK1.8

    //定义一个默认的长度10 private static final int DEFAULT_CAPACITY = 10; //定义空的数组 private static final Object[] ...

  8. ReentrantLock源码分析--jdk1.8

    JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8AQS源码分析--jdk1.8ReentrantLock源码分 ...

  9. JDK1.8源码学习-ArrayList

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

随机推荐

  1. golang goroutine的调度

    golang goroutine的调度 1.什么是协程? 协程是一种用户态的轻量级线程. 2.进程.线程.协程的关系和区别: * 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度. ...

  2. JBox使用详解

    插件说明 - jBox 是一款基于 jQuery 的多功能对话框插件,能够实现网站的整体风格效果,给用户一个新的视觉享受. 运行环境 - 兼容 IE6+.Firefox.Chrome.Safari.O ...

  3. About Windows 10 SDK Preview Build 17110

    在 Windows Developer Day 活动同时,微软正式 Release 了 Windows 10 SDK Preview Build 17110. Windows 10 SDK Previ ...

  4. git pull error

    在图形界面中,执行拉取操作时,出现下面的错误. You asked to pull from the remote 'origin', but did not specifya branch. Bec ...

  5. 学习python之路_入门篇A

    偶尔经同事的介绍进入了金角大王的博客里,看到大王编写的文章都是关于python编程的,由于自己一直也是做软件测试方面的工作,也一直想往自动化测试方面发展,了解到利用python可以进行自动化测试操作, ...

  6. shell脚本中文件测试

    shell脚本中文件测试 author:headsen chen  2017-10-17  14:35:19 个人原创,转载请注明作者,否则 依法追究法律责任 [ -f  filename  ]   ...

  7. Problems at works

    1.ssh无法连接服务器:因部署ftp服务,误将/var目录的所有者和所属组改成了ftp,以致于SSH无法二次连接: 2.集群服务器的hadoop的datanode节点死亡,在对应节点拉起即可.若无法 ...

  8. WPF 16进制byte输入框

    在WPF中,针对byte类型的输入控件可以选用 XCEED 的免费库中的 Xceed.Wpf.Toolkit.ByteUpDown(可从nuget获取). 若要使该控件在界面上以16进制显示byte, ...

  9. Algorithm --> 最长回文子串

    1.中心扩展 中心扩展就是把给定的字符串的每一个字母当做中心,向两边扩展,这样来找最长的子回文串.算法复杂度为O(N^2). 但是要考虑两种情况: 1.像aba,这样长度为奇数. 2.想abba,这样 ...

  10. 基于hi-nginx的web开发(python篇)——表单处理和文件上传

    hi-nginx会自动处理表单,所以,在hi.py框架里,要做的就是直接使用这些数据. 表单数据一般用GET和POST方法提交.hi-nginx会把这些数据解析出来,放在form成员变量里.对pyth ...