arraylist源码分析

1、前言
说到ArrayList,就不得不说Array。光看名字,还以为这2个是同一个东西。其实不然。
Array:指容量为固定的数组,常见的初始化方法如下:

String[] names = {"david","tom","kate"};

在声明的时候直接声明了内部元素,这样jvm就可以快速的分配给指定大小的空间。同时,看Array源码可知,Array的方法基本上都是native方法,其底层实现均为c/c++实现。

ArrayList:动态数组,允许新增、删除等操作,常见的初始化方法:

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

看完概念,我们开始正题,开始源码的解读。

2、与其他容器对比

与hashmap不同,ArrayList为集合的一种,来源自数据结构中的数组概念,与hashmap源自数据结构中的图概念不同。

我们先看ArrayList的uml图。

image

可以看出,ArrayList不仅实现了Cloneable、Serializable接口,还 实现了RandomAccess接口、List接口。

3、简述RandomAccess接口
在后续代码中,还可以看到RandomAccess这个接口。若实现了该接口,则表明该类可以进行下标式访问,类似于这样:

List<String> nameList = new ArrayList<>();
String david  = names[0];

(这个会提示越界了)

4、成员变量分析
ArrayList的成员变量分析如下:

//默认容量
private static final int DEFAULT_CAPACITY = 10;
//空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//也是空数组,作用是在新增元素的时候用
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//数组
transient Object[] elementData;
//数组大小
private int size;
//数组最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//继承自AbstractList,用于fail-fast机制
protected transient int modCount = 0;

从成员变量来看,数组用transient修饰符修饰,作用应该是类似于hashmap的Node[]。

5、核心方法分析

5.1 容量扩增

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //新空间分配直接扩大50%
        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:
        //元素复制
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

private static int hugeCapacity(int minCapacity) {
        //这一点比较特殊,见下面分析
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

关于hugeCapacity的判断小于0则为溢出,由于在jvm内部是以反码存储的数据,首位为符号位,当容量扩增后,若溢出,首位则变为1,此时变为负数,则可以快速判断出是否溢出。

5.2 trimToSize 压缩空间

去掉多余的空对象,精简存储空间

public void trimToSize() {
        modCount++;
        //代码也写的很简洁,经常使用三元表达式
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

由上面的扩增容量可知,如果原始容量是100,在扩增容量后,那么分配的容量为150.但是实际上可能只存110个对象实例,那么此时调用这个方法,就可以节约一定的存储空间。不过若数组较大,那么操作可能会耗费一点时间。

5.3 fail-fast机制
这个其实在hashmap中也采取了类似机制,就是额外有一个成员变量,用于快速判断该实例是否有变化,若在进行迭代的时候有变更,那么就抛出一个并发修改异常(ConcurrentModificationException)。

5.4 indexOf 求 下标

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;
}

这段代码略过,用的比较多,没什么亮点。

5.5 新增一个元素

既然是数组,那么就有两种 新增的方式:指定特定的位置、向后插入。

向指定位置写入元素:

public void add(int index, E element) {
        //下标检查,是否越界了
        rangeCheckForAdd(index);
        //扩增容量,同时改变modcount
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //index后面的元素后移
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        //指定位置放置元素
        elementData[index] = element;
        //元素数量大小自增
        size++;
    }

向后插入一个元素:

public boolean add(E e) {
        //扩大容量,修改modcount
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //注意
        //数组是从0开始的存元素的,而数组个数是从1开始计数的
        //这个地方是往第size个位置上存元素
        //再将元素个数加1
        elementData[size++] = e;
        return true;
    }

从以上源码可以看出,向后插入一个元素不用进行元素的复制,自然效率要大于指定位置插入一个元素。

5.6 移除一个元素
同新增,也是分2种。
移除指定位置的元素:

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);
        //这个与新增类似,但是是左自减运算,自己体会吧
        //特地表明将该位置的置空,让gc回收空间
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

删除指定元素:

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

//类似指定位置删除元素
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; // clear to let GC do its work
}

看到这个指定删除某个元素的代码,那么有人就会有疑问了,如果我里面有多个一样的元素,那这不是删不完吗?这个时候你得使用removeAll:

public boolean removeAll(Collection<?> c) {
    //参数校验,仅判断c != null,感觉不太严谨
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}

private boolean batchRemove(Collection<?> c, boolean complement) {
    //常量数组,不允许再赋值,但是数组内部的元素允许自由移动、被重新赋值
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++)
            //不包含该元素,则存到新数组,其实是位置前移
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        //上面if内类型不匹配抛异常时,r与size不等
        if (r != size) {
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) {
            // clear to let GC do its work
            //由于将已包含的元素前移了,那么不包含的元素都在后面,直接将后面的元素删掉
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}

5.7 序列化
这个源码类似于hashmap,就不重复分析了。

5.8 其他
set、get方法比较简单, 就不分析了。
需要注意的是,arraylist继承的AbstractList覆写了hashcode和equals方法。

6 小结
总的来说,arraylist的源码比较简单,可供分析的内容不多。

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

推荐阅读更多精彩内容

  • ArrayList是在Java中最常用的集合之一,其本质上可以当做是一个可扩容的数组,可以添加重复的数据,也支持随...
    ShawnIsACoder阅读 570评论 4 7
  • 前言 跟着我往下看(这里只讲顺序存储方式的线性表): 不明白?继续往下看 下面开始进入正题:ArrayList就是...
    小朱v阅读 441评论 0 1
  • 以下源码基于 Android SDK 23, 与JDK中略有差别,但基本相同;整体源码由 构造、添加(add)、设...
    Jacky_Y阅读 438评论 0 3
  • ArrayList 原文见:Java 容器源码分析之 ArrayList 概述 ArrayList是使用频率最高的...
    Leocat阅读 226评论 0 0
  • ArrayList概述 arrayList的类图如下: 类结构说明: 实现Iterable接口,说明该目标对象允许...
    blueSkyBird阅读 353评论 0 1