Java中Arraylist源码分析

 前言:ArrayList作为我们常用的一个集合数据类型,我们在代码中经常用它来装载数据,可谓是和HashMap一样常用的集合类型了。我们经常用它,那么就有必须知道它的内部工作原理,比如它是如何添加进去数据的,它内部的数据结构是怎样的,当我们做一个remove操作,它又做了哪些工作。了解这些内部工作的原理能够帮助我们更好的理解Arraylist,什么时候使用它和不使用它,如何提升它的效率,等等。那么本篇博文就来聚焦Arraylist,走进它的内部源码,来一探究竟吧。

本篇博客的目录

一:Arraylist简介

二:Arrsylist的构造方法

三:Arraylist的add和remove方法分析

四:Arraylist的常用方法源码分析

五:Arrraylist与linkedlist的区别

六:总结

一:Arraylist简介:

     首先是Arraylist一种java的数据容器,作为数据容器,我们在程序中经常使用它,比如Map、Collection、HashMap、TreeSet等等。如它的名字一样,它的内部结构是数组,而这个数组是可以动态扩容的,就犹如hashMap一样。它的所有操作都是基于其内部的动态数组来进行操作,list接口的大小可变数组的实现、如果1-1所示(其中index表示的是数组的元素的位置,但注意其数组下标实际是从0开始的,而不是1),源码中多次涉及到index这个变量,在这里可以先理解一下。其次它本质上一个类,实现了list接口,继承了AbstractList,和Cloneable接口、Serializable表明其继承于抽象的list,并且可以进行浅复制和序列化写入到数据文件中。同时它在构建的时候就说明了其类型泛型E,所以Arraylist是一种在新建时就声明其包含类型的集合,并且可支持复制、序列化、克隆等特性。

public class ArrayList<E> extends AbstractList<E>   implements List<E>, RandomAccess, Cloneable, java.io.Serializable

 

 二:Arrsylist的成员变量和构造方法

我们先来看一看Arraylist的成员变量,从中可以看到Arraylist维护着的数组和一些基本的数值表示出它的大小和数据类型,之所以声明为Object类型的,是因为要定义一个适用于所有java数据类型的,这样它就可以向下转型为所有基本类型了。至于元素容量为什么是10,而不是11或者12,这个可能是jdk的作者经过计算、根据使用习惯计算出来的,有一定的科学依据。这些都是全局变量,也就是说下面的方法都可以访问这些变量,并修改它的值。

    private static final long serialVersionUID = 8683452581122892189L; //序列版本号private static final int DEFAULT_CAPACITY = 10;//默认数组元素的初始容量为10private static final Object[] EMPTY_ELEMENTDATA = {};//空元素对象数组private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//默认容量的空数据数组transient Object[] elementData; // 数组元素对象,list中的元素就存在这里private int size;//添加的元素的数量

看完了成员变量,我们来看一下Arraylist的3个构造函数,其中我们用到的最多的也就是第二个无参的构造函数,平时代码里我们经常这样写:List<String>   list= new Arraylist()<String>;这就是调用了其第二个构造函数,在构造函数里面,我们可以看到它对空数组进行了赋值,将维护着的数组的大小设为10个容量,然后我们就可以使用这个list了。接下来我们就讲一讲如何往这个list里面增添元素和移除元素,这是我们使用list最常用的两个方法。

public ArrayList(int initialCapacity) {   //初始化容量 if (initialCapacity > 0) { //判断传入的参数值this.elementData = new Object[initialCapacity];//把传入的容量作为数组的初始容量} else if (initialCapacity == 0) {//如果传入的是0this.elementData = EMPTY_ELEMENTDATA;//初始化为空数组} else {throw new IllegalArgumentException("Illegal Capacity: "+ //传入的如果小于0,就抛出参数违规异常
                                               initialCapacity);}}public ArrayList() {  //空构造方法this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//空对象数组初始化,其容量为10
    }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 {// replace with empty array.this.elementData = EMPTY_ELEMENTDATA;}}

 三:Arraylist的add和remove方法分析

 1.add方法的解析:

我们先来看一下add方法,这其中又涉及到了rangeCheckForAdd方法和ensureCapacityInternal、System.arraycopy方法,我们来依次分析一下其中的源码,看看究竟在我们add一个元素的时候,Arraylist做了什么:

    public void add(int index, E element) {//根据指定元素添加指定元素rangeCheckForAdd(index);//通过位置进行范围检查
ensureCapacityInternal(size + 1);  //System.arraycopy(elementData, index, elementData, index + 1,size - index);elementData[index] = element;//元素赋值给指定的位置size++;//数组大小增加}
 private void rangeCheckForAdd(int index) {  //专为add方法增加的返回检查if (index < 0 || index > this.size)//如果传入的参数小于0或者大于数组已添加元素的个数throw new IndexOutOfBoundsException(outOfBoundsMsg(index));//抛出异常}
 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//定义数组最大的值为Integer最大值-8
private void ensureCapacityInternal(int minCapacity) {//确保容量if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //如果数组元素等于默认的空数组容量minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//取默认的容量和指定的容量的较大值 }ensureExplicitCapacity(minCapacity);//传入 ensureExplicitCapacity方法确保扩展的容量}
    private void ensureExplicitCapacity(int minCapacity) {/确保扩展容量modCount++;//修改次数+1// overflow-conscious codeif (minCapacity - elementData.length > 0)//如果指定的容量大于数组容量grow(minCapacity);//调用gorw方法}
 private void grow(int minCapacity) { //传入指定的容量// overflow-conscious codeint oldCapacity = elementData.length;//取旧的数组元素的长度int newCapacity = oldCapacity + (oldCapacity >> 1);//旧长度除以2+长度赋予新长度(相当于扩容1.5倍)if (newCapacity - minCapacity < 0)//如果新长度小于指定的容量newCapacity = minCapacity;//把新计算的长度赋予指定的容量if (newCapacity - MAX_ARRAY_SIZE > 0)//如果新容量>最大数组大小newCapacity = hugeCapacity(minCapacity);//调用bugeCapcaity方法elementData = Arrays.copyOf(elementData, newCapacity);//复制数组}
  private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // 如果指定容量小于0throw new OutOfMemoryError();//抛出异常内存错误return (minCapacity > MAX_ARRAY_SIZE) ?//如果指定容量大于最大数组大小返回int的最大值
            Integer.MAX_VALUE :MAX_ARRAY_SIZE;//否则返回最小数组大小}

    通过源码我们可以看出:Arraylist在添加元素的时候,首先进行的是范围检查,防止其传入的参数小于0或者超过数组的size大小,再是和默认分配的大小值进行比较,如果大于默认大小就要进行扩容。扩容的时候首先把旧数组的大小提升1.5倍,成为新数组的大小值。同时也可以看到Arrylist的大小边界是Interger的最大值,这个数字是很大的:2147483647,也就是它的最大值是这么多,这个值足以我们程序员平常进行使用!在检查完毕和扩容完毕之后,就是要进行数组的拷贝了,我们开看一下System.arrayCopy()方法的源码:

  public static native void arraycopy(Object src,  int  srcPos,Object dest, int destPos,int length);

其中可以看出,它是native修饰的,也就是说它是一个引用本地其他语言的代码库(windows就是.dll库)的方法,具体的源码我们不深究了,这里解释一下它的参数,src:你要从哪个数组复制,srcpos:要复制的位置起点 dest:要复制要哪个数组,destPos:要复制到的数组起始位置 lengh:复制的长度

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

代入add方法里,也就是说把elementData的从index开始的元素复制size-index长度到elementData中,从index+1开始。这是一个自我复制的过程,为了保证数据的完整。然后把指定的元素值放入到指定的位置中。我们来用图解释一下这个复制的过程:首先是图1-1表示的是一共有5个元素,那么这个数组的size就是5.我们现在要在index=2的位置上插入元素六。我们来看看通过arraycopy方法之后的数组是什么样的:如图1-2。然后调用lementData[index] = element方法,之后数组就会变成图1-3:可见我们要插入的元素六就这样插入到指定位置上了,从中也可以看出自我复制也就是数组的元素从指定的位置向后移动,这也间接说明了Arraylist插入数据的效率比较低,因为它要移动数据。

         

 

 

             

                                               图 1-3

2:remove方法

我们看romove方法首先还是进行范围检查,然后用elementData()方法去找到指定数组元素中的值,再用size-index-1转为numMoved,接着进行再次自我复制。然后把size-1甚至设置为null。这样这个数组中元素就是null了。

    public E remove(int index) {//根据指定位置移除元素rangeCheck(index);//范围检查
modCount++;//修改次数+1E oldValue = elementData(index);//根据数组元素的位置找到旧值int numMoved = size - index - 1;if (numMoved > 0)//判断是否大于0System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // return oldValue;//返回旧值}

    为了更好的理解remove方法,我们来用画图的方式理解一下:其中一二三到十使我们存放的值,如图2-1.那么这个数组的size=10,假如我们移除remove(3),则index=3,我们可以看到index=2对应的值是四,然后numMoved的值是7,大于0。然后进行自我复制。从elementData的index=3开始,复制之后的结果如图2-2,可以看出从index开始的位置所有的元素往前移动了一位,这就是arraycopy方法的根本目的,然后再把  elementData[--size] = null; 也就是最后一个元素十置为null,这样数组的大小就自然减-1,并且顺利的移出了指定位置的元素。这就是remove方法原理

 

 

                                                                图 2-1

 

                                                                      图 2-2

 

 四:Arraylist的常用方法源码分析

我们再来看一下Arraylist中一些常见方法的源码,其中包括返回其大小的方法,判断是否为空的方法。是否包含某个元素的方法等等,这些方法比较简单,我只列出了基本的注释。

 public int size() {//取其大小return size;//返回size的数组
    }public boolean isEmpty() {//判断list是否为空return size == 0;//用其size和0去比较
    }public boolean contains(Object o) {//判断指定元素是否包含return indexOf(o) >= 0;//返回indexof方法的返回结果是否大于0。大于0表示含有,如果小于0表示没有
    }public int indexOf(Object o) {//检索指定元素是否存在if (o == null) {//如果元素为nullfor (int i = 0; i < size; i++)//遍历循环整个数组if (elementData[i]==null)//如果找到元素为nullreturn i;//返回元素中的位置} else {//如果不为nullfor (int i = 0; i < size; i++)//遍历循环数组if (o.equals(elementData[i]))//如果指定元素和数组中的元素相同return i;//返回数组中的位置
        }return -1;//否则返回-1
    }public int lastIndexOf(Object o) {//从末尾检索指定元素if (o == null) {//如果元素为nullfor (int i = size-1; i >= 0; i--)//按照元素从大到小if (elementData[i]==null)//如果元素为nullreturn i;//返回数组中的位置} else {//如果元素不为nullfor (int i = size-1; i >= 0; i--)//按照元素从大到小if (o.equals(elementData[i]))//如果元素等于数组中的元素return i;//返回数组中的位置
        }return -1;//否则返回-1}
  public boolean remove(Object o) {//判断元素是否能够移除if (o == null) {//如果元素为nullfor (int index = 0; index < size; index++)//
                if (elementData[index] == null) {//如果元素值为nullfastRemove(index);//快速移除return true;}} else {for (int index = 0; index < size; index++)if (o.equals(elementData[index])) {fastRemove(index);return true;}}return false;}private void fastRemove(int index) {//根据指定的位置移除元素modCount++;//修改次数+1int numMoved = size - index - 1;//从指定元素的下一个开始if (numMoved > 0)//如果元素的下一个大于0System.arraycopy(elementData, index+1, elementData, index,numMoved);//拷贝数组elementData[--size] = null; //数组对象的最后一个值赋值为null 
    }public void clear() { //清除元素modCount++;//修改次数+1for (int i = 0; i < size; i++)//遍历循环整个数组elementData[i] = null;//将其值设为null,便于GC回收
size = 0;//size等于0}

 

五:Arraylist与linkedlist的区别

     1.从本篇博文中可以看出ArrayList基于动态数组的数据结构,但是LinkedList基于链表的数据结构。
     2:Arraylist是有序的,元素它是按照固定的顺序排列,每次都往后移动一位,并且很容易可以看出它是允许值为null。而linkedlist是无序的。

     3.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 我们在这里看一下get方法的源码,其中可以看出其只需要做个范围检查,然后就可以通过数组的位置计算         处其位置上的值,这也是数组的特点,获取数组很快。但是linkedlist是链表结构的,所谓结构决定功能,链表的获取元素时候需要一层层去节点里面遍历,这无疑增加了工作量,所以linked的随机     访 问要落后与Arraylist
    

  public E get(int index) {//根据指定位置返回对应的值rangeCheck(index);//调用范围检查的方法,防止其越界return elementData(index);//把参数传入,调用elementData()方法}

 

    4.对于新增和删除操作add和remove,LinkedList有很大的优势,因为ArrayList要移动数据

       我们通过比对发现add和remove方法,它们都要经过System.arrayCopy()方法,进行数据的移动,比较麻烦,而基于链表的结构的LinkedList则只需要进行值和位置的封装,然后放入链表即           可。

    5:Arraylist是非线程安全的,其中我们可以看出它并没有同步机制的实现,也没有synchronize等关键字的修饰。所以在多线程的环境下慎用Arraylist。同时linkedList也并非线程安全的!

六:总结

   本篇博文介绍了Arraylist的源码,从其中可以看出jdk设计者的巧妙,包括其add方法的校验,防止校验,还有动态扩容的特性,它作为一个我们在实际开发中常用的数据容器,按照从全局变量到构造方法再到其add、remove方法的学习,我们对arraylist也有了进一步的认知,同时与linkedlist进行了对比,以便于我们更好的掌握Arraylist!好了,本次博客就到这里结束了。

 

转载于:https://www.cnblogs.com/wyq178/p/6986363.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/429205.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

联想服务器系统安装bios设置,Windows 8操作系统如何通过Legacy BIOS与UEFI两种模式安装...

Win8系统相对于Win7系统在开机速度上有相当大的提升&#xff0c;这是因为Win8系统为了提升系统性能和对硬件的优化&#xff0c;加入了诸如开机引导及应用预缓存等技术。而其中的UEFI BIOS引导&#xff0c;则能使平台开机更智能&#xff0c;开机速度更快。对比采用传统BIOS引导启…

系统云服务器,系统云服务器

系统云服务器 内容精选换一换当云服务器密码即将过期、密码泄露或首次登录时(首次登录云服务器建议您修改初始密码)&#xff0c;您可以参考本节操作在操作系统内部修改云服务器密码。优先推荐您参考在控制台重置云服务器密码&#xff0c;在控制台重置实例的登录密码。可以登录云…

如何在Global.asax中判断是否是ajax请求

今天在一个应用场景中需要在Global.asax中判断一个请求是否是ajax请求&#xff0c;而在ASP.NET MVC中已经提供了一个现成的扩展方法IsAjaxRequest&#xff1a; namespace System.Web.Mvc {public static class AjaxRequestExtensions{public static bool IsAjaxRequest(this Ht…

csp真题 202109-2非零段划分C++代码(100分)

试题编号&#xff1a; 202109-2 试题名称&#xff1a; 非零段划分 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 512.0MB 样例1输入 11 3 1 2 0 0 2 0 4 5 0 2样例1输出 5样例1解释 p2时&#xff0c;A[3,0,2,0,0,2,0,4,5,0,2]&#xff0c;5个非零段依次为[3]、[2]、[2…

c++中四舍五入取整、向上取整和向下取整问题

方法一&#xff1a;利用c函数 四舍五入&#xff1a;round()函数 向上取整&#xff1a;ceil()函数 向下取整&#xff1a;floor()函数 #include <iostream> #include <cmath>//函数头文件 using namespace std; int main() {double a1.2;double b3.7;cout<<&q…

SQL PROMPT5.3.4.1的一些设置选项

摘自&#xff1a;http://www.cnblogs.com/furenjian/p/4519426.html sql prompt这个工具安装好之后就可以在SSMS里使用代码提示功能 园子里非常多的文章&#xff1a;例如SQLSERVER开发利器XXXXXX http://www.cnblogs.com/VAllen/archive/2012/09/08/SQLPrompt.html http://www.…

csp真题202112-1 序列查询 (100分)

#include <iostream> using namespace std; int A[1000000]{0};//c中一定一定要要记得给变量赋初始值&#xff0c;会减少很多不必要的麻烦 //尽可能将数组量大的数组定义在函数外面&#xff0c;不要在函数内部定义大数组 int main() {int n0,N0,sum0,i0;cin>>n&g…

云麦小米华为体脂秤怎么样_测评华为智能体脂秤,比小米智能体重秤贵30元到底差别在哪里?...

“流浪的好奇”每天更新最新科技资讯和产品测评内容&#xff0c;欢迎您关注订阅&#xff01;我们之前说过华为可不仅仅只有5G通讯技术和智能手机&#xff0c;华为杂货铺里的产品虽然不如小米杂货铺那么的丰富&#xff0c;但是也不是空铺一个&#xff0c;还是有一些华为主业之外…

c++中引用和指针的区别

指针是指向一块内存&#xff0c;它的内容是所指内存的地址&#xff1b; 而引用则是指向某块内存变量的一个别名&#xff0c;相当于变量的另一个名字。 例如&#xff1a; #include <stdio.h> int main() {int a2;int &ba;printf("a:%d\n",a);printf("…

什么是大数据,怎么理解和应对大数据时代

什么是大数据&#xff0c;怎么理解和应对大数据时代 在大数据与深度学习中蝶化的人工智能。当代人工智能离不开大数据和深度学习算法。我们先来了解什么是大数据&#xff0c;大数据的本质是什么&#xff0c;在大数据时代我们应该如何应对&#xff1f; 当我们谈论数据的时候我们…

android 手机获取公网ip_KSWEB 旧手机搭建网站服务

? 感谢你阅读「 Android 应用 」的第 113篇分享文章KSWEB 是一款由俄罗斯开发者开发的一款可以在 Android 手机上安装建站相关服务的应用&#xff0c;支持 Lighttpd、Nginx、Apache、MySQL、FTP、PHP 等。打开应用后点击不再提醒忽略弹窗&#xff0c;给予权限后等待片刻按照提…

phpStydy配置memcache扩展

phpStydy配置memcache扩展 先提示一点&#xff1a;我电脑是win7 64 位的&#xff0c;但我用的环境是phpstudy (php5.6nginx); 下面的下载里我下载64位软件安装后没反映&#xff0c;因为phpstudy环境是32位的,下载32位安装后memcache就成功了; 一、下载并安装memcached服务器端软…

外部仓库_仓库主要作业流程和WMS作业优化方案

仓库在人们眼中一直是低效、高成本的地方&#xff0c;即便是增加人手也很难做好仓库管理。那么我们应该采取哪些方式来管理&#xff0c;才能做到降本增效呢&#xff1f;首先我们需要先了解仓库主要作业流程及在作业中存在的痛点。1入库流程a.商品SKU太多&#xff0c;部分形状相…

java IO流(上)

1.字节流 1.1 IO流概述和分类【理解】 1.IO流介绍 IO&#xff1a;输入/输出(Input/Output)流&#xff1a;是一种抽象概念,是对数据传输的总称.也就是说数据在设备间的传输称为流,流的本质是数据传输IO流就是用来处理设备间数据传输问题的.常见的应用: 文件复制; 文件上传; 文…

《科技之巅2》序——机器智能数据智能:工具之王

原文链接 “凡是过去&#xff0c;皆为序曲。”——莎士比亚 通过不断发明工具&#xff0c;人类科技指数级进化。语言汇聚原始部落&#xff0c;马镫开启了中世纪的欧洲文明&#xff0c;“高产种子”引发人口爆炸&#xff0c;蒸汽机创造工业文明&#xff0c;大规模集成电路激活数…

一、java面向对象基础

1. 类和对象 面向对象和面向过程的思想对比 : ​ 面向过程 &#xff1a; 是一种以过程为中心的编程思想&#xff0c;实现功能的每一步&#xff0c;都是自己实现的 ​ 面向对象 &#xff1a; 是一种以对象为中心的编程思想&#xff0c;通过指挥对象实现具体的功能 1.1 类和对…

pythom打包文件太大_从SQL注入到整站打包与本地搭建

一、前言如题&#xff0c;由于是在已知有一处sql注入的情况下才接手进行的后续操作&#xff0c;因此前面信息搜集则一笔带过。二、信息搜集目标是个本地的传销站点其大致信息为IISASP.NET安全狗、腾讯云。三、Bypass Sql3.1 Fuzz空格 error 加号 未拦截 等号 er…

二、常用API——String类和StringBuilder类

1.API 1.1 API概述-帮助文档的使用 什么是API ​ API (Application Programming Interface) &#xff1a;应用程序编程接口 java中的API ​ 指的就是 JDK 中提供的各种功能的 Java类&#xff0c;这些类将底层的实现封装了起来&#xff0c;我们不需要关心这些类是如何实现的&a…

iphone复制不能全选_忘记Apple ID密码,如何直接在 iPhone 上更改?

最近有不少同学问我&#xff0c;如果忘记了 Apple ID 的密码&#xff0c;怎么能直接在 iPhone 上修改&#xff1f;别急&#xff0c;我现在就教你如果之前已经在 iPhone 上登录过你的账户&#xff0c;并且已经将 iPhone 设置为「受信任设备」&#xff0c;那么就可以直接在 iPhon…

nginx 上传 文件超时设置_Nginx在高并发下的性能优化点!有这篇就够了!

点击上方“java进阶架构师”&#xff0c;选择右上角“置顶公众号”20大进阶架构专题每日送达前面几周&#xff0c;讲过Nginx的日志配置&#xff1a;Nginx | 超详细&#xff01;Nginx 日志配置实践&#xff0c;然后也讲了Nginx的进程模型&#xff0c;底层原理等&#xff1a;Ngin…