JDK1.8源码泛读之Arrays
jdk1.8包含的常用集合工具类,一般包括两个:
数组工具类:`java.util.Arrays `
结合工具类:`java.util.Collections`
今天就结合源码对`java.util.Arrays `的功能进行总结说明。
1、排序方法 `sort`
- 8种基本类型数组排序
方法直接调用`java.util.DualPivotQuicksort`(双轴快速排序类)的相关方法进行排序。
而在该类的内部,又根据数组的大小和有序性的好坏,选择适合的排序算法。比如数组小于指定阈值,则采用使用双轴快排,如果顺序连续性好,直接使用TimSort算法,顺序连续性不好的数组直接使用了 双轴快排 + 成对插入排序。具体过程本文不叙述。可以参考:
- object[]数组排序
对对象类型的数组的排序,其过程又有别于基本类型,jdk会借助于参数:LegacyMergeSort.userRequested
进行排序算法的选择,如果开启,那么采用传统的归并排序,否则采用[TimSort]的算法。
- 并行排序方法`parallelSort`
parallelsort会把array分成不同的子数组,每个子数组用sort进行排序,最后再合并排序;整个过程用ForkJoin
common pool(java.util.concurrent.ForkJoinPool)进行并发操作
2、并行累积方法`parallelPrefix`
使用提供的函数并行累积给定数组中的每个元素。例如,如果数组最初保存[2, 1, 0, 3]并且操作执行加法,则返回时数组成立[2, 3, 3, 6]。并行前缀计算通常比大型数组的顺序循环更有效。
public static int[] intArrays ={55,45,23,11,10,88,102,99}; public static void parallelPrefix(){ System.out.println("*******调用前*******"); System.out.println(Arrays.toString(intArrays)); Arrays.parallelPrefix(intArrays,(x,y)->(x+y)); System.out.println("*******调用后*******"); System.out.println(Arrays.toString(intArrays)); }
1 *******排序前******* 2 [55, 45, 23, 11, 10, 88, 102, 99] 3 *******排序后******* 4 [55, 100, 123, 134, 144, 232, 334, 433] 5 55=55 6 100=55+45 7 123=55+45+23 8 ... 9 433=55+...+99
3、二叉查找`binarySearch`
在已排序数组中,找出指定值的元素所在的位置(如果为正数,返回从0开始的index,如果为负数,其绝对值-1为所找key最近的数组元素)。
1 private static int binarySearch0(int[] a, int fromIndex, int toIndex, 2 int key) { 3 int low = fromIndex; 4 int high = toIndex - 1; 5 6 while (low <= high) { 7 int mid = (low + high) >>> 1; 8 int midVal = a[mid]; 9 10 if (midVal < key) 11 low = mid + 1; 12 else if (midVal > key) 13 high = mid - 1; 14 else 15 return mid; // key found 16 } 17 return -(low + 1); // key not found. 18 }
4、相等性判断`equals`
数组等于比较,过程相对简单:
1 public static boolean equals(int[] a, int[] a2) { 2 //同一个对象,即引用地址一样 3 if (a==a2) 4 return true; 5 //一个为null 6 if (a==null || a2==null) 7 return false; 8 //长度不等,直接返回 9 int length = a.length; 10 if (a2.length != length) 11 return false; 12 //每个元素比较,有不同就返回 13 for (int i=0; i<length; i++) 14 if (a[i] != a2[i]) 15 return false; 16 17 return true; 18 }
5、批量赋值`fill`
填充数据指定或者全部位置的值为指定值,相对简单。
1 public static void fill(long[] a, long val) { 2 for (int i = 0, len = a.length; i < len; i++) 3 a[i] = val; 4 }
6、复制`copyOf`
对数组元素进行复制,其中返回的是一个新的数组,但是数组元素还是对原先数组堆对象的引用(即元素浅复制)。
该方法底层调用的是native方法,进行快速复制:`system.arraycopy`
1 //测试示例 2 ArraysDemo[] demos ={new ArraysDemo("a"),new ArraysDemo("b"),new ArraysDemo("c"),new ArraysDemo("d"),new ArraysDemo("e")}; 3 4 ArraysDemo[] newDemos = Arrays.copyOf(demos,demos.length); 5 6 //数组对象深复制 返回false 7 System.out.println(demos==newDemos); 8 //数组元素浅复制,元素引用的还是同一个地址,返回true 9 System.out.println(demos[1]==newDemos[1]); 10 11 newDemos[0] = new ArraysDemo("f"); 12 //地址改变 返回false 13 System.out.println(demos[0]==newDemos[0]); 14 //元素内容改变 15 newDemos[1].setClassVer("bb"); 16 //原始对象同时改变 打印bb 17 System.out.println(demos[1].getClassVer());
7、范围复制`copyOfRange`
数组的局部复制,最终调用6的copyOf方法。
8、返回集合`asList`
返回Arrays内部类ArrayList<>对象
1 private static class ArrayList<E> extends AbstractList<E> 2 implements RandomAccess, java.io.Serializable
相比较于java.util.ArrayList,缺少List接口的相关方法。
1 ``` 2 public class ArrayList<E> extends AbstractList<E> 3 implements List<E>, RandomAccess, Cloneable, java.io.Serializable
所以List接口的相关方法无法调用。正如《阿里巴巴java开发规约》所描述的:
使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常。
asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法,只是转换接口,后台数据仍旧是数组。
1 public static <T> List<T> asList(T... a) { 2 return new ArrayList<>(a); 3 } 4 5 //测试,异常 UnsupportedOperationException 6 List list = Arrays.asList(intArrays); 7 list.add(2);
其实asList内部是调用了抽闲类AbstractList的add方法,但内部类并没有重载实现。
所以:
1 public void add(int index, E element) { 2 throw new UnsupportedOperationException(); 3 }
9、hash计算`hashCode`
获取数组的hashcode,其内部是数组元素的hashcode的拼装,根据类型不同又有不同的拼装方式。
10、并行遍历迭代器`spliterator`
Spliterator可以理解为Iterator的Split版本(但用途要丰富很多)。使用Iterator的时候,我们可以顺序地遍历容器中的元素,使用Spliterator的时候,我们可以将元素分割成多份,分别交于不于的线程去遍历,以提高效率。使用 Spliterator 每次可以处理某个元素集合中的一个元素 — 不是从 Spliterator 中获取元素,而是使用 tryAdvance() 或 forEachRemaining() 方法对元素应用操作。但Spliterator 还可以用于估计其中保存的元素数量,而且还可以像细胞分裂一样变为一分为二。这些新增加的能力让流并行处理代码可以很方便地将工作分布到多个可用线程上完成。
11、流式处理`stream`
1 public static void stream(){ 2 String [] strArray = new String[] {"a", "b", "c","d"}; 3 4 //过滤操作,过滤掉为a的字符串 5 List afterFilterLists =Arrays.stream(strArray).filter(s -> !s.equals("a")) 6 .collect(Collectors.toList()); 7 8 System.out.println(afterFilterLists); 9 //输出[b, c, d] 10 11 //foreach操作 输出 a b c d 12 Arrays.stream(strArray).forEach(s-> System.out.println(s)); 13 14 }