支持快速失败的ArrayList:batchRemove

看了ArrayList源码后,发现batchRemove还是较为复杂的,所以在这里写篇文章记录一下,以免以后再看时又头疼,哈哈哈。

先以自己的理解,弄一个简单版的,由浅入深,干掉他,如果读者发现有什么不正确的或有什么问题请及时指出哦。

先使用两个list模拟数组,从list1中移除list2存在的元素。

@Test
    public void testBatchRemove() { // 遍历list1,从list1中移除list2存在的元素
        // 初始化数据
        List<Integer> list1 = new ArrayList<>(10);
        for (int i = 0; i < 5; i++) {
            list1.add(i);
        }

        // 初始化数据
        List<Integer> list2 = new ArrayList<>(10);
        list2.add(2);
        // 修改list数据只通过set方法,r和w作为数组的索引
        int r = 0, w = 0; // r:读, w:写

        /**
         * 从下面例子中可以看出
         * 在第一轮和第二轮循环中,r和w是同步增加的
         * 读会不停的往后读(即 r),但是写只会写入不被删除的数据
         * example
         * list1 = [0, 1, 2, 3, 4]
         * list2 = [2]
         * 第一轮循环结束 r = 1, w = 1 list1 = [0, 1, 2, 3, 4]
         * 第二轮循环结束 r = 2, w = 2 list1 = [0, 1, 2, 3, 4]
         * 第三轮循环结束 r = 3, w = 2 list1 = [0, 1, 2, 3, 4] 注:第三轮将不会进入if,导致w的更新比r慢一步
         * 第四轮循环结束 r = 4, w = 3 list1 = [0, 1, 3, 3, 4] 注:元素 [2] 已被删除
         * 第五轮循环结束 r = 5, w = 4 list1 = [1, 3, 4, 5, 5] 注:后面的元素将会向前推进一步
         */
        for(;r < list1.size();r++) {
            // 如果不包含,说明该位置的元素不能修改,则修改读(r)和写(w)的索引
            // 如果包含,说明该位置的元素需要修改,则只修改读(r)的索引
            if (!list2.contains(list1.get(r))) { // 搞清楚这个if就行啦!打断点看会舒服很多
                list1.set(w++, list1.get(r));
            }
            // (r + 1)为了模拟for循环结束一轮的场景,因为打印的时候r不会++,
            System.out.println(list1 + " w: " + w + " r: " + (r + 1));
        }
        // 只需要截取掉从0~w的元素即可
        // 那怎么知道修改了多少次呢? 在截取之前使用 list.size() - w 即可知道修改了多少次元素 5 - 4 = 1
        List<Integer> integers = list1.subList(0, w);
        System.out.println(integers);
    }

那问题来了,刚刚是移除list2中存在的元素,那要是想保留list2中存在的元素,怎么办?那难道还要再写一个方法吗?当然不是啦,只需要变一下就行啦~

增加变量 

complement 是否补充数组中的数据,false则删除,true则保留

来了来了,新版本

/**
     * 遍历list1,从list1中移除或只保留list2存在的元素
     */
    @Test
    public void testComplement() {
        // 是否补充数组中的数据,false则删除,true则保留
        boolean complement = false;
        // 初始化数据
        List<Integer> list1 = new ArrayList<>(10);
        for (int i = 0; i < 5; i++) {
            list1.add(i);
        }

        // 初始化数据
        List<Integer> list2 = new ArrayList<>(10);
        list2.add(2);
        // 修改list数据只通过set方法,r和w作为数组的索引
        int r = 0, w = 0; // r:读, w:写

        for(;r < list1.size();r++) {
            if (list2.contains(list1.get(r)) == complement) { // 搞清楚这个if就行啦!打断点看会舒服很多
                list1.set(w++, list1.get(r));
            }
            // (r + 1)为了模拟for循环结束一轮的场景,因为打印的时候r不会++,
            System.out.println(list1 + " w: " + w + " r: " + (r + 1));
        }

        List<Integer> integers = list1.subList(0, w);
        System.out.println(integers);
    }

继续。恩,是的,问题他又接着来了,现在有两个数组

list1 = [1, 2, 3, 4, 5, 6, 7]

list2 = [5, 6, 7]

像这样的,list1要删除list2中存在的元素,那list1中开头的第1~4个元素都不需要修改。现在这小数据不需要修改,那在生产环境中谁知道呢,哈哈,所以开头的优化下撒。

最终版本来了,这是你****的全新版本

/**
     * 遍历list1,从list1中移除或只保留list2存在的元素
     *  优化开头的操作
     *      list1.set(w++, list1.get(r));
     */
    @Test
    public void testOptimize() {
        // 是否补充数组中的数据,false则删除,true则保留
        boolean complement = false;
        // 初始化数据
        List<Integer> list1 = new ArrayList<>(10);
        for (int i = 0; i < 5; i++) {
            list1.add(i);
        }

        // 初始化数据
        List<Integer> list2 = new ArrayList<>(10);
        list2.add(3);
        // 修改list数据只通过set方法,r和w作为数组的索引
        int r = 0, w = 0; // r:读, w:写

        /**
         * 该for循环用于省略数组开头的赋值操作 :
         *      list1.set(w++, list1.get(r));
         */
        for (;r < list1.size(); r++) {
            // 如果没有要删除的操作,则直接返回即可,这样就完全省略了赋值操作
            //      list1.set(w++, list1.get(r));
            if (r == list1.size()) {
                return;
            }
            // 当需要删除是,则跳出
            if (list2.contains(list1.get(r)) != complement) {
                break;
            }
            // r 和 w同步增加
            w++;
        }

        // 经过上个for循环的优化,该for循环的第一个操作必定的删除操作
        // 由于r当前位置已经确定要被删除,所以r = r + 1,读取下一个
        for(r = r + 1;r < list1.size();r++) {
            if (list2.contains(list1.get(r)) == complement) { // 搞清楚这个if就行啦!打断点看会舒服很多
                list1.set(w++, list1.get(r));
            }
            // (r + 1)为了模拟for循环结束一轮的场景,因为打印的时候r不会++,
            System.out.println(list1 + " w: " + w + " r: " + (r + 1));
        }

        List<Integer> integers = list1.subList(0, w);
        System.out.println(integers);
    }

其实到这也差不多了,现原形!以下是JDK13的源码。

boolean batchRemove(Collection<?> c, boolean complement,
                        final int from, final int end) {
        // 校验集合是否为空
        Objects.requireNonNull(c);
        final Object[] es = elementData;
        // 优化
        int r;
        // Optimize for initial run of survivors
        for (r = from;; r++) {
            if (r == end)
                return false;
            if (c.contains(es[r]) != complement)
                break;
        }
        // 和优化版本的代码同理 r = r + 1
        int w = r++;
        try {
            for (Object e; r < end; r++)
                if (c.contains(e = es[r]) == complement)
                    es[w++] = e;
        } catch (Throwable ex) {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            System.arraycopy(es, r, es, w, end - r);
            w += end - r;
            throw ex;
        } finally {
            // 计算修改次数,应该是用来触发快速失败的
            modCount += end - w;
            // 收尾收尾
            shiftTailOverGap(es, w, end);
        }
        return true;
    }

之前看了JDK8的代码,发现有点不一样,无非是少了优化阶段,然后收尾阶段也没有封装,就这样撒。大家有什么问题的欢迎下面留言哦~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值