容器 AbstractCollection源码解析

5 篇文章 0 订阅

AbstractCollection源码解析

这里主要就是以下几个方法的理解可能需要费点时间

public Object[] toArray() {}
public <T> T[] toArray(T[] a) {}
private static <T> T[] finishToArray(T[] r, Iterator<?> it) {}
private static int hugeCapacity(int minCapacity) {}
public abstract class AbstractCollection<E> implements Collection<E> {
    //构造方法
    protected AbstractCollection() {
    }

    //迭代,看来迭代器是要一层层的下放到子类
    public abstract Iterator<E> iterator();

    //尺寸
    public abstract int size();

    //判断容器尺寸是否为空
    public boolean isEmpty() {
        return size() == 0;
    }

    //判断容器是否包含当前指定元素
    public boolean contains(Object o) {
        //迭代
        Iterator<E> it = iterator();
        //比较判断,为null的时候,判断一下
        if (o==null) {
            while (it.hasNext())
                if (it.next()==null)
                    return true;
        } else {
            //判断取出下一个,进行equals比较
            while (it.hasNext())
                if (o.equals(it.next()))
                    return true;
        }
        return false;
    }

    //容器转化为数组
    /**
     * 这一块代码可以用示例来说明:
     * 假设List:a,b,c,d,e五个元素,数组r的大小就是5
     * 然后进行迭代,iterator()的实现在子类中,大同小异,就是根据索引进行取值
     * 然后for循环跑起来,同时迭代器判断是否有下一个,如果不出意外,应该5个都跑完然后返回一个对应的5个元素的复制数组
     * 如果此时有另外一个线程对这个容器做了操作,可能删除(remove)了一个元素,这时候迭代器可能就跑到第四个索引就表示没有下一个了,直接return了;
     */
    public Object[] toArray() {
        // 初始化一个指定尺寸的数组
        Object[] r = new Object[size()];
        //获取迭代器
        Iterator<E> it = iterator();
        //迭代
        //这里为什么不单单使用迭代器而直接使用for循环,是因为在迭代器遍历过程中添加元素,本来是五个,数组的大小也定义的是5个,此时迭代器是6个,但数组大小不变,直接给数组赋值,就会越界了
        for (int i = 0; i < r.length; i++) {
            if (! it.hasNext()) // fewer elements than expected
                //Arrays.copyOf(r, i);方法的作用就是拷贝一个数组,第一个参数是要被拷贝的数组,第二个是要被拷贝数组的长度,如果超长,则补0
                //https://blog.csdn.net/qq_35329382/article/details/81252264
                //在这里的作用就是:如果没有下一位,也就是当前就是最后一位了,则拷贝整个数组返回,i是索引,是从0开始的,而我们要传入的是length,所以i++后刚好是length
                //因为有可能某个线程对该集合进行了删除操作,导致r.length>size(),为了节省空间,因此返回一个较小的数组
                return Arrays.copyOf(r, i);
            //进行赋值
            r[i] = it.next();
        }
        //如何执行到这一步呢?https://blog.csdn.net/qsk12345qsx/article/details/84656581
        //判断是否有下一个,有则执行 finishToArray(r, it) ,没有则返回空数组r
        //如何能执行到这一步呢?还是接着上面的解释,多线程操作中容器中添加了元素,此时数组已经for循环完成,但是迭代器中仍然有值的话就需要继续往数组里面添加,没有增删操作就直接返回数组r
        //什么时候才会返回数组r呢?有这么一种操作就是多线程,A线程在遍历赋值时,B线程添加了一个元素,所以for循环结束后,迭代器里还有值,就往下走,此时B又删除了一个元素,这时A紧接着判断是否还有下一个,没有了,就直接返回了r;这样又衍生了一个问题,就是假设B不止删除了一个,他删除了两个,甚至更多,此时我们仍然返回的是r,就有点不对头了
        //先往下走,看看finishToArray
        return it.hasNext() ? finishToArray(r, it) : r;
    }
    
    //泛型 转化为数组
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        // Estimate size of array; be prepared to see more or fewer elements
        //获取容器的大小size
        int size = size();
        //定义一个泛型数组
        //如果传入的数组a的长度小于容器中元素的个数,则反射一个数组实例,大小为容器大小;如果大于或等于容器大小则创建等同于传进来的数组大小,直接赋值
        T[] r = a.length >= size ? a :
                  (T[])java.lang.reflect.Array
                  .newInstance(a.getClass().getComponentType(), size);
        Iterator<E> it = iterator();
        //对容器元素迭代
        for (int i = 0; i < r.length; i++) {
            //迭代器中没有下一个元素了
            //1、第一种情况,就是传进来的数组容量比容器容量大,容器都迭代完了,数组还在for循环;
            //2、第二种情况,就是多线程被删除了元素,迭代器中的元素 减少,迭代次数就少了;
            if (! it.hasNext()) { // fewer elements than expected
                //判断a和r是否是同一个,这就符合第一种情况,将传进来的a赋值给了r,如果是一样的,那就将r对应的索引值制空
                if (a == r) {
                    r[i] = null; // null-terminate
                } else if (a.length < i) {
                    //如果a的长度小于i:这就是传进来的a的容量要小于容器本身,这里就是反射获取到的数组r,则直接将原数组对应拷贝返回
                    return Arrays.copyOf(r, i);
                } else {
                    //这里就是剩下的情况:如果在容器内元素迭代的时候,多线程删除n个,此时a的length是要大于或等于i的,此时将r中的值拷贝到a中
                    System.arraycopy(r, 0, a, 0, i);
                    //如果a的length要大于i,则将当前i所对应的a的索引值制空
                    if (a.length > i) {
                        a[i] = null;
                    }
                }
                return a;
            }
            //做赋值操作,将容器中的值赋值给数组
            r[i] = (T)it.next();
        }
        // more elements than expected
        //剩下的和上面的toArray一样
        return it.hasNext() ? finishToArray(r, it) : r;
    }


    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;


    @SuppressWarnings("unchecked")
    private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
        //将数组和迭代器传进来
        //获取当前数组的长度
        int i = r.length;
        //判断是否有下一个元素
        //判断迭代器中是否有下一个元素
        while (it.hasNext()) {
            //获取数组的长度
            int cap = r.length;
            //第一次应该是长度相等的,肯定需要扩容,不然就越界了
            if (i == cap) {
                //需要扩容数组,扩容原始长度/2+1,也就是扩容1.5倍+1
                int newCap = cap + (cap >> 1) + 1;
                // overflow-conscious code
                //对扩容之后的大小进行校验
                //如果新的容量比数组最大值要大
                if (newCap - MAX_ARRAY_SIZE > 0)
                    //重新计算大小,怎么计算?
                    //返回Integer.MAX_VALUE或者MAX_ARRAY_SIZE,就是他的新的大小,当到达Integer.MAX_VALUE的时候,再往下走就越界了,抛异常
                    newCap = hugeCapacity(cap + 1);
                //扩容    
                r = Arrays.copyOf(r, newCap);
            }
            //赋值
            r[i++] = (T)it.next();
        }
        // trim if overallocated
        //如果没有执行上面那个while,则直接返回数组r;执行了则截取扩容后的数组有元素的部分进行拷贝返回。
        //至于为什么是i,而不是i-1,因为上面的i是做索引用的,是从0出发的;就比如说之前有5个元素,i=r.length,之后赋值是r[i++],此时r[5]=元素值,i当前的值是6,也就是容器r的长度
        return (i == r.length) ? r : Arrays.copyOf(r, i);
    }
    
    //容器扩容,
    //看下面的返回值,第一步先是MAX_ARRAY_SIZE,第二步是Integer.MAX_VALUE,如果超过了Integer.MAX_VALUE(2^31-1),就开始变成负数了
    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;
    }
    
    //添加操作,留给子类实现
    public boolean add(E e) {
        throw new UnsupportedOperationException();
    }

    //移除操作,里面的操作内容比较简单,就是通过迭代器判断并移除
    public boolean remove(Object o) {
        Iterator<E> it = iterator();
        if (o==null) {
            while (it.hasNext()) {
                if (it.next()==null) {
                    it.remove();
                    return true;
                }
            }
        } else {
            while (it.hasNext()) {
                if (o.equals(it.next())) {
                    it.remove();
                    return true;
                }
            }
        }
        return false;
    }

    //判断当前容器是否包含指定容器中的所有元素
    public boolean containsAll(Collection<?> c) {
        for (Object e : c)
            if (!contains(e))
                //有一个不包含就返回false
                return false;
        return true;
    }
    
    //添加一个容器中的所有元素到当前容器中
    public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c)
            if (add(e))
                modified = true;
        return modified;
    }

    //从当前容器中移除指定容器中的所有元素
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(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) {
        Objects.requireNonNull(c);
        boolean modified = false;
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            if (!c.contains(it.next())) {
                it.remove();
                modified = true;
            }
        }
        return modified;
    }
    
    //迭代移除
    public void clear() {
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            it.next();
            it.remove();
        }
    }

    //打印
    public String toString() {
        Iterator<E> it = iterator();
        //没有元素
        if (! it.hasNext())
            return "[]";

        StringBuilder sb = new StringBuilder();
        sb.append('[');
        //迭代取值打印
        for (;;) {
            E e = it.next();
            sb.append(e == this ? "(this Collection)" : e);
            if (! it.hasNext())
                return sb.append(']').toString();
            sb.append(',').append(' ');
        }
    }

}

再把上面那四个方法单独拎出来看一下

public Object[] toArray() {}
public <T> T[] toArray(T[] a) {}
private static <T> T[] finishToArray(T[] r, Iterator<?> it) {}
private static int hugeCapacity(int minCapacity) {}

作用:将容器转化为数组

    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方法的发生条件:

    • 单线程正产执行,数组遍历完,容器迭代完,正常结束return;
    • 多线程,有线程对对容器进行了元素删除,则迭代次数减少,提前return;
  • 第二个return方法发生的条件:

    • 多线程,有线程对容器做了元素添加,for循环遍历结束而容器中仍然还有元素,则走finishToArray(r, it)方法;
    • 多线程,有线程对容器做了元素添加,for循环遍历结束而容器中仍然还有元素,但是此时又有线程对容器做了删除操作,则容器又没有了下一个元素,直接返回数组r;

里面有一个Arrays.copyOf(T[] original, int newLength)方法:
作用:指定一个源数组,指定长度,从源数组中拷贝指定长度的元素到一个新数组中返回;

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

指定类型创建一个数组,然后使用

public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

创建一个新数组返回,最后的参数Math.min(original.length, newLength)是取数组长度和指定长度的最小值,即可以取到指定长度的值,还可以防止越界。

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

获取之前处理了的数组的长度i,做之后赋值数组的索引用;
对容器进行迭代;
在迭代过程中,先获取之前数组的长度,然后第一次扩容(因为此时数组已经满了,没有剩余空间),一次扩容1.5倍+1;
对数组容量做边界值检查,如果新扩容的数组大于MAX_ARRAY_SIZE ,则做边界值判断,确保数组容量在MAX_ARRAY_SIZE 之内,或者就是MAX_ARRAY_SIZE ,或者Integer.MAX_VALUE,就这几种可能

  • 小于或等于MAX_ARRAY_SIZE (2^31-1-8)
  • 等于Integer.MAX_VALUE (2^31-1)
    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;
    }

这段代码就是对上面容量的解释,首先传进来的值不能大于Integer.MAX_VALUE,然后就是Integer.MAX_VALUE 和MAX_ARRAY_SIZE判断取值了,超过了MAX_ARRAY_SIZE就是Integer.MAX_VALUE;

再说到上面的扩容之后赋值;最后进行return:

  • 如果索引i+1后和数组长度相等,则直接返回数组r
  • 否则就是扩容扩多了没用完,这时候就需要截取赋值返回了Arrays.copyOf(r, i),只取索引长度的数组复制返回;
    public <T> T[] toArray(T[] a) {
        // Estimate size of array; be prepared to see more or fewer elements
        int size = size();
        T[] r = a.length >= size ? a :
                  (T[])java.lang.reflect.Array
                  .newInstance(a.getClass().getComponentType(), size);
        Iterator<E> it = iterator();

        for (int i = 0; i < r.length; i++) {
            if (! it.hasNext()) { // fewer elements than expected
                if (a == r) {
                    r[i] = null; // null-terminate
                } else if (a.length < i) {
                    return Arrays.copyOf(r, i);
                } else {
                    System.arraycopy(r, 0, a, 0, i);
                    if (a.length > i) {
                        a[i] = null;
                    }
                }
                return a;
            }
            r[i] = (T)it.next();
        }
        // more elements than expected
        return it.hasNext() ? finishToArray(r, it) : r;
    }

先获取容器的尺寸;
初始化一个泛型数组,如果传进来的数组的长度大于或等于当前容器的尺寸,则直接将传进来的数组复赋值给泛型数组,否则就反射实例化一个数组,指定容器的大小为数组的大小;

这时的泛型数组有两种情况:

  • 等同于传进来的数组a
  • 实例化的数组,大小等同于容器的大小

范型数组for循环跑起来;
容器迭代;但凡有下一个元素,就将元素赋值给数组,如果没有下一个元素了,容器迭代完了,if-else有三个判断

  • 当前的泛型数组r是不是传进来的数组a,是的话则将下一个索引对应的值制空;
  • 传进来的数组长度是不是小于泛型数组长度,是的话则不出意外的将当前容器对应的值拷贝成一个新数组返回;
  • 最后一个就是我猜测的:本来传进来的数组a的长度要小于 容器大小,但是在容器迭代的时候,有别的线程对这个进行操作,移除了n个元素,此时的a的长度length是有可能大于或等于i的,然后将泛型数组中的值从前到后拷贝到a中,同时将当前索引对应的值制空;
  • 最后将a返回;

这样就成了一个现象:
传进来的数组的大小如果小于容器的大小,则将容器中的值返回,否则的话就将容器中的值复制到a中,从索引0开始,最后将下一个索引制null(也就是容器的长度是2,则a的下标为2的值为null),最后a返回。

最后一个return和之前的toArray()一样,可能受到多线程的影响,增删了容器元素,使之for循环结束后,迭代器还有元素;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值