Java还要再学一遍基础(六)ArrayList详解

ArrayList概要。

  • ArrayList是一个容量可变的动态数组,继承了AbstractList,并且AbstractList已经实现了一些基本的增删改查,ListIterator等功能,ArrayList更关注的是内部的的数组实现。
  • ArrayList是实现RandomAccess接口,RandomAccess接口中并没有任何的方法,只是表明具有快速随机访问的功能,也就是通过Index(索引)访问。
  • ArrayList中的元素可以重复,可以为空。
  • ArrayList是线程不安全的,并发环境可以使用Vector或者CopyOnWriteList。
  • ArrayList的查找和修改效率很高,但是插入和删除效率不高。

源码解析(基于JDK1.8)

1. 重要属性

//用于存储元素的数组,并且不能序列化
transient Object[] elementData;
//元素的数量
private int size;

可以看到ArrayList内部确实是用数组实现的。

2. 构造函数

 public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

提供空参的构造方法和指定容量的构造方法,和初始数据的构造方法。
并且空参的构造方法中还没有指定容量。

3. 关键的方法

add方法:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        //如果elementData中没有数据,则取(10, minCapacity)中大的作为容量
        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;
        //新的容量为原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

在调用add方法的时候,首先会调用ensureCapacityInternal方法去判断容量。并且在当前容量小于10的情况下会以10作为容量,基本可以说默认容量为10, 如果超出则扩容到原来的1.5倍(oldCapacity >> 1)右移一位,相当于除以2.

Override了Clone方法:

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

同样克隆之前必须先调用父类的super.clone()申请空间。然后江数据中的数据拷贝到新的ArrayList中去,再将修改次数modCount置为零。

toArray方法
ArrayList提供了两个toArray方法

 public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

@SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

一个是返回的Object[] 一个是返回的指定的泛型的数组。

List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 100; i++){
            list.add(i);
        }
        Integer[] a = (Integer[])list.toArray();

上面这段代码看似没问题但是会报 java.lang.ClassCastException异常,原因是java的数组不能直接强转。

        List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 100; i++){
            list.add(i);
        }
        //Integer[] a = (Integer[])list.toArray();

        Integer[] a  = new Integer[list.size()];
        list.toArray(a);

上面这段代码便没有问题。

remove方法:

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


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
    }

remove方法基本没什么特别的,把对应位置的元素删除其实也就是把该元素以前和以后的元素重新组成一个新的数组,这样也导致删除的时候效率很低。


4. ArrayList中的ListIterator和Iterator

  • Iterator是Collection大家族中的一个迭代器,Iterator是一个接口,其中定义了hasNext,next,和remove方法等。
  • ListIterator是专门用于List集合的迭代器,也是一个接口,同时提供了hasPrevious,previous,add,set等方法,能够倒序遍历而且还可以增加元素等,功能更加齐全。

ArrayList中的ListIterator实现:

  • 首先ArrayList里面有一个private的class叫做Itr,实现了Iterator接口。这个接口很明显是为了提供基本的Iterator的迭代器功能。
 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();
        }
    }

实现方法很简单,利用一个游标记录当前的位置,再配合size做判断从而进行迭代。

  • ArrayList中还提供了一个private的class叫做ListItr,继承了Itr,同时实现了ListIterator接口,里面实现了倒序迭代,增加,修改等功能。
private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

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

        public int nextIndex() {
            return cursor;
        }

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

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

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

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

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

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

实现方法也比较简单,只是把游标的操作方向换了一下而已。

通过ArrayList中提供的public的方法即可获取相应的迭代器:

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

public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

- ArrayList中遍历的时候操作元素的坑

坑1:
例:

        List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 10; i++){
            list.add(i);
        }

        for(int i = 0; i < list.size(); i++){
            if(list.get(i) % 5 == 0)
                list.remove(i);
        }

        for(int i = 0; i < list.size(); i++){
            System.out.print(list.get(i) + " ");
        }

上面的代码没有问题,输出:

1 2 3 4 6 7 8 9 

看下面一段代码:

    List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 10; i++){
            list.add(1);
        }

        for(int i = 0; i < list.size(); i++){
            if(list.get(i) == 1)
                list.remove(i);
        }

        for(int i = 0; i < list.size(); i++){
            System.out.print(list.get(i) + " ");
        }

输出:

1 1 1 1 1 

并不是所有等于1的元素都被remove了。
实际假设index为0的位置的元素的值为1这个时候1被remove,同时从index为1开始的后面的元素都会向前挪一个位置,这个时候原来在index为1的位置的元素跑到了0的位置,所以没有遍历到。

解决办法,倒序遍历,因为无论后面的怎么变,我们判断的都是前面不变的元素:

    List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 10; i++){
            list.add(1);
        }

        for(int i = list.size() - 1; i >= 0 ; i--){
            if(list.get(i) == 1)
                list.remove(i);
        }

        for(int i = 0; i < list.size(); i++){
            System.out.print(list.get(i) + " ");
        }

坑2:
例:

        List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 10; i++){
            list.add(i);
        }

        int len = list.size();
        for(int i = 0; i < len ; i++){
            if(list.get(i) % 5 == 0)
                list.remove(i);
        }

        for(int i = 0; i < list.size(); i++){
            System.out.print(list.get(i) + " ");
        }

运行抛出异常:java.lang.IndexOutOfBoundsException
因为之前的循环条件是i < list.size(),而在remove之后size是会变小的,但是通过size方法获取到的都是正确的所以没问题,这里的循环条件一直都是len也就是remove之前的,所以抛出异常。

坑3
例:

        List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 10; i++){
            list.add(i);
        }

        for(Iterator<Integer> it = list.iterator(); it.hasNext();){
            int i = it.next();
            if(i % 5 == 0)
                list.remove(i);
        }

        for(int i = 0; i < list.size(); i++){
            System.out.print(list.get(i) + " ");
        }

运行:抛出java.util.ConcurrentModificationException
这个异常的意思是在迭代的时候list发生了预想之外的改变,
看一下Iterator中的next,和remove方法:

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

两个方法中都要先调用checkForComodification方法

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

如果modCount不等于expectedModCount就抛异常。
最后再看看ArrayList中的remove方法:

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

其中有一个很明显的modCount++的操作。
不难得出,这个一场就是由于调用ArrayList的remove方法的时候modCount发生改变从而与Iterator中的expectedModCount不相等,抛出异常。
解决办法?—》 使用Iterator提供的remove方法即可。

        List<Integer> list = new ArrayList<>(150);
        for(int i = 0; i < 10; i++){
            list.add(i);
        }

        for(Iterator<Integer> it = list.iterator(); it.hasNext();){
            int i = it.next();
            //调用Iterator的remove
            if(i % 5 == 0)
                it.remove();
        }

        for(int i = 0; i < list.size(); i++){
            System.out.print(list.get(i) + " ");
        }

输出:

1 2 3 4 6 7 8 9 

* * ArrayList中的subList坑**

  • subList返回的是ArrayList的内部类SubList,而不是ArrayList,所以不能强转成ArrayList,会抛出ClassCastException,同时对与返回的subList的操作会反映到元表上。
    例:
List<Integer> list = new ArrayList<>(150);
for(int i = 0; i < 10; i++){
    list.add(i);
}

for(Iterator<Integer> it = list.iterator(); it.hasNext();){
    int i = it.next();
    System.out.print(i + " ");
}

System.out.println();

List<Integer> subList =  list.subList(0, list.size());

for(int i = 0; i < 10; i++){
    subList.set(i, i + 1);
}

for(Iterator<Integer> it = list.iterator(); it.hasNext();){
    int i = it.next();
    System.out.print(i + " ");
}

输出:

0 1 2 3 4 5 6 7 8 9 
1 2 3 4 5 6 7 8 9 10 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值