Java究极打基础之ArrayList篇

请叫我程序猿大人

本篇主要介绍ArrayList的用法和源码分析,基于jdk1.8,先从List接口开始。


List

List接口定义了如下方法:

int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
void add(int index, E element);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);
boolean remove(Object o);
E remove(int index);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
E get(int index);
E set(int index, E element);
int indexOf(Object o);
int lastIndexOf(Object o);
List<E> subList(int fromIndex, int toIndex);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);

乍一看,这么多方法。其实很多方法是同样的功能,方法重载而已。
接下来逐个介绍下List定义的方法。

  • size 获得List元素的个数
  • isEmpty 判断List是否为空
  • contains/containsAll 判断List中是否有该元素,或者有该集合中的所有元素
  • iterator 获得迭代器对象用于迭代
  • toArray 将List转换成数组
  • add/addAll 添加元素至List中,默认直接添加到最后,也可以选择指定的位置,还可以添加整个集合
  • remove/removeAll 删除元素,可以根据元素删除,也可以根据索引删除,还可以根据集合删除
  • retainAll 取与目标集合的交集
  • clear 清空List的所有元素
  • get 根据索引获得元素
  • set 覆盖索引处的元素
  • indexOf 获得该元素的索引
  • lastIndexOf 获得该元素的索引(从后往前)
  • subList 获得子List根据start 和 end
  • listIterator 获得ListIterator对象

方法名言简意赅,基本上都可以从方法名知道方法的目的。
接下来分析List的常用实现类:
ArrayList
LinkedList
Vector

本篇介绍ArrayList

ArrayList类图

图片描述

在我的理解中,ArrayList是一个封装的数组,提供了一些便利的方法供使用者使用,规避了使用原生数组的风险。

ArrayList的定义
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

继承了AbstractList即成为了List,就要实现定义的所有方法。
实现了RandomAccess接口 就是提供了随机访问能力,可以通过下标获得指定元素
实现了Cloneable接口 代表是可克隆的,需要实现clone方法
实现了Serializable接口 代表ArrayList是可序列化的

下面介绍下ArrayList的主要方法

添加元素

boolean add(E e)
先附上源码

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

此方法的执行逻辑:

  1. 判断数组长度是否够,不够则扩容。默认扩容1.5倍
  2. elementData[size++] = e;将新元素添加进去。
    private void ensureCapacityInternal(int minCapacity) {
            //是否是空List,是则使用初始容量扩容
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
    
            ensureExplicitCapacity(minCapacity);
        }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;    //增加修改次数
    
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//扩容1.5倍,采用位运算
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);//最大容量就是Integer的最大值
        // minCapacity is usually close to size, so this is a win:
        //采用Arrays的copyOf进行深拷贝,其中调用的本地方法System.arraycopy,此方法是在内存中操作因此速度会很快。
        elementData = Arrays.copyOf(elementData, newCapacity);
        //至此扩容结束
    }

void add(int index, E element)方法稍有不同

  1. 首先检查index是否合法
  2. 扩容
  3. 将新元素插入指定位置
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将index -> (size -1)的元素都往后移动一位 
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

boolean addAll(Collection<? extends E> c)
boolean addAll(int index, Collection<? extends E> c)
这两个方法和上面的add大同小异,第一步都是判断容量,并扩容。
容量大小从1变为c的length,elementData[index] = element;赋值也变为数组拷贝,
直接上代码,秒懂。

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

add总结

  • 无论是哪种add,都需要先执行方法 ensureCapacityInternal(size + 1) 进行容量判断及扩容,确保之后的操作不会产生数组越界。
  • 建议在我们日常工作时,如果大概知道元素的数量,可以在初始化的时候指定大小,这样可以减少扩容的次数,提升性能。

删除元素

E remove(int index)

  1. 检查是否索引不正确。
  2. 计算移动的元素的个数,数组往前移动。
  3. 将之前第size位置的元素置空,返回被删除的元素。
    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);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

boolean remove(Object o)

  1. 区分要删除的元素是null还是非null。
  2. 找到该元素的索引,执行上面方法逻辑的2、3步。
    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
    }

这种方法依赖元素的equals方法,循环遍历数组,因为ArrayList是允许null元素的,所以无论是使用if (o.equals(elementData[index])) 还是 if (elementData[index].equals(o)) 均可能产生空指针,所以单独对null进行处理,逻辑都是一样的。
奇怪的是这个fastRemove方法,原本以为会有些特殊处理,结果发现代码和上面remove(int index)中的一模一样,为什么上面的remove中不调用这个fastRemove呢?难道写两个remove方法的不是同一人?逻辑不影响,只是代码冗余了一点。

boolean removeAll(Collection<?> c)

    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }

删除存在于目标集合的方法,使用了batchRemove(Collection<?> c, boolean complement)这个方法,这个方法作了封装,在retainAll(Collection<?> c)取并集这个方法也有使用。
retainAll中是这样使用的:

return batchRemove(c, true);

和我们的removeAll只差第二个参数boolean complement,remove是false,retail是true,那到底是什么意思呢?complement的原意的补充,在这里我理解为保留,remove就不保留,retail就保留,接着我们分析batchRemove这个方法。

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

定义了 r 和 w两个int,r用于遍历原来的数组,w的意思是新的数组的size。
假如定义2个数组用于举例,
数组1,五个元素 1,2,3,4,5
数组2,五个元素 3,4,6,7,8

数组1调用removeAll(数组2)
此时进入batchRemove,我们来一步一步走看r,w如何变化
此时complement是false,即数组2中没有数组1中第r个元素才满足if条件

  1. r = 0, w = 0, elementData[r] = 1, c.contains(elementData[r]) = false.
    执行elementData[w++] = elementData[r],
    数组1:1,2,3,4,5
  2. r = 1, w = 1, elementData[r] = 2, c.contains(elementData[r]) = false.
    执行elementData[w++] = elementData[r],
    数组1:1,2,3,4,5
  3. r = 2, w = 2, elementData[r] = 3, c.contains(elementData[r]) = true.
    此时if条件不满足了,w不自增了,代表elementData[r]要不存在与新数组中,要被删除,所以跳过了。
  4. r = 3, w = 2, elementData[r] = 4, c.contains(elementData[r]) = true.
    if条件依旧不满足,w不自增,此元素也是要删除。
  5. r = 4, w = 2, elementData[r] = 5, c.contains(elementData[r]) = false.
    执行elementData[w++] = elementData[r],
    数组1:1,2,5,4,5
    此时for循环结束,elementData数组目前是这样的,接着执行finally中的代码,

首先执行

            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }

我们的情况是 r=size,那什么时候r会不等于size呢,jdk中写了注释,就是在if判断时,调用数组2的contains方法,可能会抛空指针等异常。这时数组还没有遍历完,那r肯定是小于size的。
那没判断的那些数据还要不要处理?保守起见jdk还是会将他保存在数组中,因为最终w是作为新的size,所以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;
            }

这段代码意图很清晰,w因为是新的size值,所以将w及其之后的都置位null,增加修改次数,
给size赋予新值之后就结束了。

remove总结

  • 删除元素尽量使用指定下标的方法,性能好。
  • 每次删除都会进行数组移动(除非删除最后一位),如果频繁删除元素,请使用LinkedList

boolean contains(Object o)
contains 底层调用的是indexOf方法

     public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

int indexOf(Object o)

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

就是循环遍历,一个个比对,有则返回对应下标,无则返回-1
对应的lastIndexOf是从最后往前遍历。

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

E get(int index)

        rangeCheck(index);

        return elementData(index);

先检查index,再返回数组对应元素。

E set(int index, E element)

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

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

先检查index,在覆盖index的元素,返回旧元素。

void clear()

        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;

增加修改次数,将所有元素置空,size设置为0,需要注意的是,数组的大小是没有变化的。

int size()
size方法就是直接将size变量直接返回

    public int size() {
        return size;
    }

boolean isEmpty()
判断size是否等于0

    public boolean isEmpty() {
        return size == 0;
    }

迭代器

Iterator<E> iterator()

    public Iterator<E> iterator() {
        return new Itr();
    }

如代码所示,创建了一个Itr对象并返回,接下来看Itr类的定义

    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

首先介绍下三个成员变量

  1. cursor,代表下一个访问元素的在elementData数组中的索引,初始值是0。
  2. lastRet,代表上一个访问元素在elementData数组中的索引,初始值是-1。
  3. expectedModCount,预期的modcount的值,在Itr对象创建的时候从父类ArrayList的modcount获得,用于判断在使用该对象迭代时,有么有对数组进行修改,有则会抛出ConcurrentModificationException。

平常常用的迭代器方法
hasNext()
就是判断当前索引是否等于size。

E next()
首先检查list是否被修改过,expectedModCount是在创建对象时就获得了,如果在之后对list进行了其他修改操作的话,modCount就会增加,就会抛出ConcurrentModificationException。
没有异常就按下表返回坐标,cursor自增,lastRet也自增。

void remove()

首先判断是否能删除,若能则调用父类的remove方法,删除元素,接着会更新cursor和lastRet。
最重要的是会更新expectedModCount,此时调用了父类的remove方法,会使modCount+1,所以更新了 expectedModCount,让后续的检查不会抛异常。

ListIterator<E> listIterator

listIterator和iterator一样,只不过listIterator有更多的方法,相当于iterator的加强版。
定义如下

    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public E previous() {
            checkForComodification();
            try {
                int i = cursor - 1;
                E previous = get(i);
                lastRet = cursor = i;
                return previous;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor-1;
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.set(lastRet, e);
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                AbstractList.this.add(i, e);
                lastRet = -1;
                cursor = i + 1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

没有什么需要注意的地方,基本的方法都从Iterator中继承过来并添加一些方法。

List<E> subList(int fromIndex, int toIndex)

这又是一个内部类

class SubList<E> extends AbstractList<E> {
    private final AbstractList<E> l;
    private final int offset;
    private int size;

    SubList(AbstractList<E> list, int fromIndex, int toIndex) {
        if (fromIndex < 0)
            throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
        if (toIndex > list.size())
            throw new IndexOutOfBoundsException("toIndex = " + toIndex);
        if (fromIndex > toIndex)
            throw new IllegalArgumentException("fromIndex(" + fromIndex +
                                               ") > toIndex(" + toIndex + ")");
        l = list;
        offset = fromIndex;
        size = toIndex - fromIndex;
        this.modCount = l.modCount;
    }
    。
    。
    。
}

持有了父类的引用,内部的所有方法都是通过父类的这个引用去完成的,
所以需要注意的是,subList不是返回一个新的List,还是原来的引用,所以改变subList的数据,原有的数据也会更改。

终于把基本的方法都介绍完了,从源码的角度分析了所有的方法,感觉对ArrayList知根知底了,用它时肯定会更加得心应手了,看了源码才有原来实现都这么简单啊这样的感觉,不过从中也学习到了大牛规范的代码风格,良好的结构,可读性很高。下篇分析List的另一个实现类,LinkedList。

阅读 1.4k
19 声望
2 粉丝
0 条评论
19 声望
2 粉丝
文章目录
宣传栏