ArrayUtils 源码阅读有感 :) (commons-lang3)

这两天刚好在等待分配的过程中想着创建自己的代码库的,但是后来想想世界如此之大,咱想到的东东各位大牛基本上都免费提供了,为哈不双手接上呢,鼓掌,感谢!

好了,先说个 ArrayUtils 的大概吧:

顾名思义,这货就是用来进行 array 操作的哦。不过这个工具类很大有6000行左右的说,提供的功能也就相对来说比较完备的。大概有以下几大类方法(其实一般都是 overloading):

  • EMPTY_… 数组

    这一类型的属性是不太会直接使用,一般会作为以下方法中的返回值

  • toString

    在操作数组的时候,往往我们会需要查看数组的具体值,尤其是在调试的时候,自己在进行相应的查看操作时通常会比较麻烦的。

    下面代码使用的是 JDK 自带的工具方法,也是不错的选择,不过呢在 ArrayUtils 对于 null 值是可以指定替代的值。

System.out.println(Arrays.deepToString(array))
  • hashcode

    生成一个数组的 hashCode,尤其是多维的数组哦

  • toMap

    这个应该算是创建 Map 的一个简便方案,主要是将二维数组转换为 Map,当然第二维需要有两位元素,分别作为 Map 的 key 和 value

  • toArray

    当我们有不定数量的同类型元素时便可以使用此方法,它为我们将提供的元素放入到数组中,然后返回,很大方有么有

  • clone

    对于数组的复制来说,使用这个方法最好不过了,首先 ArrayUtils 对其做了充分的 overloading,其次它的实现方式是调用数组类型本身的方法 clone(),要知道这个底层的实现是 native 方式的,那个速度可想而知了。

    当然其实比较明显的是既然 ArrayUtils 自己都调用的是数组的内置方法为啥我们还要多此一举呢?确实这里边的差距不大,但是,如果需要克隆的 array 是个 null 呢?所以呢,ArrayUtils 还是为咱们省了点事儿的,不是说优秀的程序员都爱偷懒儿么:)

  • nullToEmpty

    想起第一个属性没?它就起到作用啦,只要 array 是 null 或者 array.length() == 0 那么你就会得到这个 Empty 数组,只是它是不可更改的单元素数组罢了,可以作为一个标志来使用

  • subArray

    同样,这个方法确实我们自己实现起来也不难,不过它做了大量的判断,最后通过调用 native 方法来提升元素复制的效率

  • isSameLength

    同样,精简比较过程,能处理 array 为 null 的情形

  • getLength

    通过 native 方式获取长度

  • reverse

    overloading 了许多的反转数组的方法,而且也支持反转数组中的部分子元素

  • indexOf

    这个方法的实现其实是有超出我的猜测的。在没有看之前自己想想是不是用了什么神奇的方法呢?用的还是比较朴素的做法,逐个比对。不过呢,在其的 overloading 中有对一次性查询多个相同数据的优化方案,这个会结合最后的方法进行解释的。

  • lastIndexOf

    和上一个类似,只是顺序调了个头。

  • contains

    这个的实现方式也有点出乎我的意料,原来是调用 indexOf 来判断的。

  • toPrimitive

    将数组元素统统转换为基本类型,当数组中存在 null 时一定要指定替换的值,否则不包含替代值的方法的调用会报错的,毕竟 null 与任意基本类型都没有对应值,所以一定注意。

  • toObject

    与上面方法的功能相反

  • isEmpty

    简化了的数组判断方法

  • addAll

    绝对的将两个数组进行合并哦,null数组也能搞定,只要给它,它就能帮你把它们给结合咯

  • copyArrayGrow1

    有没有觉得这个方法的名字高大上,非常清楚以至于我差点不相信自己的眼睛了。。说实话我重来没有在方法名称上使用过阿拉伯数字的说。顾名思义,先 copy 再增大 1 个空间。当然真正实现中是先创建一个 length + 1 的新数组的,然后么在用 native 方法进行 copy。

  • add

    额,忘记说了,上边那个方法是 private 的,人家是为此方法服务的,有木有很方便,数组的第一印象应该就是长度的不可变性吧:) 可是人家 ArrayUtils 就不吃你这套,帮咱们实现了数组的加法。神奇吧,不过调用的方法就是上边的 copyArrayGrow1,是不是很坦率。

  • remove

    如同 add, remove 的就过也能让数组长度少 1,这与 add 刚好是反过来的哈

  • removeElement

    这个与上一个方法的区别在于,上一个是根据元素的 index 来删的,而下一个则是要先找到指定的数组元素时才进行删除

  • removeAll

    这个方法还是蛮有意思的,下边会有贴出源代码供共赏

  • removeElements

    此方法与上边类似,只不过参数已经是相应的 数组下标了

重点介绍 removeAll,它也是一个 overloading 型选手:

static Object removeAll(final Object array, final int... indices) {
        final int length = getLength(array);
        int diff = 0; // 需要移掉的元素数量

        if (isNotEmpty(indices)) {
            Arrays.sort(indices);//排序对于进行移除有关键性的作用,从小到大

            int i = indices.length;//总共移除的数量
            int prevIndex = length;//从数组最后开始计算
            while (--i >= 0) {//这里所做移除元素合法性的验证,以及去除掉超出数组范围的下标,重复的下标
                final int index = indices[i];
                if (index < 0 || index >= length) {
                    throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length);
                }
                if (index >= prevIndex) {
                    continue;
                }
                diff++;//只要符合情况就增加 1
                prevIndex = index;
            }
        }
        final Object result = Array.newInstance(array.getClass().getComponentType(), length - diff);//表示最终形成的新数组的长度
        if (diff < length) {//如果要移除的数量等于数组本身的长度,肯定是个空数组了
            int end = length; // 当前原数组的剩余长度
            int dest = length - diff; // 当前未填充目标数组长度
            for (int i = indices.length - 1; i >= 0; i--) {
                final int index = indices[i];//指向第 i 个需要移除的元素
                if (end - index > 1) { // 等价于 (cp > 0),end 只是长度
                    final int cp = end - index - 1;
                    dest -= cp;//目标数组接收元素的起始下标
                    System.arraycopy(array, index + 1, result, dest, cp);
                    // Afer this copy, we still have room for dest items.
                }
                end = index;
            }
            if (end > 0) {//只要移除的下标不是0时,就需要执行此 block
                System.arraycopy(array, 0, result, 0, end);
            }
        }
        return result;
    }

另外一种 removeAll 与上一个方法是类似的,就补贴代码了,而调用它的方法(overloading)中使用了一个比较有意思的算法,贴出来共赏:

 public static boolean[] removeElements(final boolean[] array, final boolean... values) {
        if (isEmpty(array) || isEmpty(values)) {
            return clone(array);//clone原数组
        }
        final HashMap<Boolean, MutableInt> occurrences = new HashMap<Boolean, MutableInt>(2); // boolean 型也就只有两种了,true , false :)
        for (final boolean v : values) { // 这里就是这个算法的预处理了,很重要的哦
            final Boolean boxed = Boolean.valueOf(v);
            final MutableInt count = occurrences.get(boxed);
            if (count == null) {
                occurrences.put(boxed, new MutableInt(1));
            } else {
                count.increment(); // 最终统计出 false, true 分别移除的数量
            }
        }
        final BitSet toRemove = new BitSet();
        //这里做一件比较有意思的事
        //我们可以出这样一道题目,给出一个数组和一组需要在数组中进行查询的数集合,让找出各自所对应的下标,而且允许有重复对象存在
        //最普通的做法是穷举需要查找的数,与数组中元素一一比较,这个基本上就是o(NM)的复杂度了
        //但是这个算法就高端点了,能力有限,还不清楚这个复杂度要咋写-_-||,大神了解的还望指点指点,感激不尽啊:)
        //不过其实本质和普通方法一样,但是呢减少了对于重复元素的穷举所消耗的时间

        for (final Map.Entry<Boolean, MutableInt> e : occurrences.entrySet()) {
            final Boolean v = e.getKey();
            int found = 0;
            // 根据重复的个数进行相应次数的查找
            for (int i = 0, ct = e.getValue().intValue(); i < ct; i++) {
                found = indexOf(array, v.booleanValue(), found);
                if (found < 0) {
                    break;
                }
                toRemove.set(found++);
            }
        }
        return (boolean[]) removeAll(array, toRemove);
    }

有错误之处还请指出,谢拉 :)

时间: 2024-10-08 01:58:26

ArrayUtils 源码阅读有感 :) (commons-lang3)的相关文章

jdk 源码阅读有感(一)String

闲暇之余阅读 jdk 源码. (一)核心属性 /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 String的核心结构,char型数组与 int 型 hash值. (二)构造器 构造器方面,由于上述两个值是不可更改的,所以直接 对 String

Netty源码阅读(一) ServerBootstrap启动

Netty源码阅读(一) ServerBootstrap启动 转自我的Github Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客户端程序.本文讲会对Netty服务启动的过程进行分析,主要关注启动的调用过程,从这里面进一步理解Netty的线程模型,以及Reactor模式. 这是我画的一个Netty启动过程中使用到的主要的类的概要类图,当然是用到的类比这个多得多,而且我也忽略了各个类的继承关系

commons-io源码阅读心得

FileCleanTracker: 开启一个守护线程在后台默默的删除文件. 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ow

Spring源码阅读:Spring JDBC 组件的设计与实现

昨天回忆了我在学习JDBC时自己设计的JDBCTemplate(写在上一篇博客中),在使用Spring过程中,有时会用到Spring给我们提供的JdbcTemplate,这里看看Spring是如何实现这个组件的. 在使用Spring JDBC是你要做的工作很少: 从上面的图上可以看出来,使用Spring JDBC,你只需要做四个工作: 1)定义连接参数:也就是定义url,driver,user,password这个几个参数,一般我们会用一个jdbc.properties文件来配置. 2)指定要执

CI框架源码阅读笔记3 全局函数Common.php

从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap引导文件都会最先引入全局函数,以便于之后的处理工作). 打开Common.php中,第一行代码就非常诡异: if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 上一篇(CI框架源码阅读笔记2 一切的入口 index

淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划

body, td { font-family: tahoma; font-size: 10pt; } 淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划 SQL编译解析三部曲分为:构建语法树,生成逻辑计划,指定物理执行计划.第一步骤,在我的上一篇博客淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树里做了介绍,这篇博客主要研究第二步,生成逻辑计划. 一. 什么是逻辑计划?我们已经知道,语法树就是一个树状的结构组织,每个节点代表一种类型的语法含义.如

JDK部分源码阅读与理解

本文为博主原创,允许转载,但请声明原文地址:http://www.coselding.cn/article/2016/05/31/JDK部分源码阅读与理解/ 不喜欢重复造轮子,不喜欢贴各种东西.JDK代码什么的,让整篇文章很乱...JDK源码谁都有,没什么好贴的...如果你没看过JDK源码,建议打开Eclipse边看源码边看这篇文章,看过的可以把这篇文章当成是知识点备忘录... JDK容器类中有大量的空指针.数组越界.状态异常等异常处理,这些不是重点,我们关注的应该是它的一些底层的具体实现,这篇

如何阅读Java源码 阅读java的真实体会

刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧,如果你从来没有学过Java,或是任何一门编程语言如C++,一开始去啃<Core Java>,你是很难从中吸收到营养的,特别是<深入Java虚拟机>这类书,别人觉得好,未必适合现在的你. 虽然Tomcat的源码很漂亮,但我绝不建议你一开始就读它.我文中会专门谈到这个,暂时不展开. 强烈

Memcache-Java-Client-Release源码阅读(之七)

一.主要内容 本章节的主要内容是介绍Memcache Client的Native,Old_Compat,New_Compat三个Hash算法的应用及实现. 二.准备工作 1.服务器启动192.168.0.106:11211,192.168.0.106:11212两个服务端实例. 2.示例代码: String[] servers = { "192.168.0.106:11211", "192.168.0.106:11212" }; SockIOPool pool =