ArrayList源码分析

1 先看属性

//默认容量
private static final int DEFAULT_CAPACITY = 10;

//当用户指定ArrayList容量为0时,返回该空数组
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * 当用户没有指定ArrayList的容量时,返回的是该数组
 * 当与EMPTY_ELEMENTDATA的区别就是:该数组是默认返回的,而后者是在用户指定容量为0时返回的
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * 当用户没有指定ArrayList的容量时,返回的是该数组
 * 当该数组为DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,首次元素添加到ArrayList中时,数组将 
 * 扩容至DEFAULT_CAPACITY大小
 */
transient Object[] elementData;

/**
 * ArrayList实际存储的数据数量
 */
private int size;

2 构造方法

2.1 ArrayList(int initialCapacity)

构造一个具有指定初始容量的空列表

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        //如果从参数大于0,创建对应大小的数组。
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        //如果等于0,返回EMPTY_ELEMENTDATA。
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        //如果是负数,抛异常。
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

2.2 ArrayList()

/**
 * 无参构造函数
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

2.3 ArrayList(Collection<? extends E> c)

public ArrayList(Collection<? extends E> c) {
    //将集合转换为Object[]数组
    elementData = c.toArray();
    //把转化后的数组长度赋值给size属性,并判断是否为0
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        //这句话的意思是:c.toArray可能不会返回Object[],可以查看java官方编号为6260652的bug
        if (elementData.getClass() != Object[].class)
            //若c.toArray()返回的数组类型不是Object[],则利用Arrays.copyOf来构造一个大小为size的Object[]数组
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        //如果为0替换空数组,防止创建过多的空数组
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

3 方法

3.1 boolean add(E e)

将指定的元素添加到此列表的尾部

public boolean add(E e) {
    //将size + 1传递到这个方法,保证资源不浪费
    ensureCapacityInternal(size + 1);  // Increments modCount!!()
    //size存放的是长度,不是下标,先利用原来size修改下标值,再将size++
    elementData[size++] = e;
    return true;
}
/**
 * 扩容方法(后面会经常见到)
 */
private void ensureCapacityInternal(int minCapacity) {
    //如果elementData是空数组,则取默认容量和minCapacity的最大值
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
    // 将“修改统计数”+1,该变量主要是用来实现fail-fast机制的
    modCount++;

    // overflow-conscious code
    //防止溢出代码,确保指定的最小容量 > 数组缓冲区当前的长度
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
/**
 * 扩容,确保minCapacity至少能存储minCapacity个元素
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    //运算符>>是带符号右移,如果oldCapacity = 2, newCapacity = 2 + (2 >> 1) = 2 + 1 = 3
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //如果newCapacity依旧小于minCapacity则将minCapacity赋值给newCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //如果newCapacity大于最大容量,则直接分配Integer.MAX_VALUE
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    //Arrays.copyOf():将原来的数组扩容到newCapacity大小,扩容的部分用null填充
    elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
 * 这个比较简单,如果是负数则报错,传入的参数大于最大容量返回int最大值,否则返回最大容量。
 */
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

3.2 void add(int index, E e)

将指定的元素插入此列表中的指定位置。

public void add(int index, E element) {
    //判断角标是否越界(看下面的比较的方法,意思就是角标最大是size,也就是当前最大下标 + 1,
    //如果index == size,那就跟add(E e)效果一样)
    rangeCheckForAdd(index);
    //这个方法上面有
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //这个方法的意思是将原来的数组下标从index到最后,往后都移动一位
    //比如index是2,原数组是[1, 2, 3, 4, null] 执行完后 [1, 2, 3, 3, 4]
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    //修改数组下标为index的值
    elementData[index] = element;
    size++;
}
/**
 * 添加时检查索引是否越界
 */
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

3.3 boolean addAll(Collection<? extends E> c)

按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。

public boolean addAll(Collection<? extends E> c) {
    //将集合类变成数组
    Object[] a = c.toArray();
    //获取数组大小(这个大小也是扩容的大小,同时是System.arraycopy()的长度)
    int numNew = a.length;
    //扩容
    ensureCapacityInternal(size + numNew);  // Increments modCount
    //将数组a复制到现有数组,
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

3.4 boolean addAll(int index, Collection<? extends E> c)

从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。

public boolean addAll(int index, Collection<? extends E> c) {
    //检查index是否越界
    rangeCheckForAdd(index);
    
    //转换成数组
    Object[] a = c.toArray();
    int numNew = a.length;
    //扩容
    ensureCapacityInternal(size + numNew);  // Increments modCount

    //这个值是需要移动的长度
    int numMoved = size - index;
    //判断是否是从末尾插入
    if (numMoved > 0)
        //将原数组的index位置到最后的数组移动,预留出来numNew长度
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);

    //将数组a从下标0开始,插入到原数组,原数组从index开始插入,长度为a.length
    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

3.5 void clear()

移除此列表中的所有元素。

public void clear() {
    modCount++;

    // clear to let GC do its work
    //循环把数组中的每个地址都指向null,gc会把每个对象进行回收
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    //size置为0,但其实数组长度还是那么多
    size = 0;
}

3.5 boolean contains(Object o)

public boolean contains(Object o) {
    //结果大于0返回true
    return indexOf(o) >= 0;
}
/**
 * 循环比较数组中每个元素的值,返回列表中首次出现的指定元素的下标,如果此列表不包含则返回-1
 */
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;
}

3.6 E get(int index)

返回此列表中指定位置上的元素。

public E get(int index) {
    //检查数组下标是否越界
    rangeCheck(index);

    return elementData(index);
}
@SuppressWarnings("unchecked")
E elementData(int index) {
    //返回数组中对应下标元素
    return (E) elementData[index];
}

3.7 int indexOf(Object o)

返回列表中首次出现的指定元素的下标,如果此列表不包含则返回-1(其实contains就是利用了该方法)

/**
 * 循环比较数组中每个元素的值,返回列表中首次出现的指定元素的下标,如果此列表不包含则返回-1
 */
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;
}

3.8 int lastIndexOf(Object o)

返回此列表中最后一次出现的指定元素的索引,或如果此列表不包含索引,则返回 -1。

/**
 * 换汤不换药,从后往前遍历
 */
public int lastIndexOf(Object o) {
    if (o == null) {
        for (int i = size-1; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = size-1; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

3.9 E remove(int index)

移除此列表中指定位置上的元素。

public E remove(int index) {
    //判断下标是否越界
    rangeCheck(index);

    modCount++;
    //获取被移除的元素,最后返回用
    E oldValue = elementData(index);

    //判断移除的元素是否是数组中最后一个元素
    int numMoved = size - index - 1;
    if (numMoved > 0)
        //如果不是,则将原数组index位置之后的元素都往前移动
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //将最后一个元素设置为null,size--
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

3.10 boolean remove(Object o)

移除此列表中首次出现的指定元素(如果存在)。

public boolean remove(Object o) {
    //如果需要移除的值是null,那么就循环数组通过==null比较。
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    //如果移除的值是对象,那么就循环数组通过equals进行比较。(这也是为什么说写一个
    //类要重写equals方法,因为默认的equals比较的是地址,如果不重写这种集合类的方法就会失效)
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
/**
 * 私有的remove方法,与remove方法相比,没有边界值检查并且不用返回删除的值
 */
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
}

3.10 E set(int index, E element)

用指定的元素替代此列表中指定位置上的元素。(这个注释就不一行一行加了,相信看到这里大家就都能看懂了)

public E set(int index, E element) {
    rangeCheck(index);

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

推荐阅读更多精彩内容

  • 深入ArrayList源码分析(JDK1.8) Java 集合系列源码分析文章: 深入TreeMap源码解析(JD...
    一角钱技术阅读 223评论 0 0
  • 其实我看到已有不少大佬写过此类文章,而且写的也比较清晰明了,那我为什么要再写一遍呢?其实也是为了加深自己的印象,巩...
    善良的小黑哥阅读 137评论 0 0
  • 关于List与ArrayList,在文档中是这么说明的List:有序集合(也称为序列 )。 该界面的用户可以精确控...
    haloSky_life阅读 175评论 0 0
  • 一、概述 本文基于 JDK8 ArrayList 底层通过动态数组的数据结构实现 内存需要连续的空间保证 添加操作...
    hncboy阅读 35评论 0 0
  • List List是一个维持内部元素有序的采集器,其中的每个元素都会拥有一个索引,每个元素都可以通过他的索引获取到...
    dooze阅读 385评论 0 4