Java集合类源码阅读之AbstractCollection

时间:2022-09-20 17:02:35
AbstractCollection是Collection的一个抽象类实现。
抽象类,即给所有子类提供一些通用的方法实现,其他方法由子类自己实现。

下面关注其中的几个方法实现

1、将集合类转换成数组的toArray方法(JDK 7)

public Object[] toArray() {
        // Estimate size of array; be prepared to see more or fewer elements
        Object[] r = new Object[size()];
        Iterator<E> it = iterator();
        for (int i = 0; i < r.length; i++) {
            if (! it.hasNext()) // fewer elements than expected
                return Arrays.copyOf(r, i);
            r[i] = it.next();
        }
        return it.hasNext() ? finishToArray(r, it) : r;
    }

方法体中有两处return,第一处return Arrays.copyOf(r, i); 注释的意思是比预期的元素个数要少,这是为什么呢?因为该方法并没有同步,如果在迭代期间被其他线程修改的话,那么就可能出现在迭代复制元素时获取的元素个数比方法体一开始获取的size()要小。当然如果其他线程是添加元素的话这里仍旧是以前size个元素为基础拷贝,最后一个return说明了这个问题,来看看finishToArray方法:
private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
        int i = r.length;
        while (it.hasNext()) {
            int cap = r.length;
            if (i == cap) {
                int newCap = cap + (cap >> 1) + 1;
                // overflow-conscious code
                if (newCap - MAX_ARRAY_SIZE > 0)
                    newCap = hugeCapacity(cap + 1);
                r = Arrays.copyOf(r, newCap);
            }
            r[i++] = (T)it.next();
        }
        // trim if overallocated
        return (i == r.length) ? r : Arrays.copyOf(r, i);
    }

该方法的两个参数r和it分别是toArray方法中放置集合元素的对象数组和迭代器
r.length获取的就是toArray方法中调用size()方法获取的初始数组大小,进入while循环(即在toArray方法结束的时候发现迭代器中元素比预期的要多),int cap = r.length,此时if判断成立,即第一次进入该while循环,那么就会分配一个更大的数组(增加一半的大小,注意可能会溢出,这里处理了分配超大数组的情况),然后将原来数组r中的元素拷贝到新数组中,后续将元素放置在扩容数组中,一旦超过,还会继续分配大数组。最后只返回i个元素(即迭代器中元素个数)对应的数组
下面是hugeCapacity的代码
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError
                ("Required array size too large");
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
 MAX_ARRAY_SIZE的值是0x7ffffff7,如果需要分配的数组过大,可能导致内存溢出

2、向集合中添加元素

添加一个元素的add的默认实现总是抛出该异常
public boolean add(E e) {
        throw new UnsupportedOperationException();
    }
添加另一个集合中的所有元素addAll
public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c)
            if (add(e))
                modified = true;
        return modified;
    }
在addAll中我们看到调用了add方法,那么按照上面的说法肯定是要抛出异常的。那么怎么回事呢?
这里是要求子类实现该类时如果需要调用addAll方法的话必须自己覆盖add操作

3、关于contains, removeAll和retainAll操作的时间复杂度

首先来看一下contains方法源码
public boolean contains(Object o) {
        Iterator<E> it = iterator();
        if (o==null) {
            while (it.hasNext())
                if (it.next()==null)
                    return true;
        } else {
            while (it.hasNext())
                if (o.equals(it.next()))
                    return true;
        }
        return false;
    }
很显然该方法会遍历迭代器中所有元素,时间复杂度是O(n)(n为集合中元素个数)
下面是removeAll和retainAll中的源代码:
public boolean removeAll(Collection<?> c) {
        boolean modified = false;
        Iterator<?> it = iterator();
        while (it.hasNext()) {
            if (c.contains(it.next())) {
                it.remove();
                modified = true;
            }
        }
        return modified;
    }

public boolean retainAll(Collection<?> c) {
        boolean modified = false;
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            if (!c.contains(it.next())) {
                it.remove();
                modified = true;
            }
        }
        return modified;
    }
两个操作都调用了contains方法,所以这两个方法的时间复杂度都是O(this.size() * c.size())