IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> ArrayList源码详解 -> 正文阅读

[数据结构与算法]ArrayList源码详解

ArrayList源码详解

ArrayList作为开发最最常用的容器,了解并掌握其底层原理是每个开发人员必备的技能。

一、关键属性

/**
 * Default initial capacity.
 * 默认初始化容量
 */
private static final int DEFAULT_CAPACITY = 10;



/**
 * Shared empty array instance used for empty instances.
 * 空数组,用于空的实例化
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
* 空数组,用于空的实例化
* 你可能在想这个不是和 EMPTY_ELEMENTDATA  一样了吗
* 是的,它们的值是一样的,但是意义不大一样。
* 当我们使用 new ArrayList<>(); 创建容器的时候,使用的是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 
* 其目的在于:
* we distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
* 意思就是让我了解,以这种方式创建的容器,添加第一个元素的时候,到底做了哪些事情(后面会说明)。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
* 存数据的。官方有个说明:

 * Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
* 
*/
transient Object[] elementData;

了解了以上属性,我们就可以回答面试官最喜欢问的面试问题了:

ArrayList的底层数据结构是什么?

显然嘛,数组。

初始化容量大小?

很多人说是10,这其实是错误的,看过源码就会知道,初始化容量是0。在新增第一个元素的时候进行扩容,容量扩容为10。这也是这个属性DEFAULTCAPACITY_EMPTY_ELEMENTDATA 存在的意义。

二、容器初始化

ArrayList 底层数据结构是数组,这个想必大家都知道。
初始化方式:

/**
 * 三种构造方法
 * 无参构造
 * 指定容量构造
 * 传入Collection 容器构造
 * 在我们能够得知容量具体大小的时候,建议指定容量大小。不然频繁的扩容还是很浪费时间的
 */
List<Integer> list = new ArrayList<>();

1. 无参构造

new ArrayList<>(); 源码很简单。

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

2. 指定容量大小

也不是很复杂

/**
 * Constructs an empty list with the specified initial capacity.
 *
 * @param  initialCapacity  the initial capacity of the list
 * @throws IllegalArgumentException if the specified initial capacity
 *         is negative
 */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        // 入参大于零 初始化数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 等于0 默认{}
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        // 小于0 显然不行的啊 
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
  1. 容器参数
/**
 * Constructs a list containing the elements of the specified
 * collection, in the order they are returned by the collection's
 * iterator.
 *
 * @param c the collection whose elements are to be placed into this list
 * @throws NullPointerException if the specified collection is null
 */
public ArrayList(Collection<? extends E> c) {
    // 集合转为数组 不会有人传null吧 
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // 很多人都疑惑这一步的操作目的。问题出现在了  c.toArray();这一步,如果集合
        //  c.toArray()被重写了返回的就不一定是 Object[].class 了,这样就不是一种
        // 数据类型了。现在明白了吧。
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

三、核心add方法

整体流程图:

在这里插入图片描述

add()有几个重载方法,这里只对核心的add(E e)方法进行详细的介绍。
源码很简单三步:

  1. 确保容量
  2. 添加值
  3. 返回true
public boolean add(E e) {
    // 确保容量可以进行添加
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // size 当前容量,也就是在容器最后添加元素,前提容量有这么大。所以第一步就是确保
    // 容量
    elementData[size++] = e;
    return true;
}

1. ensureCapacityInternal() 方法

作用:确保容量满足要求,不满足扩容

private void ensureCapacityInternal(int minCapacity) {
    //字面意思 确保明确的容量
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 计算容量 
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 如果是空的化 返回 默认容量10 和最小容量中的最大值
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

    //字面意思 确保明确的容量
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        // 容量不够 扩容 
        grow(minCapacity);
}

2. grow(int minCapacity) 扩容方法

private void grow(int minCapacity) {
    // overflow-conscious code
    // 获取旧容量
    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);
    // minCapacity is usually close to size, so this is a win:
    // 数据copy
    elementData = Arrays.copyOf(elementData, newCapacity);
}

添加元素的流程结束了,是不是很简单。

四、核心get方法

get方法很简单,不用看源码应该就可以猜出来。get(下标) 不就是 elementData[下标] 嘛

/**
* 果然如此
*/
public E get(int index) {
    //范围检查
    rangeCheck(index);
    //获取数据
    return elementData(index);
}

五、核心remove 方法

remove 方法有不少重载方法。常用的是根据下标移除和根据元素移除。根据元素溢出,本质上还是根据下标移除,需要先定位到下标,再根据下标移除。这里只针对根据下标移除进行详细介绍。

public E remove(int index) {    
    // 范围检查
    rangeCheck(index);
    // 修改次数
    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
       // 删除(数组copy)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // GC 清理
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

System.arraycopy()方法

此方法常用于数组copy,数组扩容,数组根据指定下标删除。

/**
* @param      src      the source array. 原数组
* @param      srcPos   starting position in the source array.  开始位置
* @param      dest     the destination array.  目标数组
* @param      destPos  starting position in the destination data. 目标数组开始位置
* @param      length   the number of array elements to be copied. copy数量
*/
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

六、 contains() 方法

判断是否包含指定元素,主要是遍历数组,对一个元素一个元素进行判断。

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}



public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

七、总结

ArrayList 容器算是比较简单的容器,理解起来不是很困难,很适合源码阅读入门。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-05-06 11:13:30  更:2022-05-06 11:13:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 5:29:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码