Java集合框架学习---深入探究ArrayList源码(二)

接着Java集合框架学习---深入探究ArrayList源码(一)继续学习ArrayList源码。

  • ensureCapacity函数
//当ArrayList不处于默认状态时,才可拓展为大小小于DEFAULT_CAPACITY容量的数组
// 否则只有指定大小超过DEFAULT_CAPACITY时才进行扩展;
//这个方法是public,区别于ensureCapacityInternal,这个方法是在外部使用的;
 public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

这个函数的作用主要是在当ArrayList的容量不足以容纳当前元素时给它设置新的容量。即在有必要的时候增加ArrayList的容量以确保其能够容纳最小容量参数所指定的元素数量。其实在ArrayList类中还有几个私有方法与ensureCapaCity方法相互调用与配合才实现了ensureCapaCity对外发布的功能:

//ArrayList内部使用此方法来拓展容量
//但是假如说处于默认状态,拓展大小仍不能小于DEFAULT_CAPACITY
private void ensureCapacityInternal(int minCapacity) {    
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
//如果不处于默认状态,则调用ensureExplicitCapacity进行拓展
        ensureExplicitCapacity(minCapacity);
    }
private void ensureExplicitCapacity(int minCapacity) {
         modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/*将容量变为指定大小,如果溢出就会抛出OutOfMemoryError错误
  *但是通过使用grow方法可以打破MAX_VALUE的限制
*/
    private void grow(int minCapacity) {
        // 用oldCapacity保存原有的容量大小
        int oldCapacity = elementData.length;
        //将新的容量设置为原来容量的3/2,可以减少由于小幅度扩展带来的开销
        int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果newCapacity没有达到指定大小,则将其设定为指定大小
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
 /*如果newCapacity比MAX_ARRAY_SIZE(Integer.MAX_VALUE(2147483647)-8)
   *还大,则调用hugeCapacity方法给newCapacity重新赋值
*/
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 否则就直接将该ArrayList实例拓展
        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;
    }
  • contains函数
    public boolean contains(Object o)
    如果此列表中包含指定的元素,则返回 true。更确切地讲,当且仅当此列表包含至少一个满足 (o==null ? e==null : o.equals(e))的元素 e时,则返回 true。
public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

其实在contains内部是通过调用了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;
        }
//否则就返回-1
        return -1;
    }
  • indexOf函数
    public int indexOf(Object o)
    返回此列表中首次出现的指定元素的索引,或如果此列表不包含元素,则返回 -1(代码见contains函数部分)。

  • lastIndexOf函数
    public int lastIndexOf(Object o)
    返回此列表中最后一次出现的指定元素的索引,或如果此列表不包含索引,则返回 -1。更确切地讲,返回满足(o==null ? get(i)==null : o.equals(get(i)))
    的最高索引 i,如果不存在此类索引,则返回 -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;
    }
  • clone函数
    public Object clone()
    返回此ArrayList实例的浅表副本,即对此ArrayList实例进行浅层复制
 public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
  • toArray函数
    ArrayList中有两个toArray函数

1.public Object[] toArray():
按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组。由于该方法是分配一个新的数组,即此列表不维护对返回数组的任何引用,因而它将是安全的,调用者可以自由的修改返回的数组。需要注意的是,它通过调用Arrays中的copyOf函数来实现功能的。

public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
//Arrays中的copyOf函数
@SuppressWarnings("unchecked")
/*
  *复制指定的数组,截取或用 null 填充(如有必要),以使副本具有指定的长度
  *对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。对于在副本中有效而在原数组无效的所有索引,副本将包含 null。
  *当且仅当指定长度大于原数组的长度时,这些索引存在。所得数组和原数组属于完全相同的类。
*/    
public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
//被上面的 public static <T> T[] copyOf方法调用
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
//如果该数组存储的是Object类型的元素,就New一个Object类型的数组,否则就使用newInstance产生一个对应类型的数组
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
//Array中的public static Object newInstance(Class<?> componentType, int length)函数
public static Object newInstance(Class<?> componentType, int length)
        throws NegativeArraySizeException {
        return newArray(componentType, length);
    }
//

2.public <T> T[] toArray(T[] a):
按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。如果指定的数组能容纳列表,则将该列表返回此处。否则,将分配一个具有指定数组的运行时类型和此列表大小的新数组。如果指定的数组能容纳队列,并有剩余的空间(即数组的元素比队列多),那么会将数组中紧接 collection 尾部的元素设置为 null( 在调用者知道列表中不包含任何 null 元素时才能用此方法确定列表长度)。

 @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
//size为该ArrayList的size
        if (a.length < size)
            // 必须创建一个与参数类型相同的数组
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;//用来帮助调用者确定集合长度(只有在明确知道集合中没有null元素时才有用)
        return a;
    }
  • elementData方法(缺省方法)
    E elementData(int index):
    位置访问操作,返回列表中下标为index的元素的值
 @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }
  • get方法
    public E get(int index):
    返回此列表中指定位置上的元素(调用了上面的elementData方法).
public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

可以看到,get方法内部也调用了一个rangeCheck,我们来看看这个方法:

 private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

这个rangeCheck方法主要是用来进行越界判断的:如果传入的index比列表的size大,那么就抛出一个IndexOutOfBoundsException错误。
好奇......如果index<0的话是怎么进行数组越界判断的呢??我还没找到原因,先挖个坑,等找到了方法再填坑。

  • set方法
    public E set(int index, E element):
    用指定的元素替代此列表中指定位置上的元素(返回值为被替代的元素的值)。
public E set(int index, E element) {
//进行越界判断,看看index是否大于size
        rangeCheck(index);
//取出被替代的值
        E oldValue = elementData(index);
//将指定元素替代此列表中指定位置上的元素
        elementData[index] = element;
//返回指定位置上被替代的元素
        return oldValue;
    }
  • add方法
    1.public boolean add(E e):
    将指定的元素添加到此列表的尾部(添加成功则返回true).
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // modCount加一
        elementData[size++] = e;
        return true;
    }

其实在往列表尾部添加元素的时候要保证列表还有空间存放元素,ensureCapacityInternal函数就是用来完成此
工作的,关于ensureCapacityInternal具体的内部实现参见上方ensureCapacity函数部分。
2.public void add(int index, E element):
将指定的元素插入此列表中的指定位置。向右移动当前位于该位置的元素(如果有)以及所有后续元素(将其索引加 1)。

public void add(int index, E element) {
//判断是否越界
        rangeCheckForAdd(index);
//由于对列表结构进行了修改,所以必须增加modCount,关于ensureCapacityInternal参见上方ensureCapacity函数部分。
        ensureCapacityInternal(size + 1); 
//将从Index的元素都往后移一个位置
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
//在指定的index位置插入需要添加的值
        elementData[index] = element;
//列表的size加一
        size++;
    }

add函数内部还调用了rangeCheckForAdd函数,作用是判断指定的位置是否越界。下面是rangeCheckForAdd函数源码:

 private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
  • remove函数
    1.* public E remove(int index):*
    移除此列表中指定位置上的元素。向左移动所有后续元素(将其索引减 1),返回值为被移出的元素。
public E remove(int index) {
//检查下标是否越界
        rangeCheck(index);
//由于对列表结构进行了修改,modCount++
        modCount++;
//保存被移出的值
        E oldValue = elementData(index);
//计算有多少元素需要向左移动
        int numMoved = size - index - 1;
        if (numMoved > 0)
//将Index后面的元素向左移动
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
// 将第size个元素赋值为null以触发垃圾回收,并且size减一
        elementData[--size] = null; 
//返回被移出的值
        return oldValue;
    }

2.* public boolean remove(Object o):*
移除此列表中首次出现的指定元素(如果存在)。如果列表不包含此元素,则列表不做改动。更确切地讲,移除满足 (o==null ? get(i)==null : o.equals(get(i)))的最低索引的元素(如果存在此类元素)。如果列表中包含指定的元素,则返回 true(或者等同于这种情况:如果列表由于调用而发生更改,则返回 true)。

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

其实此方法内部主要还是靠调用fastRemove来完成移出功能的,以下是fastRemove源码(其实具体实现和public E remove(int index)函数差不多,只不过它不会返回被移出的元素的值):

 private void fastRemove(int index) {
//由于对列表结构进行了修改,因此modeCount加一
        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
    }
  • clear函数
    public void clear() :
    移除此列表中的所有元素。此调用返回后,列表将为空。
 public void clear() {
        modCount++;

        // 移出所有元素并触发垃圾回收
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }
  • addAll函数
    1.public boolean addAll(Collection<? extends E> c):
    按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。如果正在进行此操作时修改指定的 collection ,那么此操作的行为是不确定的。(这意味着如果指定的 collection 是此列表且此列表是非空的,那么此调用的行为是不确定的)。
 public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

2.public boolean addAll(int index, Collection<? extends E> c):
从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。向右移动当前位于该位置的元素(如果有)以及所有后续元素(增加其索引)。新元素将按照指定 collection 的迭代器所返回的元素顺序出现在列表中。

public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

Java集合框架学习---深入探究ArrayList源码(三)

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

推荐阅读更多精彩内容