Java集合框架—ArrayList—扩容原理底层源码

3.jpg

Java.util.ArrayList是Java集合中最常用的类,也是Java开发中最常用的类之一。本篇基于JDK9,从ArrayList的基本用法开始,以源码中add()方法的完整实现过程,来分析ArrayList扩容原理的实现。

ArrayList的初始化

1.普通ArrayList,可以装任意Object对象。

List list = new ArrayList();

2.泛型类ArrayList,装指定类型的对象,如String类:

List<String> list = new ArrayList<>();

初始化时可以设置数组容量:

List list = new ArrayList(10);List<String> list = new ArrayList<>(10);

也可以直接初始化赋值:

List<String> list = new ArrayList<>(){{add("A");add("B");add("C");}};

List<String> list = new ArrayList<>(Arrays.*asList*("A", "B", "C"));

ArrayList的增删改查及常用方法

1.添加元素add()

List<String> list = new ArrayList<>();

add有两种重载的方法,分别是直接添加元素和给下标为index的位置添加元素。

list.add("A");         //此时list中有一个String对象:A
list.add(1,8);        //给下标为1的位置添加Integer对象:8

注意,要是给不存在的下标位置添加元素,如list.add(3,8);则会引发异常:IndexOutOfBoundsException表示下标越界;如果list.add(index,obj);index下标对应的位置已有对象,则此对象及之后的对象全部依次后移一位。

代码如下:

image

运行结果:

新建一个ArrayList:
[A, B, C]
[A, D, B, C]

2.删除元素remove()

remove和add类似,也有两种重载的方法,分别是直接删除和根据下标index删除

List list = new ArrayList(Arrays.asList("A","B","C","B","C"));

此时数组中元素为A,B,C,B,C,此时list.remove("C")会移除第一个匹配到的"C"

remove()后的数组:

A,B,B,C

再进行list.remove(1),数组变为:

A,B,C

同样有一点需要注意:如果移除不存在的元素,并不会报错;但是移除不存在的下标,如
list.remove(99)则也会报IndexOutOfBoundsException异常。

代码如下:

image

运行结果:

初始ArrayList:[A, B, C, B, C]
[A, B, B, C]
[A, B, C]

3.查看元素get()

查看元素只有一种方法:get(index),即通过下标来查看对应的元素,如果下标不存在也会报IndexOutOfBoundsException异常。

代码如下:

image

运行结果:

初始ArrayList:[A, B, C]
get(1)查看'1'号元素:B

4.修改/重置元素set

set()和get()一样,只有一种方式,即通过下标index来修改相应位置元素的值。

代码如下:

image

运行结果:

初始ArrayList:[A, D, C]
[A, B, C]

5.除了get,set,add和remove外,ArrayList还要很多常用方法:

size()返回元素个数,addAll()合并另一个集合类,toArray()将ArrayLIst转化为Object数组,

indexOf()返回某个元素第一次出现的位置下标index,subList()截取指定下标范围的子List。

代码如下:

image

运行结果:

la.toString():[A, B, C]
la.addAll(1,lb):[A, B, C, D, B, C]
la.toArray():[Ljava.lang.Object;@3f0ee7cb
ABCDBCla.indexOf("B"):1
la.lastIndexOf("B"):4
la.subList(0,3):[A, B, C]

ArrayList从源码看扩容实现:

首先,看一下ArrayList的源码定义:

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

ArrayList类继承自抽象类AbstractList,实现了List接口,随机存取RandomAccess接口,克隆接口Cloneable,序列化接口Serializable。

通常,我们使用Arraylist添加一个对象往往无需过多考虑,直接add()即可,无需担心其容量是否超过限制,其原因就在于Arraylist源码中,实现add()方法之前会查看其容量是否够用,不够则会自动‘扩容’然后再执行add()添加元素,即自动扩容。

让我们从一个空的数组列表添加元素开始,看一下源码是如何实现的。

List list = new ArrayList();
list.add("A");

首先,List list = new ArrayList();初始化了一个空数组列表list,具体是ArrayList类源码中:

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

初始化ArrayList对象的同时创建了一个默认为空的Object数组变量elementData。然后list.add("A")对应的是源码中的add()方法:

image

如图中所示,public boolean add(E e){}方法会执行modCount++;然后调用add(e,elementData,size)方法,最后返回布尔类型的ture。

modCount在ArrayList中定义如下:

protected transient int modCount = 0;

doc说明:

/**
** The number of times this list has been <i>structurally modified</i>.*
** Structural modifications are those that change the size of the*
** list, or otherwise perturb it in such a fashion that iterations in*
** progress may yield incorrect results....*

很多结构化地改变数组的操作都会用到modCount计数,用来标识数组被修改的次数。譬如add , set , remove等。

由于是初次添加元素,故modCount++后变为1,来到下一步add(e, elementData, size),此时e为要添加的元素:“A”,elementData为刚刚创建的Object[], size为数组初始容量,值为0(在ArrayList中定义:private int size,int型初始未赋值则为0)

重点看下add方法的具体实现:

private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
}

由于初始elementData.length==size==0故执行grow()方法,返回值赋给elementData。

private Object[] grow() {
        return grow(size + 1);
}

grow有两个重载的方法,无参的grow()会调用有参的grow,参数为size+1。

private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

此处size+1=1作为minCapacity,表示最小容量,实际也很好理解,因为add一个对象首先我需要的最小容量是原长度+1,有了最小容量才能进行下一步地添加元素操作,所以通过grow()来确保我arraylist对象的长度,即【扩容】

扩容的操作通过Arrays.copyOf方法实现:

Arrays.copyOf(elementData,newCapacity(minCapacity))

此方法直接将原数组elementData复制到一个新的给定长度的数组中,并返回此新数组对象。

那么新数组长度是多少呢?这个就是newCapacity()中定义的,让我们看一下:

private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
}

http://1.int oldCapacity = elementData.length

此处由于elementData是空数组,所以oldCapacity==elementData.length==0

2.newCapacity = oldCapacity + (oldCapacity >> 1)

这一步是扩容的核心操作,即扩容后新数组的长度=原数组的容量+原数组的容量/2.

通过oldCapacity >> 1(Java移位运算,右移1位相当于除以2)来实现扩容原数组容量的一半,即ArrayList的扩容是根据原容量的1.5倍来扩容的。

但是此时oldCapacity值为0故newCapacity也为0,扩容无效...

3.if(newCapacity - minCapacity <=0)

经过第二步后的newCapacity=0,小于minCapacity=1故进入下面的步骤:

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
    return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
    throw new OutOfMemoryError();
return minCapacity;

4.return Math.max(DEFAULT_CAPACITY, minCapacity);

DEFAULT_CAPACITY为ArrayList中初始的默认容量,大小为10。定义如下:

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

return Math.max(DEFAULT_CAPACITY, minCapacity)返回10和minCapacity中的最大值,此处直接返回10

现在,经过newCapacity()计算后的新数组容量为10,让我们回到grow()中的:

return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity))

Arrays.copyOf将elementData复制到一个容量为10的新Object[]中去,并返回此数组对象elementData,完成了前面一大堆准备工作,并执行扩容后,终于来到了add()方法中的最后两部:

elementData[s] = e;

完成新增元素添加

size = s + 1;

将该ArrayList对象的数量+1

源码部分方法截图如下:

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容