ArrayList核心源码解析

一、基本方法解析

1、构造器

ArrayList有三种创建方式

ArrayList的底层实现方式为数组,在创建ArrayList时,底层会创建数组对象Object[]

1.1 无参构造器


private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

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

通过这种方式创建ArrayList,底层对创建一个空的数据,这个数组对象是 static final 类型的,即该数组对象在初始化的是否就会被创建,并且不能够被改变。

1.2 自带数组长度的的构造方式

private static final Object[] EMPTY_ELEMENTDATA = {};

transient Object[] 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);
    }
}

不难看出,这种方式在创建ArrayList对象时,会进行一系列的判断,根据你传入的参数不同,进行不同的构造方式。

  • ① 当传入的数组长度值大于0时,会创建一个特定长度的数组对象。
  • ② 当传入的数组长度值等于0时,会创建一个空的Object数组对象,基本上跟无参的构造方法是一样。
  • ③ 第三种情况就是你传入的参数是非法参数,那么,会抛出异常“Illegal Capacity: initialCapacity”。

这里要特别注意的是,elementData数组对象被transient所描述,因为Arraylist实现了java.io.Serializable接口,即在序列化的时候,Arraylist会使用Serializable的默认序列化方式,而被transient关键字修饰的属性会在序列化时被忽略,不进行序列化。

1.3 传入一个集合Collection对象,构造ArrayList对象

transient Object[] 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()、addAll()

Arraylist提供了四种的添加新增元素的方式,两种add(),以及两种addAll()。

2.1 直接在集合的结尾添加元素:add(E e)

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

在这个过程中,会做一下的操作:

  • ① 判断当前ArrayList对象的 elementData 属性是否为空,如果为空,则判断当前你要传入的集合长度 size+1 和会提供的默认集合长度10的大小,拿较大的一个值作为集合长度。如果 elementData 不为空,则直接拿传入时的 size+1 作为当前集合的长度(size是在添加当前数据之前集合的长度).

核心代码1:


private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 判断当前集合的属性elementData是否为空
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // DEFAULT_CAPACITY 默认为 10,取较大的一个值
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // 判断新的集合长度与数组长度的大小
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

  • 把获取到的集合的长度与当前数组elementData的长度进行比较,如果当前集合的长度小于其属性对象elementData的长度,则直接在当前数组中数据的后面添加数据;如果当前集合的长度大于其属性数组对象elementData的长度,则需要增加数组的长度(添加规则:在原有的数组长度的基础上在增加一半,作为一个新的数组长度,在判断新的长度与当前集合的长度大小,如果新的数组长度依然比集合长度小,则直接把集合长度作为数组长度,否则,用新的数组长度。然后再把这个新的数组长度与可分配的数组的最大大小进行比较,得到最终的新的数组大小),然后根据得到最新的数组长度创建一个新的数组,把elementData中的数组copy到其中,再将这个新的数组赋值给elementData,然后把要添加的元素添加进去。

核心代码2:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
    // 获取当前的数组长度
    int oldCapacity = elementData.length;
    // 新的数组长度为:原来的数组长度 + 原来数组长度的1/2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 比较新的数组长度与新的集合长度的大小
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 比较新的数组长度与jvm所允许的最大数组长度    
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 创建一个新的数组,将原来的数组信息添加进去,然后再赋值给elementData
    elementData = Arrays.copyOf(elementData, newCapacity);
}

2.2 将某个元素添加到集合的指定位置

public void add(int index, E element) {
    // 首先判断当前传入的位置是否合法,即在0-size之间
    rangeCheckForAdd(index);

    // 判断是否需要添加数组长度,此处的操作同上add()中的操作
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 用System.arraycopy()方法将数组index-1之后的所有数据往后移动一位
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 在index的位置加入新的元素
    elementData[index] = element;
    // 更新size
    size++;
}

private void rangeCheckForAdd(int index) {
     if (index > size || index < 0)
         throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

基本错作流程如下:

  • ① 首先判断当前传入的位置是否合法,即在0-size之间
  • ② 判断是否需要添加数组长度,此处的操作同上add()中的操作
  • ③ 用System.arraycopy()方法将数组index-1之后的所有数据往后移动一位
  • ④ 在index的位置加入新的元素
  • ⑤ 更新size

2.3 将整个Collection对象中所有的元素添加到ArrayList中

public boolean addAll(Collection<? extends E> c) {
    // 通过toArray()方法将Collection转化为数组a
    Object[] a = c.toArray();
    // 获取传入的集合的长度
    int numNew = a.length;
    // 把 size + numNew 作为新的集合长度,进行数组的处理,同add()方法中的操作
    ensureCapacityInternal(size + numNew);  // Increments modCount
    // 使用System.arraycopy()方法将传入的数据信息copy到 elementData 中
    System.arraycopy(a, 0, elementData, size, numNew);
    // 更新size
    size += numNew;
    return numNew != 0;
}

基本操作流程如下:

  • ① 通过toArray()方法将Collection转化为数组a
  • ② 获取传入的集合的长度
  • ③ 把 size + numNew 作为新的集合长度,进行数组的处理,同add()方法中的操作
  • ④ 使用System.arraycopy()方法将传入的数据信息copy到 elementData 中
  • ⑤ 更新size

2.4 将整个Collection对性中的元素从特定位置开始全部加入到Arraylist

public boolean addAll(int index, Collection<? extends E> c) {
    // 首先判断当前传入的位置是否合法,即在0-size之间
    rangeCheckForAdd(index);
    // 通过toArray()方法将Collection转化为数组a
    Object[] a = c.toArray();
    // 获取传入的集合的长度
    int numNew = a.length;
    // 把 size + numNew 作为新的集合长度,进行数组的处理,同add()方法中的操作
    ensureCapacityInternal(size + numNew);  // Increments modCount

    // 通过System.arraycopy()方法将原本elementData中index-1之后的所有元素向后移动 numNew位
    int numMoved = size - index;
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);
    // 通过System.arraycopy()方法将传入数组中的数据添加到elementData中
    System.arraycopy(a, 0, elementData, index, numNew);
    // 跟新size的值
    size += numNew;
    return numNew != 0;
}

基本操作流程如下:

  • ① 首先判断当前传入的位置是否合法,即在0-size之间
  • ② 通过toArray()方法将Collection转化为数组a
  • ③ 获取传入的集合的长度
  • ④ 把 size + numNew 作为新的集合长度,进行数组的处理,同add()方法中的操作
  • ⑤ 通过System.arraycopy()方法将原本elementData中index-1之后的所有元素向后移动 numNew位
  • ⑥ 使用System.arraycopy()方法将传入的数据信息copy到 elementData 中
  • ⑦ 更新size
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容