ArrayList从入门到入土

ArratList从入门到入土

基本介绍

继承图

​ 要想了解一个类,首先先看一下其的继承关系:java

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d2jMJfxt-1597718403987)(https://gitee.com/iszhonghu/imagebed/raw/master/img/20200729104158.png)]git

基本介绍

  • 随机访问速度快,插入和移除性能较差web

  • 支持元素重复和为null数组

  • 元素有顺序安全

  • 线程不安全多线程

  • 数组大小灵活调整dom

  • ArrayList是基于动态数组实现的,在增删时候,须要数组的拷贝复制svg

  • ArrayList的默认初始化容量是10.每次扩容时候增长原先容量的一半,也就是变为原来的1.5倍函数

  • 删除元素时不会减小容量,若但愿减小容量则调用trimToSize()性能

  • 使用iterator遍历可能会引起多线程异常

ArrayList<String> alist = new ArrayList<String>();

​ 因为ArrayList实现了List接口,因此alist变量的类型能够是list类型;new关键字声明后的尖括号中也能够不用再指定元素的类型。

​ 因为实现了RandomoAccess接口,即提供了随机访问的功能。咱们能够经过元素的须要快速得到元素对象。

​ 因为实现了Cloneable接口,即覆盖了函数clone()能被克隆

​ 因为实现了Serializable接口,意味着支持序列化,能经过序列化去传输数据

基本属性

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {  
    // 序列化id 
    private static final long serialVersionUID = 8683452581122892189L;  
    // 默认初始的容量 
    private static final int DEFAULT_CAPACITY = 10;  
    // 一个空对象(为何是new Object[0]呢?)
    //用Object[0]来代替null 不少时候咱们须要传递参数的类型,而不是传null,因此用Object[0]
    private static final Object[] EMPTY_ELEMENTDATA = new Object[0];  
    // 一个空对象,若是使用默认构造函数建立,则默认对象内容默认是该值 
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];  
    // 当前数据对象存放地方,当前对象不参与序列化(主要是关键字transient起做用的) 
    transient Object[] elementData;  
    // 当前数组长度 
    private int size;  
    // 数组最大长度 
    private static final int MAX_ARRAY_SIZE = 2147483639;  
}

方法

构造方法

无参构造方法

​ 无参构造方法直接调用DEFAULTCAPACITY_EMPTY_ELEMENTDATA

传入容量的构造

​ 当传入的是0的时候调用EMPTY_ELEMENTDATA

​ 当传入的大于0就新建一个数组存储元素

public ArrayList(int initialCapacity) {
       // 若是传入的初始容量大于0,就新建一个数组存储元素
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            // 若是传入的初始容量等于0,使用空数组EMPTY_ELEMENTDATA
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
还支持从其余集合构造
public ArrayList(Collection<? extends E> c) {
       //把集合转为数组
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // 检查c.toArray()返回的是否是Object[]类型,若是不是,从新拷贝成Object[].class类型
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 若是是空集合,则初始化为空数组EMPTY_ELEMENTDATA
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

增长方法

add方法可说是ArrayList比较重要的方法,首先总览一下:

// 直接添加元素
public boolean add(E e) {        
    ensureCapacityInternal(size + 1);         
    elementData[size++] = e;        
    return true;    
}
// 插入到特定的位置上
public void add(int index, E element) {        
    rangeCheckForAdd(index);        
    ensureCapacityInternal(size + 1);        
    System.arraycopy(elementData, index,elementData, index + 1,size - index);    
    elementData[index] = element;        
    size++;   
}
add(E,e)

添加元素到末尾,平均时间复杂度O(1)

步骤:

  • 检查是否须要扩容
  • 插入元素

首先,咱们看这个方法:

public boolean add(E e) {
    // 检查是否须要扩容
    ensureCapacityInternal(size + 1);   
    // 把元素 插入到末尾
    elementData[size++] = e;        
    return true;    
}
  • 先确认list容量,尝试容量加一,看看有无必要

  • 添加元素

接下来看这个小容量(+1)是否能知足咱们的需求

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 想要获得最小的容量(不浪费资源)
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureCapacityInternal(int minCapacity) { 
    // 明确容量大小
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private void ensureExplicitCapacity(int minCapacity) {        
    modCount++;      
    // 若是要的最小容量比数组的长度要打,那么就调用grow()来进行扩容。
    if (minCapacity - elementData.length > 0)          
        grow(minCapacity);   
}

而后调用grow扩容方法。

总结一下:

  • 首先去检查一下数组的容量是否足够
    • 扩容到原来的1.5倍
    • 第一次扩容后,若是容量仍是小于minCapacty,就将容量扩容为minCapacity。
    • 足够:直接添加
    • 不足够:扩容
add(int index,E element)

在指定位置插入元素,时间复杂度O(n)

步骤:

  • 检查角标
  • 空间检查,若是有须要就进行扩容
  • 插入元素
public void add(int index, E element) { 
    // 检查角标是否越界
    rangeCheckForAdd(index);  
    // 检查是否须要扩容
    ensureCapacityInternal(size + 1);  
    // 调用arraycopy()来进行插入C++编写的
    System.arraycopy(elementData, index, elementData, index + 1,size - index);       
    //插入到指定位置并扩容
    elementData[index] = element;        
    size++;    
}
addALL

用于批量添加

public boolean addAll(Collection<? extends E> c) {
       // 集合转化成数组
        Object[] a = c.toArray();
        
        int numNew = a.length;
        //检查是否须要扩容
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //将集合内的元素复制到 elementData 中,覆盖 [size, size+numNew) 的元素
        System.arraycopy(a, 0, elementData, size, numNew);
        //数组大小增长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)
           // 将 elementData 中位置为 index 及其之后的元素都向后移动 numNew 个位置
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
        // 将集合内的元素复制到 elementData 中,覆盖 [index, index+numNew) 的元素 
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

扩容方法

grow()方法
private void grow(int minCapacity) {               
    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);  
    // 扩容完之后,调用的是copyOf()方法
    elementData = Arrays.copyOf(elementData, newCapacity);    
}
copyOf()方法
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {       
    @SuppressWarnings("unchecked")        
    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;    
}

get方法

步骤

  • 检查角标
  • 返回元素
public E get(int index) {  
     // 检查角标
     rangeCheck(index); 
     // 返回具体元素
     return elementData(index);    
 }
// 检查角标
private void rangeCheck(int index) {        
    if (index >= size)            
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));  
}
// 返回元素 
E elementData(int index) {            
    return (E) elementData[index];    
}

set方法

步骤:

  • 检查角标
  • 代替元素
  • 返回旧值
public E set(int index, E element) {   
    // 检查角标
    rangeCheck(index);   
    // 将值进行替换,返回旧值
    E oldValue = elementData(index);        
    elementData[index] = element;        
    return oldValue;    
}

remove方法

步骤

  • 检查角标
  • 删除元素
  • 计算出须要移动的个数,并移动
  • 设置为null,让GC回收
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; 
    return oldValue;    
}

序列化

​ 须要注意的是ArrayList中有两个属性是被transient关键字修饰的。其做用就是让修饰的成员属性变量不被序列化

transient Object[] elementData;
protected transient int modCount = 0;

​ 因为ArrayList没有用Java序列化机制的默认处理来序列化elementData数组,而是经过readObject、writeObject方法自定义序列化和反序列化策略。

​ 之因此要用自定义的序列化和反序列化策略,是由于效率问题。防止序列化一些没有存数的字段

线程安全

​ ArrayList源码中,有不少快速失败的机制,好比判断扩容的方法中:

private void ensureExplicitCapacity(int minCapacity){
    // 修改次数+1,用于 fail-fast 处理
    modCount++;
}

​ 这种快读失败的机制必定程度上避免了线程安全问题

“快速失败”即fail-fast,它是java集合的一种错误检测机制。当多线程对集合进行结构上的改变或者集合在迭代元素时直接调用自身方法改变集合结构而没有通知迭代器时,有可能会触发fast-fail机制并抛出异常

​ 固然fail-fast机制只是可能触发,实际上,ArrayList的线程安全仍是没有保证的,要想保证ArrayList的线程安全,可使用一下几个方案

  • 使用Collections.SynchronizedList包装ArrayList,而后操做包装后的list便可
  • 使用CopyOnWriteArrayList代替ArrayList
  • 在使用ArrayList时,应用程序经过同步机制去控制ArrayList的读写(不推荐)
  • 使用Vector代替ArrayList(不推荐由于已通过时)

最后

  • 若是以为看完有收获,但愿能给我点个赞,这将会是我更新的最大动力,感谢各位的支持
  • 欢迎各位关注个人公众号【java冢狐】,专一于java和计算机基础知识,保证让你看完有所收获,不信你打我
  • 若是看完有不一样的意见或者建议,欢迎多多评论一块儿交流。感谢各位的支持以及厚爱。