ArrayList概述

类概述

ArrayList是List 接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。

每个 ArrayList 实例都有一个容量(capacity)。该容量是指用来存储列表元素的数组的大小。它大于或等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。在添加大量元素前,应用程序可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。注意,此实现不是同步的。

类结构

ArrayList实现

          通过查看ArrayList的源码我们可以得知,ArrayList底层是通过操作数组来实现,其实就是用java实现了数据结构中的顺序表。

底层采用数组实现

  1. private transient Object[] elementData;

看到这行代码,或许有很多同学和我刚开始一样。为什么ArrayList源代码中要将elementData定义为transient变量呢?这个问题的研究大家可以看这篇文章【http://kakaluyi.iteye.com/blog/870137】.如果对java中的序列化知识不懂的可以看本博客中的关于序列化的文章。【理解Java对象序列化】     【java中可定制的序列化过程 writeObject与readObject】      【Java中的序列化Serialable高级详解【转载】】          

可变数组

            在java中我们知道,当一个数组的长度被定义好之后,数组的长度是不可变的。那么可变数组时怎么一回事呢?其实可变数组只是表现形式上的可变
例如

  1. int array1[]={1,2,3};//定义一个数组长度为3的整形数组,此时数组的三个元素分别为1,2,3

此时如果想添加一个元素"4"到数组中去,因为数组长度是不可变的,无法直接array[3]=4;那么此时只能借助另外一个长度为4的数组array2,将数组array1的元素拷贝到新数组array2中,再赋值array[3]=4;然后将array1的引用指向array2。此时array1的表现形式就像是可变数组了.

  1. int array1[]={1,2,3};
  2. int array2[]=new int[4];
  3. for(int i=0;i<array1.length;i++){
  4. array2[i]=array1[i];
  5. }
  6. array2[3]=4;
  7. array1=array2;

此时我们就可以想到,要想动态的实现ArrayList的可变数组,那么在ArrayList初始化的时候我们要初始化一个容量,往ArrayList中添加元素时我们要动态的改变底层数组的长度。那我们来看看jdk源码是怎么初始化和动态改变数组容量的

初始化

  1. public ArrayList(int initialCapacity) {
  2. super();
  3. if (initialCapacity < 0)
  4. throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
  5. this.elementData = new Object[initialCapacity];
  6. }
  7.  
  8. public ArrayList() {
  9. this(10);
  10. }
  11.  
  12. public ArrayList(Collection<? extends E> c) {
  13. elementData = c.toArray();
  14. size = elementData.length;
  15. if (elementData.getClass() != Object[].class)
  16. elementData = Arrays.copyOf(elementData, size, Object[].class);
  17. }

从源码中我们可以得知ArrayList提供了三种方式的构造器

           1 构造一个默认初始容量为10的空列表
           2 构造一个指定初始容量的空列表
           3 构造一个包含指定collection中元素的列表

动态调整底层数组的容量

  1. public void ensureCapacity(int minCapacity) {
  2. modCount++;//Fast-Fail机制(具体机制本文章中专门有说的)
  3. int oldCapacity = elementData.length;
  4. if (minCapacity > oldCapacity) {
  5. Object oldData[] = elementData;
  6. int newCapacity = (oldCapacity * 3) / 2 + 1;
  7. if (newCapacity < minCapacity)
  8. newCapacity = minCapacity;
  9. elementData = Arrays.copyOf(elementData, newCapacity);
  10. }
  11. }

从源码中我们可知动态调整容量的过程是

          1 获取当前数组的大小(旧容量)
          2 如果当前要保证的容量小于旧容量,那么容量不进行扩充,反之就扩充。
          3 如果要扩充容量,那么新容量为 旧容量的1.5倍+1
             3.1  取计算出来的新容量和需要的容量中的最大值,最为最后底层数组的真实容量
             3.2  拷贝旧数组的数据到新数组中,让底层数组的引用指向新容量的数组
从上述分析可知如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。这样可以减少内存的占用和提高存储效率。

集合操作

ArrayList提供了add,set,get,delete等一系列操作。下面对ArrayList中实现这些操作一一讲解。

add添加元素

元素添加到列表的尾部

  1. public boolean add(E e) {
  2. ensureCapacity(size + 1); //动态调整底层数组的容量
  3. elementData[size++] = e;//将元素添加到列表尾部,列表大小加1
  4. return true;//返回true,添加成功
  5. }

元素添加到指定位置

  1. public void add(int index, E element) {
  2. if (index > size || index < 0)// 首先检查index是否合法,要保证不能大于列表的实际长度,且index不能为负数,如果不满足就抛出异常
  3. throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
  4. ensureCapacity(size + 1);// 添加元素的操作都要动态的调整底层数组的容量
  5. System.arraycopy(elementData, index, elementData, index + 1, size - index);//调用System的数组拷贝方法
  6. elementData[index] = element;// 设置新数组处index处的值为指定的元素
  7. size++;// 列表长度加1
  8. }

集合中的元素添加到列表尾部

  1. public boolean addAll(Collection<? extends E> c) {
  2. Object[] a = c.toArray();
  3. int numNew = a.length;
  4. ensureCapacity(size + numNew);
  5. System.arraycopy(a, 0, elementData, size, numNew);
  6. size += numNew;
  7. return numNew != 0;
  8. }

从指定位置开始添加集合中元素

  1. public boolean addAll(int index, Collection<? extends E> c) {
  2. if (index > size || index < 0)
  3. throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
  4.  
  5. Object[] a = c.toArray();
  6. int numNew = a.length;
  7. ensureCapacity(size + numNew); // Increments modCount
  8.  
  9. int numMoved = size - index;
  10. if (numMoved > 0) {
  11. System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
  12. }
  13. System.arraycopy(a, 0, elementData, index, numNew);
  14. size += numNew;
  15. return numNew != 0;
  16. }

get获取元素

我们知道ArrayList底层是通过数组实现的,那么我们就可以通过数组的索引获取元素。代码如下
  1. public E get(int index) {
  2. RangeCheck(index);// 检查index是否合法
  3.  
  4. return (E) elementData[index];// 返回指定index处的元素
  5. }

ArrayList中有一私有方法,来检测index是否合法。

  1. private void RangeCheck(int index) {
  2. //如果index不小于列表的元素个数,就抛出异常
  3. if (index >= size)
  4. throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
  5. }

remove删除元素

删除指定位置处的元素

  1. public E remove(int index) {
  2. RangeCheck(index);// 检查index是否合法
  3.  
  4. modCount++;// Fail-Fast机制
  5. E oldValue = (E) elementData[index];// 获取待删除的元素
  6.  
  7. int numMoved = size - index - 1;//移动元素个数
  8. if (numMoved > 0) {
  9. System.arraycopy(elementData, index + 1, elementData, index, numMoved);//向前移动元素
  10. }
  11. elementData[--size] = null;//将原来size-1处的元素设置为空,列表大小-1
  12. return oldValue;//返回删除的元素
  13. }

删除首次出现的指定元素

ArrayList允许重复元素的存在,此方法删除的是首次出现的指定元素,要想删除指定元素内部实现过程是:(1)循环遍历列表中找到指定元素首次出现的位置 (2)删除此位置的元素,那么这样就类似remove(int index)方法。jdk内部实现代码如下:

  1. // 删除首次出现的指定元素,元素存在返回 true,元素不存在返回false
  2. public boolean remove(Object o) {
  3. if (o == null) {
  4. for (int index = 0; index < size; index++)
  5. if (elementData[index] == null) {
  6. fastRemove(index);
  7. return true;
  8. }
  9. } else {
  10. for (int index = 0; index < size; index++)
  11. if (o.equals(elementData[index])) {
  12. fastRemove(index);
  13. return true;
  14. }
  15. }
  16. return false;
  17. }
  18.  
  19. // ArrayList的私有方法,与remove(int index)方法类似
  20. private void fastRemove(int index) {
  21. modCount++;// Fail-Fast机制
  22. int numMoved = size - index - 1;
  23. if (numMoved > 0) {
  24. System.arraycopy(elementData, index + 1, elementData, index, numMoved);
  25. }
  26. elementData[--size] = null;
  27. }

删除指定位置区间内的元素

  1. // 移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之间的所有元素
  2. protected void removeRange(int fromIndex, int toIndex) {
  3. modCount++;
  4. int numMoved = size - toIndex;
  5. System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved);
  6. int newSize = size - (toIndex - fromIndex);
  7. while (size != newSize)
  8. elementData[--size] = null;
  9. }

为什么jdk源码中此方法是protected呢?具体的解释请参考
ArrayList removeRange方法分析这篇博文。

深入java集合学习2-ArrayList的实现原理的更多相关文章

  1. Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  2. 2019/3/4 java集合学习(二)

    java集合学习(二) 在学完ArrayList 和 LinkedList之后,基本已经掌握了最基本的java常用数据结构,但是为了提高程序的效率,还有很多种特点各异的数据结构等着我们去运用,类如可以 ...

  3. Java集合学习(9):集合对比

    一.HashMap与HashTable的区别 HashMap和Hashtable的比较是Java面试中的常见问题,用来考验程序员是否能够正确使用集合类以及是否可以随机应变使用多种思路解决问题.Hash ...

  4. 转:深入Java集合学习系列:HashSet的实现原理

    0.参考文献 深入Java集合学习系列:HashSet的实现原理 1.HashSet概述: HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特 ...

  5. 2019/3/2周末 java集合学习(一)

    Java集合学习(一) ArraysList ArraysList集合就像C++中的vector容器,它可以不考虑其容器的长度,就像一个大染缸一 样,无穷无尽的丢进去也没问题.Java的数据结构和C有 ...

  6. java集合系列之ArrayList源码分析

    java集合系列之ArrayList源码分析(基于jdk1.8) ArrayList简介 ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快, ...

  7. Java集合框架之ArrayList浅析

    Java集合框架之ArrayList浅析 一.ArrayList综述: 位于java.util包下的ArrayList是java集合框架的重要成员,它就是传说中的动态数组,用MSDN中的说法,就是Ar ...

  8. java集合学习(2):Map和HashMap

    Map接口 java.util 中的集合类包含 Java 中某些最常用的类.最常用的集合类是 List 和 Map. Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含 ...

  9. Java IO学习笔记:概念与原理

    Java IO学习笔记:概念与原理   一.概念   Java中对文件的操作是以流的方式进行的.流是Java内存中的一组有序数据序列.Java将数据从源(文件.内存.键盘.网络)读入到内存 中,形成了 ...

  10. java集合系列之三(ArrayList)

    上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayLi ...

随机推荐

  1. 如何删除webstrom中生成的.idea wrokspace

    首先说下遇到的问题,之前一直是通过webstrom来操纵github 以及git ,包括切换,生成分支,pull,push代码,这几天心血来潮 通过git代码进行了一次这些操作,然后当我在gitlab ...

  2. java https单向认证(忽略认证)并支持http基本认证

    https单向认证(忽略认证)并支持http基本认证, 温馨提示 1,jar包要导入对 2,有匿名类编译要注意 3,欢迎提问,拿走不谢!背景知识 Https访问的相关知识中,主要分为单向验证和双向验证 ...

  3. T-sql语句查询执行顺序

    前言 数据库的查询执行,毋庸置疑是程序员必备技能之一,然而数据库查询执行的过程绚烂多彩,却是很少被人了解,今天哥哥要带你装逼带你飞,深入一下这sql查询的来龙去脉,为查询的性能优化处理打个基础,或许面 ...

  4. 返回顶部的功能 div固定在页面位置不变

    1.你在网上搜索的时候,可能会搜索到div固定在页面上,不随滚动条滚动而滚动是用CSS写的,写法是position:fixed;bottom:0; 但是这个在iframe满地跑的页面实际开发中,有啥用 ...

  5. 已经重写,源码和文章请跳转http://www.cnblogs.com/ymnets/p/5621706.html

    文章由于写得比较仓促 已经重写,源码和文章请跳转 http://www.cnblogs.com/ymnets/p/5621706.html 系列目录 前言: 导入导出实在多例子,很多成熟的组建都分装了 ...

  6. 用枚举enum替代int常量

    枚举的好处: 1. 类型安全性 2.使用方便性 public class EnumDemo { enum Color{ RED(3),BLUE(5),BLACK(8),YELLOW(13),GREEN ...

  7. C#6新特性,让你的代码更干净

    前言 前几天看一个朋友的博客时,看他用到了C#6的特性,而6出来这么长时间还没有正儿八经看过它,今儿专门看了下新特性,说白了也不过是语法糖而已.但是用起来确实能让你的代码更加干净些.Let's try ...

  8. c#编程基础之ref、out参数

    引例: 先看这个源码,函数传递后由于传递的是副本所以真正的值并没有改变. 源码如下: using System; using System.Collections.Generic; using Sys ...

  9. [原创]django+ldap实现统一认证部分一(django-auth-ldap实践)

    前言 接之前我的文章,django+ldap+memcache实现单点登录+统一认证 ,ldap部署相关,ldap双机\LAM配置管理\ldap备份还原,目前来说,我们已经有了高可用性的ldap环境了 ...

  10. [连载]《C#通讯(串口和网络)框架的设计与实现》- 0.前言

                                  目       录 前言 前言 刚参加工作,使用过VB.VC开发软件,随着C#的崛起,听说是C++++,公司决定以后开发软件使用C#,凭借在 ...