• ArrayList使用的存储的数据结构
  • ArrayList的初始化
  • ArrayList是如何动态增长
  • ArrayList如何实现元素的移除
  • ArrayList小结

ArrayList是我们经常使用的一个数据结构,我们通常把其用作一个可变长度的动态数组使用,大部分时候,可以替代数组的作用,我们不用事先设定ArrayList的长度,只需要往里不断添加元素即可,ArrayList会动态增加容量。ArrayList是作为List接口的一个实现。 那么ArrayList背后使用的数据结构是什么呢? ArrayList是如何保证动态增加容量,使得能够正确添加元素的呢?

要回答上面的问题,我们就需要对ArrayList的源码进行一番分析,深入了解其实现原理的话,我们就自然能够解答上述问题。

需要说明的是,本文所分析的源码引用自JDK 8版本

ArrayList使用的存储的数据结构 从源码中我们可以发现,ArrayList使用的存储的数据结构是Object的对象数组。 其实这也不能想象,我们知道ArrayList是支持随机存取的类似于数组,所以自然不可能是链表结构。

/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access

我想大家一定对这里出现的transient关键字很疑惑,我们都知道ArrayList对象是可序列化的,但这里为什么要用transient关键字修饰它呢?查看源码,我们发现ArrayList实现了自己的readObject和writeObject方法,所以这保证了ArrayList的可序列化。具体序列化的知识我们在此不过多赘述。有兴趣的读者可以参考笔者关于序列化的文章。

ArrayList的初始化

ArrayList提供了三个构造函数。下面我们依次来分析

public ArrayList(int initialCapacity);//当我们初始化的时候,给ArrayList指定一个初始化大小的时候,就会调用这个构造方法
List<String> myList = new ArrayList<String>(7);

源码中这个方法的实现如下

/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
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);
}
}

这里的EMPTY_ELEMENTDATA 实际上就是一个共享的空的Object数组对象。

/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};

上述代码很容易理解,如果用户指定的初始化容量大于0,就new一个相应大小的数组,如果指定的大小为0,就复制为共享的那个空的Object数组对象。如果小于0,就直接抛出异常。

  • public ArrayList() 默认的空的构造函数。 我们一般会这么使用

     myList = new ArrayList();

    源码中的实现是

    /**
    * Constructs an empty list with an initial capacity of ten.
    */
    public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    //其中DEFAULTCAPACITY_EMPTY_ELEMENTDATA 定义为
    **
    * Shared empty array instance used for default sized empty instances. We
    * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
    * first element is added.
    */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    注释中解释的很清楚,就是说刚初始化的时候,会是一个共享的类变量,也就是一个Object空数组,当第一次add的时候,这个数组就会被初始化一个大小为10的数组。

    • public ArrayList(Collection<? extends E> c) 如果我们想要初始化一个list,这个list包含另外一个特定的collection的元素,那么我们就可以调用这个构造函数。 我们通常会这么使用

      Set<Integer> set = new HashSet<>();
      set.add(1);
      set.add(2);
      set.add(3);
      set.add(4);
      ArrayList<Integer> list = new ArrayList<>(set);

      源码中是这么实现的

      /**
      * Constructs a list containing the elements of the specified
      * collection, in the order they are returned by the collection's
      * iterator.
      *
      * @param c the collection whose elements are to be placed into this list
      * @throws NullPointerException if the specified collection is null
      */
      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;
      }
      }

      首先调用给定的collection的toArray方法将其转换成一个Array。 然后根据这个array的大小进行判断,如果不为0,就调用Arrays的copyOf的方法,复制到Object数组中,完成初始化,如果为0,就直接初始化为空的Object数组。

      ArrayList是如何动态增长

    • 当我们像一个ArrayList中添加数组的时候,首先会先检查数组中是不是有足够的空间来存储这个新添加的元素。如果有的话,那就什么都不用做,直接添加。如果空间不够用了,那么就根据原始的容量增加原始容量的一半。源码中是如此实现的:
      /**
      * Appends the specified element to the end of this list.
      *
      * @param e element to be appended to this list
      * @return <tt>true</tt> (as specified by {@link Collection#add})
      */
      public boolean add(E e) {
      ensureCapacityInternal(size + 1); // Increments modCount!!
      elementData[size++] = e;
      return true;
      }

      ensureCapacityInternal的实现如下:

      private void ensureCapacityInternal(int minCapacity) {
      if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
      minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
      } ensureExplicitCapacity(minCapacity);
      }

      DEFAULT_CAPACITY为:

      private static final int DEFAULT_CAPACITY = 10;
    • 这也就实现了当我们不指定初始化大小的时候,添加第一个元素的时候,数组会扩容为10.
      private void ensureExplicitCapacity(int minCapacity) {
      modCount++; // overflow-conscious code
      if (minCapacity - elementData.length > 0)
      grow(minCapacity);
      }

      这个函数判断是否需要扩容,如果需要就调用grow方法扩容

      /**
      * Increases the capacity to ensure that it can hold at least the
      * number of elements specified by the minimum capacity argument.
      *
      * @param minCapacity the desired minimum capacity
      */
      private void grow(int minCapacity) {
      // overflow-conscious code
      int oldCapacity = elementData.length;
      int newCapacity = oldCapacity + (oldCapacity >> 1);
      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);
      }

      我们可以看到grow方法将数组扩容为原数组的1.5倍,调用的是Arrays.copy 方法。 在jdk6及之前的版本中,采用的还不是右移的方法

    • ArrayList如何实现元素的移除

      我们移除元素的时候,有两种方法,一是指定下标,二是指定对象

    • list.remove(3);//index
      list.remove("aaa");//object

      下面先来分析第一种,也就是

      • public E remove(int index) 源码中是如此实现的

        /**
        * Removes the element at the specified position in this list.
        * Shifts any subsequent elements to the left (subtracts one from their
        * indices).
        *
        * @param index the index of the element to be removed
        * @return the element that was removed from the list
        * @throws IndexOutOfBoundsException {@inheritDoc}
        */
        public E remove(int index) {
        rangeCheck(index); modCount++;
        E oldValue = elementData(index); 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 return oldValue;
        }

        对于数组的元素删除算法我们应该很熟悉,删除一个数组元素,我们需要将这个元素后面的元素全部向前移动,并将size减1. 我们看到源码中,首先检查下标是否在可用范围内。然后调用System.arrayCopy方法将右边的数组向左移动,并且将size减一,并置为null。

        public boolean remove(Object o)

源码中实现如下:

/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns <tt>true</tt> if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
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;
}

我们可以看到,这个remove方法会移除数组中第一个符合的给定对象,如果不存在就什么也不做,如果存在多个只移除第一个。 fastRemove方法如下

/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
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
}

可以理解为简化版的remove(index)方法。

ArrayList小结

  • ArrayList是List接口的一个可变大小的数组的实现
  • ArrayList的内部是使用一个Object对象数组来存储元素的
  • 初始化ArrayList的时候,可以指定初始化容量的大小,如果不指定,就会使用默认大小,为10
  • 当添加一个新元素的时候,首先会检查容量是否足够添加这个元素,如果够就直接添加,如果不够就进行扩容,扩容为原数组容量的1.5倍
  • 当删除一个元素的时候,会将数组右边的元素全部左移

ArrayList实现原理分析的更多相关文章

  1. ArrayList扩容原理分析

    1:代码解读和分析 1.1:构造方法分析 1: public ArrayList(int initialCapacity) { ) { this.elementData = new Object[in ...

  2. Junit 注解 类加载器 .动态代理 jdbc 连接池 DButils 事务 Arraylist Linklist hashset 异常 哈希表的数据结构,存储过程 Map Object String Stringbufere File类 文件过滤器_原理分析 flush方法和close方法 序列号冲突问题

    Junit 注解 3).其它注意事项: 1).@Test运行的方法,不能有形参: 2).@Test运行的方法,不能有返回值: 3).@Test运行的方法,不能是静态方法: 4).在一个类中,可以同时定 ...

  3. 「必知必会」最细致的 ArrayList 原理分析

      从今天开始也正式开 JDK 原理分析的坑了,其实写源码分析的目的不再是像以前一样搞懂原理,更重要的是看看他们编码风格更进一步体会到他们的设计思想.看源码前先自己实现一个再比对也许会有不一样的收获! ...

  4. ArrayList实现原理及源码分析之JDK8

    转载 ArrayList源码分析 一.ArrayList介绍 Java 集合框架主要包括两种类型的容器: 一种是集合(Collection),存储一个元素集合. 一种是图(Map),存储键/值对映射. ...

  5. Android ListView实现不同item的方法和原理分析

    ListView实现不同item的方法和原理分析 一问题抛出Listview是android里面的重要组件,用来显示一个竖向列表,这个没有什么问题:但是有个时候列表里面的item不是一样的,如下图,列 ...

  6. JAVA常用数据结构及原理分析

    JAVA常用数据结构及原理分析 http://www.2cto.com/kf/201506/412305.html 前不久面试官让我说一下怎么理解java数据结构框架,之前也看过部分源码,balaba ...

  7. Struts2 学习笔记18 拦截器原理分析

    我们来进行一下拦截器的原理分析,从Struts2的源代码开始,然后我们手动创建一个项目进行模拟.(源代码需要下载然后添加好才能看到)我们可以用Debug来读源码. 从doFilter开始执行,流程如图 ...

  8. Android 4.4 KitKat NotificationManagerService使用具体解释与原理分析(一)__使用具体解释

    概况 Android在4.3的版本号中(即API 18)增加了NotificationListenerService,依据SDK的描写叙述(AndroidDeveloper)能够知道,当系统收到新的通 ...

  9. java ArrayList的序列化分析

    一.绪论 所谓的JAVA序列化与反序列化,序列化就是将JAVA 对象以一种的形式保持,比如存放到硬盘,或是用于传输.反序列化是序列化的一个逆过程. JAVA规定被序列化的对象必须实现java.io.S ...

随机推荐

  1. 全面解读PHP-JS和jQuery

    一.变量的定义 1.未使用值来申明的变量,其值为 undefined. 2.如果重新声明一个变量,该变量的值不会丢失. //定义一个变量 var str = 'hello'; //重新申明 var s ...

  2. Android 带你读懂事件分发

    工作有一段时间,有必要掌握事件传递的机制,最近研究了一下,记录下心得.1 Android中的事件 android中触摸事件比较多,封装中MotionEvent类中,点击.触摸.滑动是我们常用的事件 M ...

  3. MongoDB简单查询语句<平时使用语录,持续更新>

    MongoDB查询语句 --查询近三个月的客户使用量  aggregate:使用聚合  match:过滤  group分组   -- mysql中select org_code as 近三个月使用商户 ...

  4. Elasticsearch 6.2.3版本 Windows环境 简单操作

    背景描述 Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎.无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进.性能最好的.功能最全的搜索引擎库. El ...

  5. spring cloud依赖服务调用优化

    1.请求缓存 优点: 注解方式实现: 设置缓存key: 如果可以确认,对要缓存的数据的操作,主要是写操作都只在feign调用中完成且读多写少,则可以使用此方式:如果在其他地方还有对数据的写操作,则可能 ...

  6. 小型自动化运维工具pssh和传输工具rsync

    一.简单介绍 1.pssh全称是parallel-ssh,基于Python编写的并发在多台服务器上批量执行命令的工具.包括pssh,pscp,prsync,pnuke和pslurp.该项目包括pssh ...

  7. Vue --》this.$set()的神奇用法

    作为一名开发者,我们都知道: data中数据,都是响应式.也就是说,如果操作data中的数据,视图会实时更新: 但在实际开发中,遇到过一个坑:若data中数据类型为对象,方法methods中改变对象的 ...

  8. json得回顾

  9. IDEA 如何批量修改变量名

    修改前的变量 System.out.println("bbbbb"); System.out.println("bbbbb"); System.out.prin ...

  10. javaScript中==和===对数组、对象的判断是它们是否同一个实例对象

      问题描述 在实现业务时,大量用到了 if(a === b)这样的判断,但有一个类似判断一直进不去这个if条件, a === b 返回的一直是false,但是其他几个类似判断,都正常触发条件. 原因 ...