ArrayList源码分析

List

List是一个维持内部元素有序的采集器,其中的每个元素都会拥有一个索引,每个元素都可以
通过他的索引获取到元素,并且索引的开始下标是从0开始的,List中的元素还可重复。
而Set中不不含重复元素的。

以上便List的定义。实际中List仅是一个接口,并没有具体的方法实现,只是定义好了统一的方法。

以下便是List的继承结构:

  • Iterable
    • Collection
      • List

我们先来看看顶级的Iterable:

Instances of classes that implement this interface can be used with
the enhanced for loop.
也就是说继承了这个接口能增强子类的循环能力。

Iterable中有唯一定义的接口方法:

Iterator<T> iterator();

他的作用就是返回一个Iterator<T>的实例。

接下来看看Iterator到底是什么东西

他是一个对象序列的迭代器,例如说集合。
它拥有三个接口方法:

//是否还有更多的没有被迭代的元素,有则返回true,否则返回false
public boolean hasNext();

 //返回下一个对象元素,并且是迭代器前进,如果没有元素了的话会抛出NoSuchElementException
 public E next();

 //移除最后通过next()返回对象元素。此方法只能在调用next()后才能调用,否则会抛出IllegalStateException
 public void remove();

我们再来看看Collection接口:

Collection是所受Collection类型的根节点,也就是说所有的集合类型的都会实现这个接口。

方法 说明
add(E object) 尝试将一个对象添加到Collection中,保证添加成功了该对象元素就包含在Collection中。在实现该接口方法的类中,需要根据具体的添加规则抛出相应的Exception。注意一点是抛出异常了就不会返回false,而添加成功会返回true。
addAll(Collection<? extends E> collection) 试图将参数中的collection的所有元素添加到当前实例中的Collection中。添加成功返回ture,否则返回false。
clear() 将原本collection中的元素全部删除。如果移除操作不允许会抛出UnsupportedOperationException。
contains(Object object) 遍历Collection中的所有元素,找到一个相等的元素则返回true,否则返回false。可能抛出的异常:ClassCastException,NullPointerException。
containsAll(Collection<?> collection) Collection是是否包含参数中collection中的每个元素,即使是每个参数仅仅包含一次都会返回true,否则返回false。参数collection==null 或者 collection中至少包含一个null元素的时候回抛出NullPointerException
equals(Object object) 当前Collection中与给定的object是否相等。
hashCode() 返回Collection中所有元素的哈希值和,主要用于比较。
isEmpty() 是否Collection中没有元素。
Iterator<E> iterator() 返回一个迭代器实例,用来访问Collection中的内容。
remove(Object object) 将Collection中与参数object相等的元素。可能抛出的异常:UnsupportedOperationException,ClassCastException,NullPointerException
removeAll(Collection<?> collection) 将在Collection中包含参数collection中的元素移除。返回的结果是Collection中不包含有参数collection中的元素。
retainAll(Collection<?> collection) 将Collection中不包含在参数collection中的元素移除。返回的结果是Collection中包含有参数collection中的元素。
size() 返回Collection中元素的个数,如果个数大于Integer.MAX_VALUE,返回的结果是Integer.MAX_VALUE
Object[] toArray() 将Collection中的所有元素根据迭代顺序以一个新数组返回,即使Collection的底层已经是一个数组结构。
<T> T[] toArray(T[] array) 将Collection中的元素根据迭代顺序添加到给定的array中,如果array不能装下Collection中的所有元素则会新建一个对应类型,对应大小的数组,如果array的大小大于Collection的元素个数,则array多余的索引位置元素置为null。

而List在Collection的基础上增加了以下接口方法:

方法 说明
add(int location, E object) 在索引location处插入一个元素,location==size()的话,直接在末尾添加。<size的话,在location处插入,location之后的元素依次后移。location < 0 或者 location > size()则抛出IndexOutOfBoundsException
boolean addAll(int location, Collection<? extends E> collection) 在指定索引处插入一个contains的所有元素
E get(int location) 返回指定索引处的元素。location < 0 或者 location > size()则抛出IndexOutOfBoundsException
int indexOf(Object object) 返回一个指定object元素在list中的索引,没有此元素则返回-1
int lastIndexOf(Object object) 最后一个等于指定object元素的索引,没有则返回-1
remove(int location) 根据索引移除元素,location < 0 或者 location > size()则抛出IndexOutOfBoundsException
remove(Object object) 找到并移除了该元素则返回true,否则返回false
E set(int location, E object) 将指定索引位置设置为元素object。会有IndexOutOfBoundsException,ClassCastException。
List<E> subList(int start, int end) 返回索引start到end处的子列表,会有IndexOutOfBoundsException。

此外还有:

ListIterator<E> listIterator();
ListIterator<E> listIterator(int location);

其中ListIterator继承子Iterator,新添加了几个方法:

public boolean hasPrevious();
public int nextIndex();
public E previous();
public int previousIndex();
void set(E object);

ArrayList

ArrayList继承自AbstractList,实现了Cloneable,Serializable,RandomAccess接口
而AbstractList继承自AbstractCollection<E>实现了List<E>接口,是一个抽象类,他的子类必须实现iterator(),size()这个两个抽象方法,以使得可以创建处一个不可变的集合。而创建一个可修改的集合类型则需要重写add()方法。

ArrayList是List的一个基于数组的实现类,支持增加,移除,替换等元素操作。并且支持所有元素类型包括null。它的所有操作同步进行的。而CopyOnWriteArrayList则可以用于高度并发,频繁遍历的情形。

数组容量增长步长。

private static final int MIN_CAPACITY_INCREMENT = 12;

构造函数

初始化时指定数组容量,直到自己数据容量的时候,最好都指定。默认是一个空数组。

public ArrayList(int capacity) {
    if (capacity < 0) {
        throw new IllegalArgumentException("capacity < 0: " + capacity);
    }
    array = (capacity == 0 ? EmptyArray.OBJECT : new Object[capacity]);
}

public ArrayList() {
    array = EmptyArray.OBJECT;
  }

 //指定一个collection初始化的时候
 public ArrayList(Collection<? extends E> collection) {
   if (collection == null) {
       throw new NullPointerException("collection == null");
   }
   //先转化成数组
   Object[] a = collection.toArray();

   if (a.getClass() != Object[].class) {
       //创建一个长度为collection长度的新数组,并将collection数组复制到新数组中
       Object[] newArray = new Object[a.length];
       System.arraycopy(a, 0, newArray, 0, a.length);
       a = newArray;
   }
   array = a;
   size = a.length;
}

添加

添加的时候基本策略是,有指定添加的索引位置的时候就检查是否索引越界,如果是则直接抛出越界异常。然后是检查当前数组是否已经装满,如果是则计算新的数组容量,并创建一个新的数组,原数组的元素复制到新数组并将新添加的元素加入到list的末尾,更新size值。

@Override public boolean add(E object) {
    Object[] a = array;
    int s = size;
    //原数组已满
    if (s == a.length) {
        Object[] newArray = new Object[s +
                (s < (MIN_CAPACITY_INCREMENT / 2) ?
                 MIN_CAPACITY_INCREMENT : s >> 1)];
        System.arraycopy(a, 0, newArray, 0, s);
        array = a = newArray;
    }
    a[s] = object;
    size = s + 1;//容量加一
    modCount++;
    return true;
}

该方法是根据传入的当前数组容量值计算并返回新的容量值,时空权衡。
扩容策略:

  1. 当前容量小于最下增长量的一半:
    当前容量+最小增长量
  2. 当前容量大于等于最下增长量的一半:
    当前容量+当前容量/2
private static int newCapacity(int currentCapacity) {
    int increment = (currentCapacity < (MIN_CAPACITY_INCREMENT / 2) ?
            MIN_CAPACITY_INCREMENT : currentCapacity >> 1);
    return currentCapacity + increment;
}

查找

方法 默认返回值
public boolean contains(Object object) false
public int indexOf(Object object) -1
public int lastIndexOf(Object object) -1
public boolean remove(Object object) false

当需要查找的元素对象不为空的时候,从头开始遍历数组的元素,equals则直接返回对应类型。
查找的元素对象为空时,从头开始遍历数组的元素,遇到一个 ==null的元素的时候则直接返回对应类型。

Object[] a = array;
int s = size;
if (object != null) {
    for (int i = 0; i < s; i++) {
        if (object.equals(a[i])) {
            ...
            return ...;
        }
    }
} else {
    for (int i = 0; i < s; i++) {
        if (a[i] == null) {
            ...
            return ...;
        }
    }
}

remove一个元素的时候会有这么一个操作:a[s] = null; 是为了防止内存溢出。

指定位置元素移除: public E remove(int index),只需检测时候下标越界。不越界则移除该位置的元素。

移除元素位置之后的元素都前移一位
System.arraycopy(a, index + 1, a, index, --s - index);
//此时a[s] == a[s-1],所以可以删除末尾的那个a[s]
a[s] = null;  // Prevent memory leak
size = s;

在每次list的增删改操作的时候都会modCount++。modCount是用来记录list的修改次数,
主要是在ArrayList总的内部迭代器ArrayListIterator中使用

ArrayListIterator

//剩余没有被迭代到的元素数量
private int remaining = size;
//可被使用remove()移除的元素的索引, -1表示没有可以被移除的元素
private int removalIndex = -1;
//期待的list操作次数,可判断是否存在并发操作
private int expectedModCount = modCount;

//是否迭代完
public boolean hasNext() {
    return remaining != 0;
}

public E next() {
    ArrayList<E> ourList = ArrayList.this;
    int rem = remaining;
    //存在并发操作
    if (ourList.modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    //已经迭代完,继续迭代抛出异常, 用hasNext()做检查,避免此异常
    if (rem == 0) {
        throw new NoSuchElementException();
    }
    remaining = rem - 1;
    //ourList.size - rem处的元素
    return (E) ourList.array[removalIndex = ourList.size - rem];
}

public void remove() {
    Object[] a = array;
    int removalIdx = removalIndex;

    ...

    System.arraycopy(a, removalIdx + 1, a, removalIdx, remaining);
    a[--size] = null;  // Prevent memory leak
    removalIndex = -1;//重新置为-1,表示该索引的元素已移除。除非被next()更新。
    expectedModCount = ++modCount;//会更新list的操作次数
}

再来看看的ArrayList的迭代器中定义的equals策略

public boolean equals(Object o) {
    //引用相等
    if (o == this) {
        return true;
    }
    //参数o不是List的子类
    if (!(o instanceof List)) {
        return false;
    }
    List<?> that = (List<?>) o;
    int s = size;
    //两个list的size不想等
    if (that.size() != s) {
        return false;
    }
    Object[] a = array;

    if (that instanceof RandomAccess) {
        for (int i = 0; i < s; i++) {
            Object eThis = a[i];
            Object ethat = that.get(i);
            if (eThis == null ? ethat != null : !eThis.equals(ethat)) {
                return false;
            }
        }
    } else {  // 参数的list不支持随机访问,则使用迭代器进行迭代
        Iterator<?> it = that.iterator();
        for (int i = 0; i < s; i++) {
            Object eThis = a[i];
            Object eThat = it.next();
            if (eThis == null ? eThat != null : !eThis.equals(eThat)) {
                return false;
            }
        }
    }
    return true;
}

至此,ArrayList已被你KO。

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

推荐阅读更多精彩内容