一天一种数据结构 ArrayList

ArrayList简介

什么是ArrayList,首先我们先把ArrayList拆分开来,一个是Array,一个是List

Array

什么是array,array即数组,想想我们是如何创建数组的。创建数组时我们会声明数组的容量大小。创建完数组后容量就不可以在更改。之所以容量不能更改是因为数组在内存中时连续的存储的内存单元。想想如果我们在更改容量时内存,就不再连续。

List

什么是list,list即链表,链表在内存中是不连续的,那么如何把这些不连续的内存单元联合起来形成一个独特的链表,就需要有每一个内存单元都有指向下一个元素的应用共同构成一个内存单元。所以链表是可以更改容量的。

ArrayList

ArrayList就是一种基于array实现的list。也就是动态数组。ArrayList当存储的对象超过一定数量后自动扩容。

ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。

ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。

ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。

ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。

ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。

源码分析

属性

private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 初始化容量为10.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 一个空对象
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
    一个空对象,如果使用默认构造函数创建,则默认对象内容默认是该值
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
    当前数据对象存放地方,当前对象不参与序列化
     */
    transient Object[] elementData; 

    /**
     当前数组长度
     */
    private int size;

无参构造

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // elementData指向地址空对象
    }

普通有参构造

  /**
    有参构造数组长度
  */
    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);
        }
    }

由collection进行构造

  1. 首先将collection对象转化为数组
  2. 更新size属性的值为当前数组的长度然后再进行判断
  3. 如果数组长度不为零则执行Arrays.copy方法,把collection对象的内容(可以理解为深拷贝)copy到elementData中。如果数组长度为0则将空对象EMPTY_ELEMENTDATA的地址赋给elementData
 public ArrayList(Collection c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

add(E e)方法解析

添加一个元素主入口

  1. 先将size+1然后计算容量,这里以无参的构造方法add一个元素举例,也就是当size+1过后为1先执行calculateCapacity方法然后返回10
  2. 再执行ensureExplicitCapacity方法,修改的次数会加一同时会再进行一次判断此时的minCapacity为10>elementData.length的0所以执行grow方法此时oldCapacity=0,newCapacity执行完后为10将数组进行深拷贝,回到add方法将添加的元素赋值到elementData数组的相应位置。
  3. 也就是说第一次进行拷贝的时候是size=0,第二次是size=10,第三次size=15。也就是每一次拷贝数组容量会变成原来的1.5倍。
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    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++;

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

add(int index, E element)方法

  1. 先进行索引范围判断是否越界等问题
  2. 判断size+1后是否足够存下数据和上面的add(E e)方法类似
  3. 然后调用system类中封装的本地方法(基于C封装)第一个参数为源数组,第二个为源数组复制的起始位置,第三个为目的数组,第四个为目的数组的起始位置,第五个为复制的长度。
    比如:
    数组1:int[] arr = { 1, 2, 3, 4, 5 };
    数组2:int[] arr2 = { 5, 6,7, 8, 9 };
    运行:System.arraycopy(arr, 1, arr2, 0, 3);
    得到:
    int[] arr2 = { 2, 3, 4, 8, 9 };
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

remove(int index) 方法

  1. 首先判断索引范围
  2. 根据索引取出元素
  3. 计算需要向前移动元素的个数然后进行深拷贝
  4. 将最后的元素置为空
    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; // clear to let GC do its work

        return oldValue;
    }

ArrayList总结

  • ArrayList基于数组实现必然读快写慢(往中间写)会造成数组的拷贝(也可理解为移动)可以扩展大小
  • ArrayList增加元素时可能会扩容,每次扩容的大小为原来的1.5倍。
  • 删除元素时也是通过数组的拷贝只是拷贝的位置不同,然后将最后一个元素置为null,此时底层的数组大小并没有改变,但ArrayList的size属性改变了。然后等待gc回收
  • ArrayList没有用任何同步机制会出现线程安全问题

你可能感兴趣的:(一天一种数据结构 ArrayList)