Collection源码之路(1)——ArrayList

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/LosingCarryJie/article/details/81435547

(以下源码建立在JDK 10版本基础上)

ArrayList这个类用的实在是太频繁了,除基本类型之外应该算是最常用了吧,但是一直用过却一直不曾研究过里面的源码,这是程序员的大忌,用什么就要研究什么,否则只是代码工匠谈不上代码师。

在开始ArrayList学习之前,我一直有个疑问,就是数组可以动态扩容吗?我们知道java声明数组时,必须指明数组的个数或者列举出所有的元素(等于告诉编译器自己长度为多少)

  String a[] = new String[]{"1","2","3"};
  String b[] = new String[10];
  String c[] = new String[];//这一行编译不过去

可是如果我们使用的时候发现数据太多了,数组太小了,需要扩容怎么办呢?

没办法,新建一个数组,将旧的数组数据拷贝到新数组里面

 String a[] = new String[]{"1","2","3"};
 String b[] = new String[a.length+10];

例子比较简单,这样做尚可但是如果比较复杂的数组怎么处理长度呢?而且即使你处理完了发现数组又小了,该如何是好?继续创建新的数组吗?这样做太麻烦了!

创建一个可改变长度的数组就好了——ArrayList

正因为有了这样的需求,ArrayList横空出世,ArrayList就是可改变长度的数组

Resizable-array implementation of the {@code List} interface.

与此同时,其元素还包括null

Implements all optional list operations, and permits all elements, including {@code null}

Vector线程安全,ArrayList线程不安全

(This class is roughly equivalent to {@code Vector}, except that it is unsynchronized.)

其成员变量有下面几个

  /**
     * Default initial capacity. 默认集合长度为10
     */
    private static final int DEFAULT_CAPACITY = 10;
   /**
     * Shared empty array instance used for empty instances.
     * 当你用new ArrayList(0)初始化的时候指定长度为0就会用到这个数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
 /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     * 当你使用new ArrayList()生成集合的时候默认使用这个数组,解释具体看后面的代码
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
前面transient 表示是暂时的变量,序列化和反序列化都不会操作这个变量
这个变量什么呢?就是在不断变化的那个数组,就好像我们使用p=head,在不破坏原有指针的情况下,构造了一个局部变量来代替head指针,这个也是如此用于代替上面两个数组
transient Object[] elementData;
集合的长度
需要注意的是size是表示集合的长度,而后续源码中有elementData.length表示的是内部数组的长度,这两个不是一个概念!size是暴露给调用者的,elementData这个数组是底层数组,对于用户是不可知的,他的长度不用和size相同
private int size;
集合最大长度,最大值-8,为啥-8我也不知道????
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

1 先看看我们常用的初始化操作 new ArrayList<>()

List<String> a=new ArrayList<>();
 public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

也就是说如果我们使用无参构造方式创建的集合,使用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个数组

2 再看看add()方法

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

如果发现数组已经满了,程序会走进grow()方法,用于扩容这个数组,操作成功之后集合长度+1

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

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

最少也要在原来的数组上增加1个,所以size+1,接下来我们看下Arrays.copyOf里面的newCapacity方法,这个方法是获取要扩容的长度,我们增加一个元素只扩容1的话岂不是每次扩容全部方法都要走一遍?不如多扩容点,这样的话省的以后麻烦

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

int newCapacity = oldCapacity + (oldCapacity >> 1);
这句话的意思是新的数组长度要是旧的1.5倍,也就是说扩容要扩充1.5倍,>>1表示除以2的意思
当集合为空的时候,newCapacity - minCapacity <= 0,又是DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组所以返回的扩容长度为DEFAULT_CAPACITY,所以默认集合长度为10

public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        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;
    }

new Object[newLength]是新建了一个数组

System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
这段代码很重要,数组扩容就是用的这个方法

这句话的意思是:
将original数组从0位置至original.length位置,复制到copy数组0位置到Math.min位置。
为什么做了一个取最小值操作呢?
因为有可能是数组增加也有可能是数组删除,防止造成数组下标越界。
经过了一系列操作,这个数组变成了一个新数组copy,数组长度也变化了

3 再看看get()方法

public E get(int index) {
        Objects.checkIndex(index, size);
        return elementData(index);
    }
 E elementData(int index) {
        return (E) elementData[index];
    }

Objects.checkIndex(index, size);这句话是检验是否下标越界,如果越界的话会抛出异常
本身就是数组,所以取相应索引的数据也很容易看懂

4 remove方法

  public boolean remove(Object o) {
        final Object[] es = elementData;
        final int size = this.size;
        int i = 0;
        found: {
            if (o == null) {
                for (; i < size; i++)
                    if (es[i] == null)
                        break found;
            } else {
                for (; i < size; i++)
                    if (o.equals(es[i]))
                        break found;
            }
            return false;
        }
        fastRemove(es, i);
        return true;
    }

这也很容易只是找到对应object的索引,具体删除的工作交给了fastRemove方法
found:{}这个是语句块命名的意思,现在基本上不这么写了,估计是作者写1.2java的时候还是忘不掉c语言的goto语句就这么写了,就是返回个索引而已,很简单

  private void fastRemove(Object[] es, int i) {
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;
    }

如果要删除的元素在最后一个,也就是说(newSize = size - 1) == i的话,没有必要操作数组,直接数组的最后一个数据为空就好了,但是如果是前面的某个数据要被删除呢?

System.arraycopy(es, i + 1, es, i, newSize - i);

又是这句话,意思是把i之后的元素复制到i个位置开始到newSize - i长度,这个有点绕,举个例子

数组     1 2 3 4
我想删除第二个元素,下标是1
变成新数组 1 3 4

把2删除掉,34前移

5 addAll()方法

   public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        modCount++;
        int numNew = a.length;
        if (numNew == 0)
            return false;
        Object[] elementData;
        final int s;
        if (numNew > (elementData = this.elementData).length - (s = size))
            elementData = grow(s + numNew);
        System.arraycopy(a, 0, elementData, s, numNew);
        size = s + numNew;
        return true;
    }

增加的操作之前已经说过啦,但是有一点需要说下,项目中是不允许addAll空的集合的,因为作者在第一句Object[] a = c.toArray();使用的时候就没有做判空操作,所以我项目中就遇到过源码的空指针异常,把我吓坏了~~~

6 clear方法

   public void clear() {
        modCount++;
        final Object[] es = elementData;
        for (int to = size, i = size = 0; i < to; i++)
            es[i] = null;
    }

数组还在,只是里面每个数据变成空了而已,但是size已经被赋值成了0,也就是说对于外部来说这个集合为空但是实际上里面还是有数组的。

好啦,以上就是ArrayList的大致源码总结,欢迎读者朋友下方留言。

下一篇 Collection源码之路(2)——LinkedList

猜你喜欢

转载自blog.csdn.net/LosingCarryJie/article/details/81435547