Java学习|ArrayList源码浅析


文章目录

  • 前言
  • 一.ArrayList介绍
  • 二.ArrayList的部分源码学习
    • 1.初始化
    • 2.add方法
      • (1).add(E e)
      • (2).Arrays.copyOf

前言 ArrayList是我们常用到的一个重要的java数据结构。ArrayList本质上是一个数组,可以包含万物的数组,同时是一个增长的数组。下面就简单的分析下它的一些机制。本文分析基于JDK1.8
一.ArrayList介绍 【Java学习|ArrayList源码浅析】ArrayList的继承关系:
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable

ArrayList主要是继承AbstractList类和List接口
这里有一个奇怪的接口RandomAccess,其实这个是有很大的用处的,后面将详细的介绍下该接口的使用。
ArrayList与Collection关系如下图:
Java学习|ArrayList源码浅析
文章图片

ArrayList的构造函数:
// 默认构造函数 ArrayList() // capacity是ArrayList的默认容量大小。当由于增加数据导致容量不足时,容量会添加上一次容量大小的一半。 ArrayList(int capacity) // 创建一个包含collection的ArrayList ArrayList(Collection collection)

二.ArrayList的部分源码学习 1.初始化 从本质上ArrayList是对Object[]数组的封装,ArrayList的设计是为了解决普通的数组只能固定长度。普通数组在使用中一方面可能会出现内存的浪费,另一方面可能会造成数组容量不够。
/** *有参构造函数 *1.根据你输入的initialCapacity生成initialCapacity长度的Object数组 *2.如果你输入的initialCapacity=0,会使用默认的长度为0的数组 *3.如果小于0直接非法参数异常 */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = https://www.it610.com/article/new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * 无参构造函数 * 当使用无参构造函数的时候,内部的创建一个默认的长度为0的数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA */ public ArrayList() { this.elementData = https://www.it610.com/article/DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 有参构造函数 * 将Collection的数据作为ArrayList的一部分 * 通过toArray转换为Object[]的数组,判断长度是否为0。 */ public ArrayList(Collection c) { elementData = https://www.it610.com/article/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; } }

这个elementData数组是整个ArrayList的真正的容器。所有的操作最后都要落实到它上面。
2.add方法 ArrayList之所以被称为动态数组,在于它可以进行扩容,扩容的秘密藏于add方法中
ArrayList的add方法有两个方法分别是add(int index, E element)和add(E e)
先来看add(E e)方法。
(1).add(E e)
add方法中调用了ensureCapacityInternal方法,详细的说明已经在注释中写的很清楚了。
//add方法 public boolean add(E e) { ensureCapacityInternal(size + 1); // 添加一个元素,在原有的size基础上增加1,但是注意这里成员变量size的值没有被改变 elementData[size++] = e; //添加一个元素 return true; }//ensureCapacityInternal方法 private void ensureCapacityInternal(int minCapacity) { //判断是否是ArrayList内部提供的默认数组 if (elementData =https://www.it610.com/article/= DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //如果是,算出minCapacity,取出DEFAULT_CAPACITY和传入的参数minCapacity的最大值 // 同时这种写法也是避免出现minCapacity出现非正数的情况 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } //调用ensureExplicitCapacity方法,这时候minCapacity的值一定是大于0的 ensureExplicitCapacity(minCapacity); }private void ensureExplicitCapacity(int minCapacity) { //modCount是在对list,Iterator迭代的时候判断是否改变了整体的数据结构的一种标记 modCount++; //minCapacity> elementData.length才会调用grow方法 // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }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: //使用Arrays.copyOf方法来增加原数组的的长度 elementData = https://www.it610.com/article/Arrays.copyOf(elementData, newCapacity); }

注意这句代码:
oldCapacity >> 1

它表示oldCapacity除以2的一次方,实际上就是除以2。这种写法相对于直接写成"oldCapacity /2"的效率更高,java的源码中大量使用了这种写法。到目前为止还没有增加Object[]数组的容量,那么如何增加已经固定长度的Object[]数组大小呢?让我们看下"Arrays.copyOf"代码
(2).Arrays.copyOf
public static T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass()); } public static T[] copyOf(U[] original, int newLength, Class newType) { // 保证copy数组是Object[]类型的 T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); // 重头戏 System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }

System.arraycopy的解释
Object src : 原数组 int srcPos : 原数组的起始位置 Object dest :目标数组 int destPos :目标数组的起始位置 int length:要copy的数组的长度public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)public static native void arraycopy(Object src,intsrcPos, Object dest, int destPos, int length);

最后扩容是重新创建了一个比原来数组大二分之一的新数组,再通过System.arraycopy拷贝原数组数据到新数组中。相当于elementData数组扩大了原来数组的二分之一。
待续

    推荐阅读