Java-ArrayList源码解析

191 阅读4分钟

1.ArrayList底层数据结构

ArrayList的数据结构是采用数组形式来完成数据的容纳,像Java中的ArrayList、LinkedList、map、set等一系列集合都是拿来装数据的,首先你需要考虑的就是此集合采用的是什么数据结构容纳数据的,其次考虑并发情况下是否安全、性能如何。

1.1数组

image.png 像数组这里就不过多的描述了毕竟是最常见最常使用的一个最基本的线性表 这里提下数组是因为ArrayList的底层数据结构是采用数组

2.ArrayList源码解析

2.1ArrayList方法使用

        // 定义一个ArrayList
        ArrayList<String> arrayList = new ArrayList<>();
        ArrayList<String> arrayList1 = new ArrayList<>(20);
        // ArrayList添加元素的方法
        arrayList.add("CHINA");
        arrayList.add("BEIJING");
        arrayList.add("CHONGQING");
        // ArrayList根据下标获取元素的方法
        arrayList.get(1);
        // ArrayList根据下标删除元素的方法
        arrayList.remove(1);

2.2源码之前的几点思考

首先你需要思考几个点 带着你的问题 往源码里面看 可能效果会更好

1.既然ArrayList是采用的数据结构是数组,在创建ArrayList的时候,肯定要定义数组大小 为什么在使用的时候(像上文),并没有定义数组大小,如果ArrayList底层给我们定义默认的数组大小,那么默认的数组大小又是多大?且也可以像上文代码arrayList1创建指定数组大小,现在你可以先不考虑此定义构造函数

2.默认的ArrayList容量,当你一直添加元素进去,你并没有产生ArrayList‘装不下’的可能性(有限条件下),那肯定底层是对数组进行扩容了,那么是怎么扩容的?每次扩容的触发条件又是什么?

2.3源码解析

像LinkedList ArrayList都是实现List接口 就是将公用的方法抽出来面向接口编程 这里将不画实现图了

       
        ArrayList<String> arrayList = new ArrayList<>();
        ArrayList<String> arrayList1 = new ArrayList<>(20);
     

ArrayList的几个重要参数:

    
    // 默认的初始化ArrayList长度
    private static final int DEFAULT_CAPACITY = 10;
     
    transient Object[] elementData;
    
    private int size;

先看构造函数

  // 无参
  public ArrayList() {
        // 定义一个空数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
     // 将由用户输入指定的初始化长度
     public ArrayList(int initialCapacity) {
        // 主要判断输入的数组长度是否合法 
        /**
        如果大于0 就直接初始化容器
        如果等于0 就初始化空的数组
        如果小于0 就会抛出异常
        */
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

2.4ArrayList-add源码分析

以空参的构造器分析

使用数组作为容器时 在性能方面需要最好是指定容器大小 因为有扩容的操作 最好是知道你数据量的大小 做一个适中的判断

// 泛型 
 public boolean add(E e) {
        // 初始化数组长度 详情看下面的第一-第三步
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 当初始化容器后 就利用数组装数组
        elementData[size++] = e;
        // 所有操作成功后 返回true
        return true;
    }
     // 第一步 现在的size=0 所以这里传进来的值minCapacity为1
     private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
     // 第二步elementData为空 minCapacity=1
     private static int calculateCapacity(Object[] elementData, int minCapacity) {
         // 判断elementData是否为空 
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
           // 如果为空就返回 DEFAULT_CAPACITY和minCapacity最大的值 
            // 这里系统默认DEFAULT_CAPACITY=10 
            // 所以返回 10
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
         // 如果不为空这里就返回minCapacity
        return minCapacity;
    }
     // 第三步
     // 现在这里的minCapacity就为10
     private void ensureExplicitCapacity(int minCapacity) {
     // modCount默认等于0
        modCount++;
   
        // overflow-conscious code
        // 这里就是要扩容机制的判断
        // 当size+1的长度>数组的长度就触发扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
     private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 1.5倍的扩容机制
        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);
    }

当size+1的长度>数组的长度就触发扩容

image.png

2.5ArrayList-get源码分析

   // ArrayList的底层数据结构是数组 所以get方法 是通过数组下标获取数据的
  public E get(int index) {
        // 验证你输入的index的合法性 就跟你判空系列一样的操作
        // 如果你输入的indexx>=size 就会抛出下标越界异常
        rangeCheck(index);
        // 返回数组下标为index的数据内容
        return elementData(index);
    }

2.6ArrayList-remove(index)源码分析

// 通过下标删除指定元素
public E remove(int index) {
        // 验证你输入的index的合法性 就跟你判空系列一样的操作
        // 如果你输入的index>=size 就会抛出下标越界异常 
        rangeCheck(index);

        modCount++;
        // 获取你要删除的元素
        E oldValue = elementData(index);
        // 计算出要copy的数组的长度
        int numMoved = size - index - 1;
        if (numMoved > 0)
        //  Object src : 原数组
        //   int srcPos : 从元数据的起始位置开始
        //  Object dest : 目标数组
        //  int destPos : 目标数组的开始起始位置
        //  int length  : 要copy的数组的长度
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
        // remove方法 删除成功 会返回你删除的元素
        return oldValue;
    }

2.7ArrayList-remove(Object o)源码分析

 // 根据数据内容进行删除
 public boolean remove(Object o) {
        // 这里就是删除数据为null的
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
          
            // 因为是数组 只能循环遍历 进行匹配在进行删除 所以这里删除的不是所有的元素比如(数组:[a,c,d,a] 你要删除a 删除成功后的数组为:[c,d,a]) 是删除匹配的第一个元素
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    // 这里就跟remove(index)是一样的
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    
    
     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
    }