面试题总结-Java集合类系列(1)-ArrayList

前言:为什么要写这篇文章?

  这是我写的面试题系列第一篇文章,来说说原因:

  现在是知识大爆炸的时代,任何面试题在网上都能找到一堆的答案,我为什么还需要写?

  因为每个人的知识接受范围和接收程度是不一样的,你在网上找到的答案,简单了不稀罕看,复杂的又看不懂,或者文章语气看不习惯的,很少找到了非常适合自己的,很多知识还是碎片式的。

  所以,建议各位朋友也整理一份属于自己的知识文档。

 

1. ArrayList 概述

  ArrayList 是Java开发者最常用的集合类之一,底层是Object数组组成。

  ArrayList 是继承了AbstractList 类,实现了 List 接口。AbstractList 是一个抽象类,也是实现了 List 接口。而 List 接口继承了 Collection  接口。所以,我们需要向上查看接口类,看 List 和 Collection 就可以了。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable 

 

1.1  ArrayList的常量

  ArrayList 类中,定义了3个常量,官方解释如下:

    /**
     * Default initial capacity.
* * 默认初始化容量,10
*/ private static final int DEFAULT_CAPACITY = 10; /** * Shared empty array instance used for empty instances.
* * 所有空数组共享的 空数组
*/ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added.
* * 用于默认大小的空实例的共享空数组实例。 * 我们要与EMPTY_ELEMENTDATA 区分开,当添加第一个元素时,用于扩张数组
*/ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

 

1.2 ArrayList 的属性

    /**
     * ArrayList 中保存的具体数据
     * 如果实例化时指定了容量,数组长度与指定容量相同。
     * 如果实例化时是空ArrayList,则使用elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     */
    transient Object[] elementData; 

    /**
     * ArrayList 包含的元素数据量
     */
    private int size;

 

1.3 构造函数

  ArrayList 提供了3个构造方法

    /**
     * 构造一个具有指定初始容量的空列表。
     * initialCapacity 是初始化的容量
     * 当 initialCapacity > 0 时,this.elementData = new Object[initialCapacity];
     * 当 initialCapacity == 0 时,this.elementData = EMPTY_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);
        }
    }

    /**
     * 构造一个初始容量为10的空列表。
     * 初始化时就是空列表,只有在第一次调用add方法时,才把数组容量设为10
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 构造一个包含指定的元素的列表集合返回它们的顺序迭代器。
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

 

2. Add方法

  Add方法的主要逻辑如下:

  (1) 先判断当前的数组是否有空间添加此元素,判断参数是最小容量 minCapacity =  size + 1

    (1.1) 先判断当前数组是否为空:为空的情况,比较默认值容量 与 minCapacity 谁大,返回大的值;数组不为空就返回 minCapacity 

    (1.2) minCapacity 如果大于当前数组容量,则需要对数组扩容,扩容逻辑参见 grow 方法

  (2) 如果是指定了index的add方法,需要复制数组的数据,原index位置的数据整体往后移。

  (3) 添加元素到指定位置。

  (4) size变量值加1

    /**
     * Appends the specified element to the end of this list.
     * 将指定的元素追加到列表的末尾。
     */
    public boolean add(E e) {
        // 判断存储能力
        ensureCapacityInternal(size + 1);
        // 在队尾添加元素
        elementData[size++] = e;
        return true;
    }

    /**
     * 添加元素到指定的index位置
     * 
     */
    public void add(int index, E element) {
        // 判断index范围,如果>size 或 <0 会抛异常
        rangeCheckForAdd(index);
        // 判断存储能力
        ensureCapacityInternal(size + 1);
        // 转移数字位置,把index位置的元素空出来
        System.arraycopy(elementData, index, elementData, index + 1,  size - index);
        // 在index位置添加元素
        elementData[index] = element;
        size++;
    }

    /**
     * 确认处理能力的方法
     */
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    /**
     * 计算当前数组所需的存储容量
     */
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // 当数组还是空的时候,判断默认存储空间和 minCapacity的值,返回最大的
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    /**
     * 处理存储能力
     */
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            // grow是数组的扩容方法,有需要的自己看一下
            grow(minCapacity);
    }

 

3. Get方法

  Get方法很简单,首先判断了index参数是否合法,再直接从数值取值。

    /**
     * Returns the element at the specified position in this list.
     */
    public E get(int index) {
        // 验证index参数是否大于等于size,是的话抛异常
        rangeCheck(index);
        // 根据数组原理,直接从index位置取值
        return elementData(index);
    }

 

4. Remove方法

4.1 remove(int index)

  根据坐标删除数据,操作如下:

  (1) 验证参数index是否合法

  (2) 查询出要删除的值,最后返回使用

  (3) 计算复制的数组元素数量

  (4) 复制数组,用index后面的数据,整体向前移动,把原index位置的数据覆盖

  (5) 把最后一个数据,设置为null

    /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).*/
    public E remove(int index) {
        // 验证index参数是否合法
        rangeCheck(index);

        modCount++;
        // 取出要删除的值
        E oldValue = elementData(index);
        // 计算要复制的数组元素的数量
        int numMoved = size - index - 1;
        if (numMoved > 0)
            // 复制数组,把index + 1位置开始的数据,整体往前复制
            System.arraycopy(elementData, index+1, elementData, index, numMoved);
            // 上面是复制操作,所以size--的位置上还保留着一条重复数据,需要设置为null
            elementData[--size] = null; // clear to let GC do its work
        // 返回删除的数据
        return oldValue;
    }    

 

4.2 remove(Object o)

  根据元素删除,先判断元素是否存在,存在就使用刚查到的index,使用快速删除方法删除元素。

    /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     */
    public boolean remove(Object o) {
        if (o == null) {
            // 如果元素为空,判断null元素的位置
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    // 删除方法
                    fastRemove(index);
                    return true;
                }
        } else {
            // 元素不为空,使用equals 判断,元素的位置
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    // 删除方法
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * 快速删除方法,原理与remove(int index) 一样
     */
    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;
    }

 

5 扩容

  首先,ArrayList定义了有一个数组容量的最大值 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8

  扩容方法 grow 的参数是 minCapacity,表示要存储当前数据所需要的最小容量

  oldCapacity 是数组的原始容量

  newCapacity 是新容量,默认是 oldCapacity的1.5倍

  还有两种特性情况:

  (1)  如果计算出来的 newCapacity ,小于传入参数 minCapacity,就使用 minCapacity 赋值给 newCapacity 

  (2)  如果 newCapacity 大于 MAX_ARRAY_SIZE 时,就调用 hugeCapacity 方法判断新容量。如果 minCapacity 大于 MAX_ARRAY_SIZE 就返回 Integer.MAX_VALUE,否则返回 MAX_ARRAY_SIZE 

 

  结论:ArrayList扩容时,默认会扩容到原始容量的1.5倍;如果原始容量的1.5倍小于参数minCapacity,就按照 minCapacity 的值扩容; ArrayList的扩容最大值到 Integer.MAX_VALUE

 

    /**
     * 限制ArrayList的数组容量最大值
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * 增加容量,以确保它至少能容纳最小容量参数指定的元素数量。
     *
     * @param minCapacity 最小容量
     */
    private void grow(int minCapacity) {
        // oldCapacity是数组的原始容量
        int oldCapacity = elementData.length;
        // newCapacity是新的容量,是原始容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果计算出来的newCapacity ,小于传入参数的minCapacity,就使用minCapacity 赋值给 newCapacity 
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 如果 newCapacity 大于 MAX_ARRAY_SIZE时,使用minCapacity判断新容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 复制数组,把原始数据复制到newCapacity容量的新数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // 参数小于0时,抛异常
            throw new OutOfMemoryError();
        // minCapacity 大于 MAX_ARRAY_SIZE时,返回Integer.MAX_VALUE, 否则返回 MAX_ARRAY_SIZE
        return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
    }

 

posted @ 2022-06-28 11:26  闲人鹤  阅读(96)  评论(0编辑  收藏  举报