JAVA语言集合框架源码解析之ArrayList
小标 2018-10-12 来源 : 阅读 1297 评论 0

摘要:本文主要向大家介绍了JAVA语言集合框架源码解析之ArrayList,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。

本文主要向大家介绍了JAVA语言集合框架源码解析之ArrayList,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。


ArrayList 可能是很多人使用得最为频繁的容器类了,ArrayList 实现了 List 接口,是一个有序容器,即存放元素的顺序与添加顺序相同,允许添加相同元素,包括 null ,底层通过数组来实现数据存储,容器内存储的元素个数不能超出数组空间。所以向容器中添加元素时如果发现数组空间不足,容器会自动对底层数组进行扩容操作


ArrayList 的类声明


public class ArrayList<e> extends AbstractList<e>

  implements List<e>, RandomAccess, Cloneable, java.io.Serializable</e></e></e>

   


从其实现的几个接口可以看出来,ArrayList 是支持快速访问,可克隆,可序列化的


包含的成员变量


//序列化ID

private static final long serialVersionUID = 8683452581122892189L;

 

//集合默认的初始大小

private static final int DEFAULT_CAPACITY = 10;

 

//如果外部为集合设置的初始化大小为 0,则 elementData 指向空数组对象 EMPTY_ELEMENTDATA

private static final Object[] EMPTY_ELEMENTDATA = {};

 

//如果在初始化集合时使用的是无参数的构造函数,则 elementData 指向空数组对象 DEFAULTCAPACITY_EMPTY_ELEMENTDATA

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

 

//包含实际元素的数组

transient Object[] elementData;

 

//集合大小

private int size;

   


elementData 是一个 Object 类型的数组对象,即可用来存放任何对象类型,也是 ArrarList 中用来存放数据的容器。而 ArrayList 是一个泛型类,我们在初始化时就直接指定了数据类型,Java泛型只是编译器为我们提供的语法糖,方便我们在向 elementData 存取数据时,将之自动转换为特定的类型


包含的构造函数


//指定集合的初始容量,以此来进行数组的初始化操作

 public ArrayList(int initialCapacity) {

  if (initialCapacity > 0) {

this.elementData = new Object[initialCapacity];

  } else if (initialCapacity == 0) {

this.elementData = EMPTY_ELEMENTDATA;

  } else {

throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);

  }

 }

 

 //使用默认的初始化大小

 public ArrayList() {

  this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

 }

 

 //传入一份初始数据,以此进行初始化

 public ArrayList(Collection<!--? extends E--> c) {

  elementData = c.toArray();

  if ((size = elementData.length) != 0) {

// c.toArray might (incorrectly) not return Object[] (see 6260652)

if (elementData.getClass() != Object[].class)

 elementData = Arrays.copyOf(elementData, size, Object[].class);

  } else {

this.elementData = EMPTY_ELEMENTDATA;

  }

 }

   


可以在初始化 ArrayList 的时候传入集合的初始化大小,这通常来说都是更为高效率一些的,因为如果是让集合在赋值的过程中自动扩容,则可能会需要进行多次扩容操作,而每次扩容都需要复制原有数据到新数组,这会导致运行效率降低


看看我们常用的几个存取和移除数据的方法


在获取指定索引处的元素时,都是直接通过坐标指向该元素 (E) elementData[index],而无需从头开始遍历集合,所以说 ArrayList 的遍历效率较高


//通过索引直接访问数组

@SuppressWarnings("unchecked")

E elementData(int index) {

 return (E) elementData[index];

}

 

//获取索引 index 处的元素值

public E get(int index) {

 rangeCheck(index);

 return elementData(index);

}

 

//将索引 index 出的元素值置为 element,并返回原始数值

public E set(int index, E element) {

 rangeCheck(index);

 E oldValue = elementData(index);

 elementData[index] = element;

 return oldValue;

}

   


ArrayList 在存入数据时相对来说就不是那么理想了

如果是直接向集合尾端添加数据 add(E e),则先检查是否需要扩容,需要的话则创建一个新的符合大小的数组,并将原数组中的元素移到新数组中,再向数组尾端添加待存入的元素

如果是向集合非尾端位置添加数据 add(int index, E element),一样需要先检查是否需要扩容,然后将数组中索引 index 后的所有元素向后推移一位,然后将 element 插入到空出的位置上

由此看出来,向集合添加元素由于可能会导致数组扩容,从而导致数组元素的大量移动,所以说 ArrayList 存入数据的效率并不高


//向集合添加数据

public boolean add(E e) {

 //检查是否需要扩容

 ensureCapacityInternal(size + 1);

 //赋值

 elementData[size++] = e;

 return true;

}

 

//将元素 element 添加索引 index 位置

public void add(int index, E element) {

 rangeCheckForAdd(index);

 //检查是否需要扩容

 ensureCapacityInternal(size + 1);

 //将索引 index 后的所有数值向后推移一位 

 System.arraycopy(elementData, index, elementData, index + 1,size - index);

 //将 element 插入到空出的位置

 elementData[index] = element;

 //集合大小加1

 size++;

}

   


以上说的是存入单个元素,此外还有存入整个集合的情况


//向集合添加数据

 //如果待添加的数据不为空则返回 true,否则返回 false

 public boolean addAll(Collection<!--? extends E--> c) {

  Object[] a = c.toArray();

  int numNew = a.length;

  //检查是否需要扩容

  ensureCapacityInternal(size + numNew);

  //将数组 a 复制到 elementData 的尾端

  System.arraycopy(a, 0, elementData, size, numNew);

  size += numNew;

  return numNew != 0;

 }

 

 //从指定索引处添加数据

 //如果待添加的数据不为空则返回 true,否则返回 false

 public boolean addAll(int index, Collection<!--? extends E--> c) {

  rangeCheckForAdd(index);

  Object[] a = c.toArray();

  int numNew = a.length;

  //检查是否需要扩容

  ensureCapacityInternal(size + numNew);

  //需要移动的数组元素数量

  int numMoved = size - index;

  //因为要添加的数据可能刚好是从数组最尾端开始添加,所以 numMoved 可能为 0

  //所以只在 numMoved > 0 的时候才需要对数组的元素值进行移动,以此空出位置给数组 a

  if (numMoved > 0)

System.arraycopy(elementData, index, elementData, index + numNew, numMoved);

  //将数组 a 包含的数据添加到 elementData 中

  System.arraycopy(a, 0, elementData, index, numNew);

  size += numNew;

  return numNew != 0;

 }

   


再看下移除元素的方法

因为数组是一种内存地址连续的数据结构,所以移除某个元素同样可能导致大量元素的移动


//移除指定索引处的元素值,并返回该值

 public E remove(int index) {

  rangeCheck(index);

  modCount++;

  //待移除的元素值

  E oldValue = elementData(index);

  //因为要移除元素导致需要移动的元素数量

  int numMoved = size - index - 1;

  //因为要移除的元素可能刚好是数组最后一位,所以 numMoved 可能为 0

  //所以只在 numMoved > 0 的时候才需要对数组的元素值进行移动

  if (numMoved > 0)

System.arraycopy(elementData, index+1, elementData, index, numMoved);

  //不管数组是否需要对元素值进行移动,数组的最后一位都是无效数据了

  //此处将之置为 null 以帮助GC回收 

  elementData[--size] = null;

  return oldValue;

 }

 

 //移除集合中包含的第一位元素值为 o 的对象

 //如果包含该对象,则返回 true ,否则返回 false

 <code class    

   

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注编程语言JAVA频道!


本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 1 不喜欢 | 0
看完这篇文章有何感觉?已经有1人表态,100%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式AI+学习就业服务平台 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved