资讯专栏INFORMATION COLUMN

Java容器类研究3:AbstractCollection

Donald / 3480人阅读

摘要:随机访问数据是相对于顺序访问数据而言,例如链表的形式。方法将容器中的元素转化为数组的形式。在函数中,对容量每次扩展的大小,并且会检查是否会超过设定的最大数组长度。如果长度超过了限定值,则以原容量为底线,返回一个最大容量。

ArrayList继承自AbstractList,AbstractList为“random access”的数组提供了基本的实现。随机访问数据是相对于顺序访问数据而言,例如链表的形式。AbstractSequentialList为链表形式的顺序访问提供了基本实现。AbstractList提供了默认的随机访问数据的iterator。AbstractList继承自AbstractCollection,AbstractCollection为Collection提供了基本实现。

java.util.AbstractCollection

contains

AbstractCollection实现了查询是否包含某一个元素的方法。最好使用Iterator遍历集合中的元素,因为可以屏蔽集合内部元素存储的具体实现,并且根据不同的数据存储特点,优化访问策略。这里还可以正确查找null元素,需要注意的是对null元素的查询需要特别的处理,有时候自己实现方法时,往往会忽略传入参数为null时的处理,导致方法无法处理特殊情况。

    public boolean contains(Object o) {
        Iterator 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;
    }
toArray

toArray方法将容器中的元素转化为数组的形式。这里元素的复制,采用的是直接复制引用。这里还考虑到了并发运算时,元素数量在复制时产生变化的情况,当数量减少时,就用Arrays.copy()截取结果。当数量增加时,会调用finishToArray函数扩容。

    public Object[] toArray() {
        // Estimate size of array; be prepared to see more or fewer elements
        Object[] r = new Object[size()];
        Iterator 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;
    }

在finishToArray函数中,对容量每次扩展1/2+1的大小,并且会检查是否会超过设定的最大数组长度MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8。如果长度超过了限定值,则以原容量+1为底线,返回一个最大容量。最后返回的数组也会修剪掉多余的位置。

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

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

这里有一个toArray的重载,需要传入一个数组作为参数,据我观察,目的是将集合中的元素存入指定的数组,重利用已有数组的存储。如果元素过多,而目标数组容纳不下,只能重新申请数组来进行存储。

    public  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 it = iterator();

        for (int i = 0; i < r.length; i++) {
            if (! it.hasNext()) { // fewer elements than expected
                if (a == r) {
                    //利用的原数组,且元素不足,则补null表示结束
                    r[i] = null; // null-terminate
                } else if (a.length < i) {
                    //元素较少,但数量超过原始数组,只能用新数组
                    return Arrays.copyOf(r, i);
                } else {
                    //元素减少,导致原始数组可以容纳下,则copy回原始数组,折腾呗
                    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;
    }
removeAll

removeAll方法删除目标集合中的所有重叠元素。Iterator可以做到在遍历时,安全的删除元素,而通过index循环删除则可能导致index偏移。

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

AbstractCollection的toString实现,比较令我震惊的是e == this的作用。这里为什么要判断打印的内容是否为自己本身?因为sb.append(e == this ? "(this Collection)" : e);会调用e的toString函数来生成字符串,若不加判断,则会形成无限的toString递归调用。我猜想一下,估计当时toString的实现者没有注意到该问题的存在,直到该bug出现,脑洞不大还真难想起来这个问题。

    public String toString() {
        Iterator 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(" ");
        }
    }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67735.html

相关文章

  • 通过面试题,让我们来了解Collection

    摘要:说一说迭代器通过集合对象获取其对应的对象判断是否存在下一个元素取出该元素并将迭代器对象指向下一个元素取出元素的方式迭代器。对于使用容器者而言,具体的实现不重要,只要通过容器获取到该实现的迭代器的对象即可,也就是方法。 前言 欢迎关注微信公众号:Coder编程获取最新原创技术文章和相关免费学习资料,随时随地学习技术知识!** 本章主要介绍Collection集合相关知识,结合面试中会提到...

    HelKyle 评论0 收藏0
  • 容器之Collection、Iterable、List、Vector(Stack)分析(三)

    摘要:容器相关的操作及其源码分析说明本文是基于分析的。通常,我们通过迭代器来遍历集合。是接口所特有的,在接口中,通过返回一个对象。为了偷懒啊,底层使用了迭代器。即返回的和原在元素上保持一致,但不可修改。 容器相关的操作及其源码分析 说明 1、本文是基于JDK 7 分析的。JDK 8 待我工作了得好好研究下。Lambda、Stream。 2、本文会贴出大量的官方注释文档,强迫自己学英语,篇幅...

    liaosilzu2007 评论0 收藏0
  • 说一说java.util.Arrays$ArrayList

    摘要:值得注意的是,的迭代器是否要调用方法是由要删除的目标是否数组的元素决定的,如果不存在这样的元素,则下面的代码并不会出现异常, java.util.Arrays$ArrayList(下文:Arrays$ArrayList)是java.util.Arrays的私有静态内部类,他实现的接口,继承的父类几乎和java.util.ArrayList(下文:ArrayList)相同,既然是私有的,...

    linkin 评论0 收藏0
  • Java笔记-容器源码(持续更新)

    摘要:加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行操作即重建内部数据结构,从而哈希表将具有大约两倍的桶数。成员变量每个对由封装,存在了对象数组中。 虽是读书笔记,但是如转载请注明出处 http://segmentfault.com/blog/exploring/ .. 拒绝伸手复制党 LinkedLis...

    mrli2016 评论0 收藏0
  • 浅析guava容器multimap

    摘要:它主要做了件事初始化容器,并将元素添加到容器里维护这样我们再调用的方法直接就返回了,不需要再次遍历和统计的过程。维护实时的维护,及时删除总结整体上是对底层的二次封装,很好的处理了各种细节,比如子容器的判空处理,的计算效率,的维护等。 在日常开发中我们通常有需要对 List 容器进行分组的情况,比如对下面的list数据根据name字段来进行分组: [ { date...

    yy13818512006 评论0 收藏0

发表评论

0条评论

Donald

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<