前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从源码上分析 ArrayList

从源码上分析 ArrayList

作者头像
一份执着✘
发布2018-06-04 17:32:35
3820
发布2018-06-04 17:32:35
举报
文章被收录于专栏:赵俊的Java专栏赵俊的Java专栏

前言

ArrayListList 接口的一个实现类,那么 ArrayList 的底层是如何实现的呢?让我们来一探究竟。

源码分析

属性

先来看看 ArrayList 中比较重要的两个属性:

代码语言:javascript
复制
transient Object[] elementData; 

private int size;

  • elementData 用来存储 ArrayList 中的元素,其实 ArrayList 的底层是用 Object[] 数组来实现的。
  • size 指的是的逻辑长度,就好像一个水杯,容量是 600 毫升,但杯中只有 200 毫升的水,ArrayList 容器就是水杯, 这个 size 属性表示的就是水杯中水的容积。

构造方法

无参构造方法:

123

public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

很简单,就一行语句,elementData 在上面我们已经知道是什么了,那 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 又是什么呢?

1

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

原来只是一个空的 Object[]

指定容量的构造方法:

代码语言:javascript
复制
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);
    }
}

也很简单,就是为 ArrayList 指定初始容量。这里主要对传入的容量做了有效性验证,当传入的参数小于 0 时,抛出异常。当参数等于 0 时,则用一个空的数组常量 EMPTY_ELEMENTDATA 来初始化。

用一个 Collection 对象进行构造的的构造方法:

代码语言:javascript
复制
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;
    }
}

首先将传入的参数转为对象数组赋值给 elementData ,如果传入的 Collection 参数的长度为 0,则就将空的常量数组对象 EMPTY_ELEMENTDATA 赋给了 elementData

常用方法

add
代码语言:javascript
复制
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

调用了 ensureCapacityInternal 函数:

代码语言:javascript
复制
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

这里判断 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 表示如果如果为没有初始化容量时,用默认容量 DEFAULT_CAPACITY 也就是 10,来开启空间,也就是上面我们说的水杯的容量。

接下来看 ensureExplicitCapacity 方法:

代码语言:javascript
复制
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

继续看 grow 方法:

代码语言:javascript
复制
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    
    // 对 ArrayList 容量进行 1.5 倍扩容。
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    // 如果扩容后的容量还不够实际占用的容量,那么需要扩容到实际占用容量的大小。
    // 如当前 newCapacity 为 10,扩容后为 15,
    // 但现在实际的元素数量为 16 个,那么至少应该先装下这 16 个。
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    
    // 防止阔中的过大,过大时,则使用 Integer.MAX_VALUE 代替。
    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);
}

其实这段代码也很简单,就是动态扩容 ArrayList 的大小,每次扩容都为当前容量的 1.5 倍,当然也进行了扩容过大或过小的限制。 其扩容原理就是创建一个容量为当前容量 1.5 倍的新数组,将旧数组的内容拷贝给新数组。

add(int index, E element)

代码语言:javascript
复制
public void add(int index, E element) {
    // 判断要插入的位置不小于 0 且不大于当前最大位置
    rangeCheckForAdd(index);
    
    // 容量扩容检测,具体参见 add(E e) 方法
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

看过了 add(E e) 的源码,这里也就很好理解了,先进行插入位置有效性判断,然后判断容量是否需要扩容,然后先将插入位置开始的所有元素向后移动一位,最后将需要插入的元素插入指定位置。

get
代码语言:javascript
复制
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

ArrayList 的底层是用数组实现的,那么获取元素也就很方便了,判断获取位置合法后,直接用下标获取即可。

set
代码语言:javascript
复制
public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

这个方法其实就是修改指定位置的元素,先拿到要覆盖的元素,然后将新元素赋值上去,最后返回覆盖的元素即可。

remove
代码语言:javascript
复制
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;
}

先进行有效性判断,然后从要删除的元素开始的所有元素都向前拷贝一位,并将最后一位设值为 null

indexOf
代码语言:javascript
复制
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;
}

如果要找的内容 null,则依次用 == 判断是否为要找的元素,如果不为 null,则用 equals 来依次判断。

总结

根据上方的源码分析,我们可以得出 ArrayList 的一些特性:

  • ArrayList 底层数据结构是对象数组,如不指定长度,则初始容量为 10。
  • 底层的对象数组是在第一次添加元素的时候才进行初始化的。
  • 每次扩容为原来容量的 1.5 倍,扩容原理是将原来的数据拷贝到新数组中。

所以我们在使用时要注意:

  • 如知道大概要存多少个数据,最好指定初始化容量,这样可以提高程序性能。
  • ArrayList 查找元素很快,但删除元素和添加元素的效率相对较差。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-01-262,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 源码分析
    • 属性
      • 构造方法
        • 常用方法
          • add
          • get
          • set
          • remove
          • indexOf
      • 总结
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档