Java集合源码探究~List

在Java中,集合是咱们常常要使用的内容,而且集合也是面试的考点之一,掌握集合帮助咱们了解更多的内部构造。面试

List

list集合是表明的是一个元素有序的,可重复的集合。数组

虽然List中有不少子类的实现,但咱们常常用的仍是那几个,ArrayList,LinkedList,Vector等内容。安全

ArrayList

ArrayList 是底层由数组构成的集合,可是ArrayList有哪些优势呢?bash

  • 可以作到动态扩容,再也不局限于设置的数组大小。
  • 继承于List,有集合的操做方式,方便快速的操做书库,添加,删除,修改,遍历等内容。

缺点多线程

  • 不是线程安全的集合,在操做多线程的时候须要采用别的集合例如Vector或者CopyOnWriteArrayList方式。

ArrayList 源码解析

基本元素

// 默认的List 集合大小,在建立ArrayList 的时候没有制定大小 默认是10 
    private static final int DEFAULT_CAPACITY = 10;

   // 默认的空对象
    private static final Object[] EMPTY_ELEMENTDATA = {};

   // 默认对象内容是该值
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //当前数据对象存放的地方  
    transient Object[] elementData; // non-private to simplify nested class access

    //当前数组的长度
    private int size;
    
    // 数组最大的长度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    // 改变list 大小的次数,进行增删除数据都涉及到此数值
    protected transient int modCount = 0;
复制代码

方法介绍

既然是数组集合,就须要涉及到数组的扩容与缩容,在原先咱们学习数组的时候就了解,数组的扩容与缩容都涉及到数组内的数据的迁移问题。性能

既然ArrayList 底层是数组,想固然的也须要涉及到这部份内容。学习

add方法

add 方法中涉及到增长单个元素,增长单个元素到指定位置,增长一个集合元素,增长一个集合元素到指定位置四个不一样类型的方法,可是基本内容是相同的.ui

public boolean add(E e) {
          元素增长 ,在如今的大小上增长1 
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
     // 计算是否须要进行扩容 ,须要就进行扩容
     private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        // 默认的空对象 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 
        // 判断 如今list 列表中的元素 是不是空对象。 空对象 返回 最大的值 。不是空对象 返回minCapacity
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    private void ensureExplicitCapacity(int minCapacity) {
        // 扩容结构进行加1 
        modCount++;
        // 进行扩容 
        // 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:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    // 计算容量 选择 
     private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    
复制代码

从以上代码来看库容有如下流程来判断this

  • 若是是新创建的集合list 加入数据
    1. 首先new出来的是集合,不给定参数的状况下,是没有进行任何容量的初始化的。
    2. 在执行插入add的时候,会进行容量的初始化10.
  • 已经存在的数据量的集合或者指定集合数量
    1. 在建立的时候指定数量,那么会初始化这么数组空内容。
    2. 执行插入的时候会进行直接插入。

以上内容新建后的流程,有内容后,就涉及到扩容的问题。spa

  • List 数组扩容。
    1. 判断如今list里面内容的大小是否超过设置的容量大小。
    2. 不超过不执行扩容
    3. 超过执行库容
    4. 扩容首先扩大1.5倍的大小容量
    5. 若是该容量仍是不足以放置新增的数据,会直接扩容到最小要求的容量。
    6. 新的容量大小与最大值进行比较
    7. 存在负值状况小于0 直接超出内容容量的大小。
    8. 大于如今最大值直接返回Integer的最大值。
    9. 说明list不是无限大小的,最大是Integer的最大值。
    10. 进行数据的copy进行数组的扩容。 代码解释请看上面
public void add(int index, E element) {
        // 指定位置增长数据,须要检查该位置是否已经被安置数据若是没有那么执行失败
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 进行数据的copy 工做,将该位置的数据日后面进行复制
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        而后修改该值
        elementData[index] = element;
        size++;
    }    
    
      public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
    
       public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
复制代码

整体添加流程简单来讲是这样的:

  1. 判断是不是新初始化的空集合或者指定了容量的集合
  2. 进行添加数据判断是否须要扩容,或者先判断指定位置数据是否存在
  3. 扩容后的数据迁移。

ArrayList 集合容量扩容会致使性能问题,Java中复制是须要消耗内容空间,建立一样数量的对象大小,特别是大批量数据进行库容容易致使性能降低。

set get

set get 方法没有须要多说的,根据下标进行数据的读取与插入,小标注意不要超过集合大小。

remove

remove 方法在数组中会致使数组的结构的破坏,删除数据也须要进行数据的迁移。

// 移除指定元素
    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);
        //最后的一位置位null  让gc来处理
        elementData[--size] = null; // clear to let GC do its work
        返回移除内容
        return oldValue;
    }
复制代码
// 批量进行删除
private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                // 判断不被 删除的元素是否在集合中 集合是 0  1 2  3 4  5   删除 1 3 complement = true 
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
        
            // Preserve behavioral compatibility with AbstractCollection, 
            // even if c.contains() throws. 1 3 2 3 4 5  异常执行复制copy  这里主要是存在可能删除的元素不在集合中
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work 0 1  size = 6  w = 2 
                for (int i = w; i < size; i++)     
                    elementData[i] = null;
                modCount += size - w;  
                size = w;       
                modified = true; 
            }
        }
        return modified;
    }
复制代码

删除代码中主要涉及到

  • 删除指定元素,都涉及到数组内容的拷贝。

循环

  • for循环,根据指针进行循环,比较快速
  • foreach与迭代器循环数据比较适合链表式的集合数据
  • 删除集合中的数据使用迭代器,若是使用for指针循环删除数据容易出现异常。
相关文章
相关标签/搜索