下面我们来看看ArrayList的底层实现,

ArrayList继承了AbstractList,实现Cloneable、Serializable、RandomAccess接口,

它的成员属性有Object[]  elementData 和 int size,

显然底层是以可扩展的数组来存储元素,

新增元素

有如下这段代码,

public static void main(String[] args) {List<Integer> list = new ArrayList<Integer>();list.add(1);
}

我们进到add(E e)方法,下如图,

1 public boolean add(E e) {
2         ensureCapacityInternal(size + 1);  // Increments modCount!!
3         elementData[size++] = e;
4         return true;
5 }

size因为是成员属性,并且是基本数据类型,所以它的初始值为0,

第3行elementData[size++] = e;等价于第一步先elementData[0] = e ,第二步size自增,

第2行的 ensureCapacityInternal(size + 1),上面我们也提到了,Object[]  elementData是一个可动态扩展的一个数组,

因此我们需要校验当前的的容量是否满足元素的存储,如果不满足,又是采取怎样的方式进行扩容呢?

我们来看 ensureCapacityInternal(int minCapacity),

1 private void ensureCapacityInternal(int minCapacity) {
2         if (elementData == EMPTY_ELEMENTDATA) {
3             minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
4         }
5
6         ensureExplicitCapacity(minCapacity);
7 }

DEFAULT_CAPACITY = 10 是默认的数组容量,

下面来看ensureExplicitCapacity(int minCapacity),

1 private void ensureExplicitCapacity(int minCapacity) {
2         modCount++;
3
4         // overflow-conscious code
5         if (minCapacity - elementData.length > 0)
6             grow(minCapacity);
7 }

如果需要的长度大于数组当前的长度,则调用grow(int minCapacity),

 1 private void grow(int minCapacity) {2         // overflow-conscious code3         int oldCapacity = elementData.length;4         int newCapacity = oldCapacity + (oldCapacity >> 1);5         if (newCapacity - minCapacity < 0)6             newCapacity = minCapacity;7         if (newCapacity - MAX_ARRAY_SIZE > 0)8             newCapacity = hugeCapacity(minCapacity);9         // minCapacity is usually close to size, so this is a win:
10         elementData = Arrays.copyOf(elementData, newCapacity);
11 }

扩容的规则是,当前数组的长度乘以1.5,结果带小数则取整数,

最后调用Arrays.copyOf(T[] original, int newLength),直接看到最内层的实现,

1 public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
2         T[] copy = ((Object)newType == (Object)Object[].class)
3             ? (T[]) new Object[newLength]
4             : (T[]) Array.newInstance(newType.getComponentType(), newLength);
5         System.arraycopy(original, 0, copy, 0,
6                          Math.min(original.length, newLength));
7         return copy;
8 }

会创建一个新数组,同时将原数组的内容复制到新数组中,

扩容是的操作是int newCapacity = oldCapacity + (oldCapacity >> 1),我们可以这么认为

1.扩容一次性太多,势必会造成对内存空间的过多占用,

2.扩容太少,会造成次数太多,下次的扩容很快到来,同时,将原数组的元素复制到新的数组中,频繁的数组拷贝需要消耗一定的性能,

因此也许这是一种比较折中的处理方式,

删除元素 

如下一段代码

1 public static void main(String[] args) {
2         List<Integer> list = new ArrayList<Integer>();
3         list.add(111);
4         list.add(222);
5         list.remove(1);
6         list.remove(222);
7 }

元素的删除分为两种,一种是按照下标来删除,一种是按照元素来删除

按照下标来删除元素,先看一下代码,

 1 public E remove(int index) {2         rangeCheck(index);3 4         modCount++;5         E oldValue = elementData(index);6 7         int numMoved = size - index - 1;8         if (numMoved > 0)9             System.arraycopy(elementData, index+1, elementData, index,
10                              numMoved);
11         elementData[--size] = null; // clear to let GC do its work
12
13         return oldValue;
14 }

第二行,校验是否越界,第九行将删除的数据之后的元素往前移动一位,第十一行将size自减,同时将最后一位引用指向null

clear to let GC do its work!

按照元素来删除元素,先看一下代码,

public boolean remove(Object o) {if (o == null) {for (int index = 0; index < size; index++)if (elementData[index] == null) {fastRemove(index);return true;}} else {for (int index = 0; index < size; index++)if (o.equals(elementData[index])) {fastRemove(index);return true;}}return false;
}

遍历找出下标之后调用 fastRemove(int index),代码如下,

private void fastRemove(int index) {modCount++;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
}

两种实现方式差不多,最后都是调用上面的数组部分元素的移动,以及最后一位引用让虚拟机自己释放元素的引用

插入元素

如下一段代码,

1 public static void main(String[] args) {
2         List<Integer> list = new ArrayList<Integer>();
3         list.add(111);
4         list.add(222);
5         list.add(1,22222);
6 }

第5行表示,在第一个元素之后插入22222,

我们进到add(int index, E element),

1 public void add(int index, E element) {
2         rangeCheckForAdd(index);
3
4         ensureCapacityInternal(size + 1);  // Increments modCount!!
5         System.arraycopy(elementData, index, elementData, index + 1,
6                          size - index);
7         elementData[index] = element;
8         size++;
9 }

第2行,校验插入的位置是否在数组的大小范围内,否则跑出异常,

第4行,判断是否有必要扩容,和新增元素的扩容是一样方法,

第5行,插入的位置到最后的所有元素向后移动一位,

第6行,插入的位置的引用指向新元素,

下面我们ArrayList实现了Random接口,然鹅RandomAccess是一个空接口,javadoc中是这么描述的,

Marker interface used by <tt>List</tt> implementations to indicate that
they support fast (generally constant time) random access.

翻译过来的意思是 这是一个标记接口,仅仅是一个标记,arrayList实现标记,表明它能快速的查询数据,

从上面几个方面,我们总结一下ArrayList的优缺点,

优点

1.新增一个元素,顺序新增,增加数组元素的一个引用而已,

2.数组查询非常快捷,

缺点

1.删除元素,会造成部分元素的移动,势必会造成性能的一定影响,

2.插入元素,会造成部分元素的移动,势必会造成性能的一定影响,

所以,在业务开发中,涉及到查询较多的,考虑ArrayList。

同样,想借鉴大神对于集合的四个关注点

1.是否允许为空,

2.元素是否允许重复,

3.元素的存储顺序与查找顺序是否一致,

4.是否线程安全,

ArrayList允许元素为空,允许重复,有序,非线程安全,

我们再回头看数组的定义

private transient Object[] elementData;

被transient关键字修饰,表示该数组不会被序列化,而是提供了writeObject(java.io.ObjectOutputStream s),javadoc有这么一句话,

Save the state of the <tt>ArrayList</tt> instance to a stream (that
is, serialize it)

我们来看一下writeObject(java.io.ObjectOutputStream s),

 1 private void writeObject(java.io.ObjectOutputStream s)2         throws java.io.IOException{3         // Write out element count, and any hidden stuff4         int expectedModCount = modCount;5         s.defaultWriteObject();6 7         // Write out size as capacity for behavioural compatibility with clone()8         s.writeInt(size);9
10         // Write out all elements in the proper order.
11         for (int i=0; i<size; i++) {
12             s.writeObject(elementData[i]);
13         }
14
15         if (modCount != expectedModCount) {
16             throw new ConcurrentModificationException();
17         }
18 }

第5行,对非transient的成员属性序列化,

第11~13行,对有数组中有值得元素逐个序列化。

这么做的好处,

1.大大缩短序列化的时间,

2.减少序列化后文件的大小。

转载于:https://www.cnblogs.com/sunshine798798/p/9053733.html

Java集合之ArrayList源码解析相关推荐

  1. Java集合之TreeMap源码解析上篇

    上期回顾 上期我从树型结构谈到了红黑树的概念以及自平衡的各种变化(指路上期←戳),本期我将会对TreeMap结合红黑树理论进行解读. 首先,我们先来回忆一下红黑树的5条基本规则. 1.结点是红色或者黑 ...

  2. java arraylist_死磕 java集合之ArrayList源码分析

    简介 ArrayList是一种以数组实现的List,与数组相比,它具有动态扩展的能力,因此也可称之为动态数组. 继承体系 ArrayList实现了List, RandomAccess, Cloneab ...

  3. 【源码阅读】Java集合之一 - ArrayList源码深度解读

    Java 源码阅读的第一步是Collection框架源码,这也是面试基础中的基础: 针对Collection的源码阅读写一个系列的文章,从ArrayList开始第一篇. ---@pdai JDK版本 ...

  4. Java集合系列---List源码解析(ArrayList和LinkedList的区别)

    List源码主要讲ArrayList,LinkedList,Vector三个类 1 ArrayList ArrayList是一个底层基于数组的集合, 首先来看一下它的继承关系, public clas ...

  5. Java集合干货——ArrayList源码分析

    前言 在之前的文章中我们提到过ArrayList,ArrayList可以说是每一个学java的人使用最多最熟练的集合了,但是知其然不知其所以然.关于ArrayList的具体实现,一些基本的都也知道,譬 ...

  6. Java集合部分学习+源码解析

    Java集合 对象的容器,实现了对对象常用的操作,类似数组功能. 集合中的数据都是在内存中,当程序关闭或者重启后集合中的数据就会丢失,所以说是临时存储数据的容器 集合整体框架 Collection:单 ...

  7. Java集合---Arrays类源码解析

    一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Primitive(8种基本类型)和Object两大类. 基本类型:采用调优的快速排序: 对象类型: ...

  8. Java集合系列---Collection源码解析及整体框架结构

    集合的整体框架结构及依赖关系 1.Collection public interface Collection<E> extends Iterable<E> {} Collec ...

  9. Java集合系列---ConcurrentHashMap源码解析

    ConcurrentHashMap是Java并发容器的一员,jdk1.8以后的基本的数据结构和HashMap相似,也是选用了数组+链表/红黑树的结构,在jdk1,.7以前则是采用了分段锁的技术.Con ...

最新文章

  1. 25行代码实现Promise函数
  2. Linux登陆Mariadb数据库,Mariadb数据库的远程连接(centos 7+ Navicat)
  3. 【C++深度剖析教程25】继承中的构造与析构
  4. mongodb最大连接数、最大连接数修改
  5. angelajs中ajax,Fabric.js Triangle angle属性用法及代码示例
  6. python自动生成报告_python实现自动生成oracle awr报告
  7. HDU 1210 Eddy's 洗牌问题(foj1062) || FOJ1050 Number lengths水
  8. 如何将现有Apple ID 更改为美区
  9. 利用IPv6看清晰流畅的网络电视
  10. CentOS7安装Pure-ftpd
  11. java8的stream写法实现去重
  12. 类型多样的电子数码免抠元素素材,速来收藏
  13. C#_WPF中创建二维码、识别二维码
  14. linux docker容器MySQL自动备份发送到邮箱
  15. 用友U8其中一个账套提示演示期已到期-修复方法
  16. JD6621快速充电协议芯片,带有PPS 控制器的USB-PD3.0
  17. vim visual block模式
  18. 计算机图形学常见算法原理,计算机图形学常用算法及代码大全
  19. 【计算机毕业设计】6.超市仓库管理系统+vue
  20. Win7 查看端口占用的进程,并根据进程id杀死进程。

热门文章

  1. python交作业的格式_python作业4
  2. html取 输入框中的值,jquery获取input输入框中的值
  3. 网站关键词如何布局更有利于关键词排名提升?
  4. 网站推广——网站制作二维码只是为了更好的实现网站推广
  5. 企业网络推广——企业网络推广公司解读企业新站沙盒期如何优化?
  6. 浅析关键词与搜索引擎之间不得不说的关系
  7. php7和7.1,PHP7.0和7.1 部分新增特性备忘代码分享
  8. python构造一个二叉树_如何用python构造一个n层的完全二叉树
  9. yandex注册验证码怎么填_注册资本到底怎么填?
  10. jmeter json断言_Jmeter接口测试+压力测试