Collections、Arrays工具类源码解析

272 阅读10分钟

Collections工具类

Collections 是 Java官方提供的操作Set、List、Map等集合的工具类。

该工具类中包含一些集合排序、查找等一系列操作的方法。

构造方法和属性

private Collections() {
}

构造方法私有,说明不对外提供,一般我们用到的时候都是用Collections提供的静态方法即可。

private static final int BINARYSEARCH_THRESHOLD   = 5000;	// 二分查找阈值
private static final int REVERSE_THRESHOLD        =   18;	// 反转集合阈值
private static final int SHUFFLE_THRESHOLD        =    5;	// 随机排序阈值
private static final int FILL_THRESHOLD           =   25;	// 替换阈值
private static final int ROTATE_THRESHOLD         =  100;	// 旋转(右移)阈值
private static final int COPY_THRESHOLD           =   10;	// 拷贝阈值
private static final int REPLACEALL_THRESHOLD     =   11;	// replaceAll阈值
private static final int INDEXOFSUBLIST_THRESHOLD =   35;	// indexOfSubList阈值

这些属性是Collections的调优参数。通常Collections的许多算法都有两个实现,一个适用于随机访问,另一个适合顺序访问。通常随机访问在列表数据量小的适合可以获得很好的性能,这里的每个值代表了该操作使用随机访问的数据的阈值。而这些值的确定是根据以往的经验确定的,对LinkedList是很有效的。这里每个调优参数名的第一个词是它所应用的算法。

查找

  • max(Collection<? extends T> coll)

    根据元素的自然顺序,返回集合中的最大元素

    public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {
        Iterator<? extends T> i = coll.iterator();
        // 使用迭代器来操作
        T candidate = i.next();
        while (i.hasNext()) {
            T next = i.next();
            // 按照默认顺序来进行比较
            if (next.compareTo(candidate) > 0)
                candidate = next;
        }
        return candidate;
    }
    
  • max(Collection<? extends T> coll, Comparator<? super T> comp)

    根据给定比较器,返回集合中的最大元素,源码与述方法相似,不加赘述。

  • min(Collection<? extends T> coll)

    根据元素的自然顺序,返回集合中的最小元素

    public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll) {
        // 使用迭代器来操作
        Iterator<? extends T> i = coll.iterator();
        // 通过一个变量来保存最小值
       T candidate = i.next();
        while (i.hasNext()) {
            T next = i.next();
            // 通过compareTo方法来进行比较
            if (next.compareTo(candidate) < 0)
                candidate = next;
        }
        return candidate;
    }
    
  • min(Collection<? extends T> coll, Comparator<? super T> comp)

    根据给定比较器,返回集合中的最小元素,源码与述方法相似,不加赘述。

  • binarySearch(List list, T key)

    二分查找目标元素

    如果List集合支持随机访问或者集合长度小于二分查找阈值(5000),调用indexedBinarySearch()方法,否则,调用iteratorBinarySearch()方法。

    public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) {
        // 如果List集合支持随机访问或者集合长度小于二分查找阈值(5000)
        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
            return Collections.indexedBinarySearch(list, key);
        else // 否则
            return Collections.iteratorBinarySearch(list, key);
    }
    

    indexedBinarySearch(),通过访问索引的方式二分查找,效率较高。

    private static <T>
    int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
        int low = 0;
        int high = list.size()-1;
    
        while (low <= high) {
            // 使用位运算,计算中间索引值
            int mid = (low + high) >>> 1;
            // 计算中间的元素值
            Comparable<? super T> midVal = list.get(mid);
            // 进行比较
            int cmp = midVal.compareTo(key);
            if (cmp < 0)
                // 比传入的key小,在list的高位部分查找
                low = mid + 1;
            else if (cmp > 0)
                // 比传入的key大,在list的低位部分查询
                high = mid - 1;
            else
                // 相等,直接返回
                return mid; // key found
        }
        // 没有找到,返回负数
        return -(low + 1);  // key not found
    }
    

    iteratorBinarySearch(),通过迭代器遍历的方式,效率较低。

    private static <T>
    int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
    {
        int low = 0;
        int high = list.size()-1;
        // 通过ListIterator迭代器来进行查找
        ListIterator<? extends Comparable<? super T>> i = list.listIterator();
    
        while (low <= high) {
            int mid = (low + high) >>> 1;
            // 通过Collections.get方法获取中间索引处的元素值
            Comparable<? super T> midVal = get(i, mid);
            int cmp = midVal.compareTo(key);
    
            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found
    }
    

    上述iteratorBinarySearch函数用到的 get() 方法源码

    private static <T> T get(ListIterator<? extends T> i, int index) {
        T obj = null;
        // 获取下一个索引值
        int pos = i.nextIndex();
        // 循环判断获取的索引是否小于中间索引index
        // 如果小于,从前往后遍历,否则,从后往前遍历,最后返回元素值
        // 并没有采用从头开始遍历的方式。
        if (pos <= index) {
            do {
                obj = i.next();
            } while (pos++ < index);
        } else {
            do {
                obj = i.previous();
            } while (--pos > index);
        }
        return obj;
    }
    
  • frequency(Collection<?> c, Object o)

    返回目标元素在集合中出现的次数

    public static int frequency(Collection<?> c, Object o) {
        int result = 0;	// 计数
        if (o == null) {
            for (Object e : c)
                if (e == null)
                    result++;
        } else {
            for (Object e : c)
                if (o.equals(e)) // 相等计数加一
                    result++;
        }
        return result;
    }
    
  • indexOfSubList方法和lastIndexOfSubList方法

    查找集合包含子集合的下标索引,如果查找不到则返回-1。

    indexOfSubList是查找第一次出现的索引,而lastIndexOfSubList则是查找最后一次出现的索引。

    这两个方法的性能都不是太好,都是一种属于暴力搜索的算法,并且这里用到了Java中循环标签的概念。

    public static int indexOfSubList(List<?> source, List<?> target) {
        // 原集合大小
        int sourceSize = source.size();
        // 目标集合大小
        int targetSize = target.size();
        int maxCandidate = sourceSize - targetSize;
        // 如果原集合和目标集合都支持随机访问,或者原集合小于阈值
        if (sourceSize < INDEXOFSUBLIST_THRESHOLD ||
            (source instanceof RandomAccess&&target instanceof RandomAccess)) {
        nextCand:
            // 双层遍历
            for (int candidate = 0; candidate <= maxCandidate; candidate++) {
                for (int i=0, j=candidate; i<targetSize; i++, j++)
                    if (!eq(target.get(i), source.get(j)))
                        // 使用循环标签跳转至最外层
                        continue nextCand;  // Element mismatch, try next cand
                // 全部匹配,返回索引
                return candidate;  
            }
        } else {  // Iterator version of above algorithm
            ListIterator<?> si = source.listIterator();
        nextCand:
            // 使用迭代器来进行循环
            for (int candidate = 0; candidate <= maxCandidate; candidate++) {
                ListIterator<?> ti = target.listIterator();
                for (int i=0; i<targetSize; i++) {
                    if (!eq(ti.next(), si.next())) {
                        // 游标前移
                        for (int j=0; j<i; j++)
                            si.previous();
                        continue nextCand;
                    }
                }
                return candidate;
            }
        }
        // 查询不到,返回-1
        return -1;  
    }
    

排序

  • sort(List<T> list)

    对集合进行排序,其实函数内部就是调用了List集合自身的排序接口

    public static <T extends Comparable<? super T>> void sort(List<T> list) {
        list.sort(null);	// 传入的参数为null,默认是升序排序
    }
    
  • sort(List<T> list, Comparator<? super T> c)

    同样是排序,但是该方法可以传入自定义的比较器。

    public static <T> void sort(List<T> list, Comparator<? super T> c) {
        list.sort(c);
    }
    
  • swap(List<?> list, int i, int j)

    将List集合中的i处元素和j处元素交换

    public static void swap(List<?> list, int i, int j) {
        // instead of using a raw type here, it's possible to capture
        // the wildcard but it will require a call to a supplementary
        // private method
        final List l = list;
        l.set(i, l.set(j, l.get(i)));
    }
    
  • public static void reverse(List<?> list)

    反转集合中元素的顺序

    如果列表支持随机访问或者List长度小于反转阈值18,则直接采用交换操作;

    否则采用双迭代操作,一个从头遍历,一个从尾遍历,然后交换。

    public static void reverse(List<?> list) {
        int size = list.size();
        // 直接交换
        if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) {
            for (int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)
                swap(list, i, j);
        } else { // 双迭代
            // 迭代器遍历从头开始(forward)
            ListIterator fwd = list.listIterator();
            // 迭代器遍历从尾部开始(reverse)
            ListIterator rev = list.listIterator(size);
            // 遍历size/2 次
            for (int i=0, mid=list.size()>>1; i<mid; i++) {
                Object tmp = fwd.next();
                // 设置fwd下一个元素为rev前一个元素,交换
                fwd.set(rev.previous());
                rev.set(tmp);
            }
        }
    }
    
  • shuffle(List<?> list)

    对集合进行乱序排列

    1.如果列表支持随机访问或者List长度小于乱序阈值5,那么就进行交换。交换的规则是从当前列表的最后一个元素开始,依次和前面随机一个元素进行交换,这样交换整个列表,就可以认为这个列表是无序的。

    2.如果列表不满足上述条件,就将列表先转为数组,然后按照相同的方式进行交换处理,最后再将数组放回列表中即可。

    3.还有一个重载方法shuffle(List<?> list, Random rnd),可以传入指定种子数的Random。也就是说一旦指定了种子数,那么每次将会产生相同的随机数,也就相当于这种随机生成的元素就是一种伪随机。我们可以根据需要调用相应的方法。

    public static void shuffle(List<?> list, Random rnd) {
        int size = list.size();
        // 如果List的长度小于乱序阈值(SHUFFLE_THRESHOLD == 5)
       	// 或者List实现了RandomAccess接口(不是太懂)
        // 直接在原List的基础上交换排序,不会利用临时数组排序,在List长度较小的情况下可以节省空间
        if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {
            for (int i=size; i>1; i--)
                swap(list, i-1, rnd.nextInt(i));
        } else {	// 如果以上条件不满足,会生成一个临时数组辅助排序
            Object arr[] = list.toArray();
            // 随机排序
            for (int i=size; i>1; i--)
                swap(arr, i-1, rnd.nextInt(i));
            // Dump array back into list
            // instead of using a raw type here, it's possible to capture
            // the wildcard but it will require a call to a supplementary
            // private method
            ListIterator it = list.listIterator();
            for (int i=0; i<arr.length; i++) {
                it.next();
                it.set(arr[i]);
            }
        }
    }
    
  • shuffle(List<?> list, Random rnd)

  • rotate(List<?> list, int distance)

    对集合进行旋转操作,实际上就是集合里的元素循环右移操作,参数distance就是右移的距离。

    public static void rotate(List<?> list, int distance) {
        // List集合支持随机访问
        // 或者集合长度小于旋转阈值100
        if (list instanceof RandomAccess || list.size() < ROTATE_THRESHOLD)
            rotate1(list, distance);
        else
            rotate2(list, distance);
    }
    

    rotate方法也是分为两种情况,如果集合支持随机访问或者集合大小小于旋转的阈值,则执行rotate1操作;否则,执行rotate2操作。

    rotate1方法对集合中每个元素直接循环右移(随机访问)

    private static <T> void rotate1(List<T> list, int distance) {
        int size = list.size();
        if (size == 0)
            return;
        // 距离取余,计算实际要移动距离
        distance = distance % size;
        // 考虑有可能是负数
        if (distance < 0)
            distance += size;
        if (distance == 0)
            return;
        // 循环移动
        for (int cycleStart = 0, nMoved = 0; nMoved != size; cycleStart++) {
            T displaced = list.get(cycleStart);
            int i = cycleStart;
            do {
                // 通过distance来确定下标
                i += distance;
                if (i >= size)
                    i -= size;
                displaced = list.set(i, displaced);
                // nMoved是最终移动的次数
                nMoved ++;
            } while (i != cycleStart);
        }
    }
    

    rotate2方法,则是借助于反转方法reverse方法来进行操作的。

    private static void rotate2(List<?> list, int distance) {
        int size = list.size();
        if (size == 0)
            return;
        int mid =  -distance % size;
        if (mid < 0)
            mid += size;
        if (mid == 0)
            return;
    	// 反转前 size - distance 位 (0 < distance < size)
        reverse(list.subList(0, mid));
        // 反转后distance位
        reverse(list.subList(mid, size));
        // 整体反转
        reverse(list);
    }
    

    举个例子,比如我们要对[1,2,3,4,5,6,7,8,9]进行3位旋转,则我们旋转的方式可以是:先对前size-3位进行反转,然后再对后3位进行反转,最后整体再进行反转就可以实现旋转的操作了。其中,mid的值就是确定要前后反转的中间值。

    img

替换

  • fill(List<? super T> list, T obj)

    将集合内的所有元素全部替换成目标元素

    如果集合的长度小于阈值(FILL_THRESHOLD== 25) 或者集合支持随机访问操作 那么通过索引来赋值

    否则,通过迭代器来赋值

public static <T> void fill(List<? super T> list, T obj) {
    int size = list.size();
    // 如果集合的长度小于阈值(FILL_THRESHOLD== 25)或者集合支持随机访问操作
	// 那么通过索引来赋值
    if (size < FILL_THRESHOLD || list instanceof RandomAccess) {
        for (int i=0; i<size; i++)
            list.set(i, obj);
    } else {
        // 否则,通过迭代器来赋值
        ListIterator<? super T> itr = list.listIterator();
        for (int i=0; i<size; i++) {
            itr.next();
            itr.set(obj);
        }
    }
}
  • replaceAll(List<T> list, T oldVal, T newVal)

    使用一个新值newVal来替换集合中所有的旧值oldVal

    public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal) {
        boolean result = false;
        int size = list.size();
        // 如果支持序列化或者集合大小小于替换的阈值11
        if (size < REPLACEALL_THRESHOLD || list instanceof RandomAccess) {
            // 如果旧值为null
            if (oldVal==null) {
                // 遍历数组,将为null的都替换为新的值
                for (int i=0; i<size; i++) {
                    if (list.get(i)==null) {
                        list.set(i, newVal);
                        result = true;
                    }
                }
            } else {
                // 不为null,遍历集合,通过equals方法进行判断
                for (int i=0; i<size; i++) {
                    if (oldVal.equals(list.get(i))) {
                        list.set(i, newVal);
                        result = true;
                    }
                }
            }
        } else {
            // 获取迭代器,使用迭代器进行操作
            ListIterator<T> itr=list.listIterator();
            if (oldVal==null) {
                for (int i=0; i<size; i++) {
                    if (itr.next()==null) {
                        // 通过迭代器的set方法设置新值
                        itr.set(newVal);
                        result = true;
                    }
                }
            } else {
                for (int i=0; i<size; i++) {
                    if (oldVal.equals(itr.next())) {
                        itr.set(newVal);
                        result = true;
                    }
                }
            }
        }
        return result;
    }
    

Arrays 工具类

排序

  • sort(int[] a)

    对基本类型数组进行升序排序,当数组规模较小(小于快排阈值286),采用快速排序,规模较大时,采用TimSort排序算法。

    Timsort是一个自适应的、混合的、稳定的排序算法,融合了归并算法和二分插入排序算法的精髓。

    这个算法之所以快,是因为其充分利用了现实世界的待排序数据里面,有很多子串是已经排好序的不需要再重新排序,利用这个特性并且加上合适的合并规则可以更加高效的排序剩下的待排序序列。

    Timsort运行在部分排序好的数组里面的时候,需要的比较次数要远小于nlogn,也是远小于相同情况下的归并排序算法需要的比较次数。

    但是和其他的归并排序算法一样,最坏情况下的时间复杂度是O(nlogn)的水平。但是在最坏的情况下,Timsort需要的临时存储空间只有n/2,在最好的情况下,需要的额外空间是常数级别的。从各个方面都能够击败需要O(n)空间和稳定O(nlogn)时间的归并算法。

    public static void sort(int[] a) {
        DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
    }
    
  • sort(int[] a, int fromIndex, int toIndex)

    对基本类型数组 a[fromIndex ... toIndex] 范围内升序排序

  • sort(Object[] a)

    将对象数组按照自然顺序排序,底层使用的是Tim排序方法。

    public static void sort(Object[] a) {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a);
        else
            ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
    }
    
  • sort(Object[] a, int fromIndex, int toIndex)

    将对象数组 a[fromIndex ... toIndex] 范围内按照自然顺序排序

查找

  • binarySearch(int[] a, int fromIndex, int toIndex, int key)

    在升序的前提下,在数组 a [fromIndex ... toIndex] 范围上二分查找key,并返回key对于的索引,若未找到,则返回负数。如果想在降序数组上二分查找,应选择binarySearch(T[] a, int fromIndex, int toIndex, T key, Comparator<? super T> c)方法,该方法支持自定义比较器。

    public static int binarySearch(int[] a, int fromIndex, int toIndex, int key) {
        rangeCheck(a.length, fromIndex, toIndex);
        return binarySearch0(a, fromIndex, toIndex, key);
    }
    
    private static int binarySearch0(int[] a, int fromIndex, int toIndex, int key) {
        int low = fromIndex;
        int high = toIndex - 1;
        while (low <= high) {
            // 取中间索引
            int mid = (low + high) >>> 1;
            int midVal = a[mid];
            if (midVal < key)
                low = mid + 1;
            else if (midVal > key)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found.
    }
    

替换

  • fill(int[] a, int val)

    将数组上的所有元素替换成val

    public static void fill(int[] a, int val) {
        for (int i = 0, len = a.length; i < len; i++)
            a[i] = val;
    }
    

其他

  • copyOf(int[] original, int newLength)

    拷贝数组,返回拷贝后的数组

    public static int[] copyOf(int[] original, int newLength) {
        int[] copy = new int[newLength];
        // 调用系统的接口来拷贝数组的内容
        System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
        return copy;
    }
    
  • toString(int[] a)

    将数组转化成被中括号包裹的字符串,形如 “[1,2,3]”。

    public static String toString(int[] a) {
        if (a == null)
            return "null";
        int iMax = a.length - 1;
        if (iMax == -1)
            return "[]";
        // 创建一个StringBuilder对象来进行字符串拼接操作
        StringBuilder b = new StringBuilder();
        b.append('[');
        // 迭代拼接
        for (int i = 0; ; i++) {
            b.append(a[i]);
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }
    

参考:

  1. Java1.8-Collections源码解析