ArrayList的基本使用及底层解析

ArrayList的概述和基本使用


前言

数组的长度在程序运行期间不可以发生改变,
但是ArrayLIst集合的长度是可以随意变化的。

类 ArrayList<E>尖括号里面的是泛型;
泛型:也就是装在集合当中的所有元素全都是统一的类型。
注意:泛型只能是引用类型不能是基本类型

为什么泛型只能是引用类型?
	java泛型是使用类型擦除来实现的,类型会被擦除为Object,也就是说泛型在底层都会被强制转换为Object类型。像是(Object)1这种强制转换在理论上
来说是会报类型转换错误的。但是编译器做了处理:(Object)1->(Object)Integer.valueOf(1)。而基本类型不能转换为Object类型,所以在编译时期,就会指出错误。


ArrayList<String> list=new ArrayList<>();
从jdk7开始右边的尖括号可以不写类型,但是左边必须要写

注意事项:1、对于ArrayList集合来说,直接打印得到的不是地址值而是内容,如果内容是空的,得到的是空的中括号
public class Demo1ArrayList {
    public static void main(String[] args) {
        ArrayList<String> list=new ArrayList<>();

        list.add("Arvin");
        list.add("Kevin");
        System.out.println(list);
    }
}

一、常用方法

如果想往ArrayList中存放基本类型数据,必须要使用基本类型对应的包装类
包装类:
byte    Byte
short   Short
int       Integer    特殊
long     Long
float     Float
double  Double
char    Character   特殊
boolean    Boolean

ArrayList当中常用的方法

public boolean add(E e),向集合当中添加元素,参数的类型和泛型一致
备注:对于ArrayList集合来说,add添加动作是一定成功的,所以返回值可以不用
但是对于其它集合来说,add添加动作不一定成功,所以返回值代表添加是否成功。

public E get(int index);从集合当中获取元素,参数是索引编号,返回值就是对于位置的元素。

public E remove(int index);从集合当中删除元素,参数是索引编号,返回值就是被删除的元素。

public int size();获取集合的尺寸长度,返回值是集合中包含的元素个数。

void clear() 从列表中删除所有元素。  

boolean contains(Object o) 如果此列表包含指定的元素,则返回 true 。
 
boolean isEmpty() 如果此列表不包含元素,则返回 true 。  

二、ArrayList底层

ArrayList有序而且查询极快,因为底层是动态数组实现的,故我们可以通过数组索引的下标定位元素所在的位置。

ArrayList数组的三种构造方法

指定初始集合容量

  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);
        }
    }

注意:集合初始化容量时,底层集合为指定大小,但是size值依然为 0

空构造方法创建

  public ArrayList() {
  //private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  //创建一个空数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

单列集合参数构造方法创建

public ArrayList(Collection<? extends E> c) {
//将传入的单列表集合变成数组,并赋值给当前集合的底层数组
        elementData = c.toArray();
        
        if ((size = elementData.length) != 0) {
         // 数组类型不匹配的情况发生在toArray()中,实际元素量大于预期量时,迭代产生新的数组
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

add方法的实现

   public static Integer valueOf(int i) {
   //判断Integer值是否合理
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  
        //集合尾部添加数据,并将size的大小+1
        elementData[size++] = e;
        return true;
    }

添加前动态数组的动作:
判断创建的初速数组是否为 {} 如果是则 比较minCapacity与DEFAULT_CAPACITY(10) 的大小,取最大值。
也就是说,在添加第一个元素的时候,底层的动态数组会自动扩容一个大小为 10 的数组空间。

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

        ensureExplicitCapacity(minCapacity);
    }

remove方法实现

 public E remove(int index) {
 //判断索引是否合理
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);
		//判断是否是尾元素,如果是则直接将其值为null 并size-1 即可
        int numMoved = size - index - 1;
        //不是尾元素,则要重写创建一个新数组
        if (numMoved > 0){
        //批量赋值元素至新数组中
        //将index后续的元素复制到数组对象上
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
                             }
           //被删除索引是最后一位则将其为空且size-1
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

clear方法

将集合元素依次设置为 null
最后将size =0

 public void clear() {
        modCount++;
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

clone方法

ArrayList的克隆是浅克隆,是复制了原来的数组

三、ArrayList扩容

首先它会计算出数组需要的最小容量,然后调用grow(int minCapacity)方法进行扩容

  // 计算出最小需要的容量大小
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        // 如果当前数组还处于空数组阶段,那么判断size+1的值也就是minCapacity和默认初始容量10的大小,取最大值
        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++;

        //如果当前数组的长度不足以容纳最小容量的元素,那么就扩容
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
        //容量增长方法
            grow(minCapacity);
    }
数组扩容时,会将老数组中的元素重新拷贝一份到新的数组中,
每次数组容量增长大约是其容量的1.5倍,如果扩容一半不够,
就将目标size作为扩容后的容量.这种操作的代价很高。
采用的是 Arrays.copyOf浅拷贝,

grow(int minCapacity)方法

// 扩容机制,传入当前需要的最小容量大小
    private void grow(int minCapacity) {
        // overflow-conscious code
        // 扩容前,数组的长度
        int oldCapacity = elementData.length;
        // 新的数组长度 = 旧的数组长度 + 旧数组长度/2 = oldCapacity*1.5 (新数组长度为旧数组长度1.5倍) 赋值为int时向下取整
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果新数组长度小于最小容量大小,则新数组长度=最小容量大小
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 如果新数组长度大于int范围,则返回int最大值
        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);
    }

原文链接:https://blog.csdn.net/qq_42290561/article/details/107429431

四、集合为什么在add或者remove方法中的modCount

记录集合机构被修改的次数
modCount是ArrayList的抽象父类AbstractList中的一个变量,用来记录集合机构被修改的次数。源码文档中的解释是它用来在迭代遍历集合的时候判断集合的修改状态,如果在遍历过程中发现modCount发生了改变,就会抛出ConcurrentModificationExceptions
因为ArrayList是线程不安全的,所以在并发的时候,如果有线程正在遍历集合,而有线程修改,modCount的状态就会发生变化-----》抛出并发修改异常

五、 浅拷贝与深拷贝

原文链接:https://blog.csdn.net/qq_38859786/article/details/80318977
定义
首先让我们来看一下它们俩的定义:
浅拷贝:使用一个已知实例对新创建实例的成员变量逐个 赋值,这个方式被称为浅拷贝。

深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。

浅拷贝

浅拷贝的前提是该类了实现Cloneable接口,并重写clone方法。在拷贝某个对象时,调用该对象的clone方法返回一个新的对象,该对象就是浅拷贝的结果。

基本数据类型:对该成员变量进行复制。
引用数据类型:复制引用,但不会开辟新的内存空间,因此被拷贝对象的该成员变量与拷贝对象对应的成员变量 指向同一块内存地址

深拷贝

当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。

六、总结

参考文献:https://blog.csdn.net/qq_25868207/article/details/55259978

ArrayList是List接口的可变数组非同步实现,并允许包括null在内的所有元素。
底层使用数组实现
该集合是可变长度数组,数组扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量增长大约是其容量的1.5倍,这种操作的代价很高。若是能预估到顶峰容量,可以设置一个足够大的量以避免数组容量以后的扩展。
采用了Fail-Fast机制,面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险
remove方法会让下标到数组末尾的元素向前移动一个单位,并把最后一位的值置空,方便GC
add、remove操作对于ArrayList其运行时间是O(N),因为在它当中在前端进行添加或移除构造新数组是O(N)操作;get方法的调用为O(1)操作。要是使用一个增强的for循环,对于任意List的运行时间都是O(N),因为迭代器将有效地从一项到下一项推进。
相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页