【Java杂记】工具类:数组工具类 Arrays
在文章开头,我们先看看工具类通用的特征:
- 构造器必须是私有的。这样的话,工具类就无法被 new 出来,因为工具类在使用的时候,无需初始化,直接使用即可,所以不会开放出构造器出来。
- 工具类的工具方法必须被 static、final 关键字修饰。这样的话就可以保证方法不可变,并且可以直接使用,非常方便。
我们需要注意的是,尽量不在工具方法中,对共享变量有做修改的操作访问(如果必须要做的话,必须加锁),因为会有线程安全的问题。除此之外,工具类方法本身是没有线程安全问题的,可以放心使用。
Arrays 主要对数组提供了一些高效的操作,比如说排序、查找、填充、拷贝、相等判断等等
1.Arrays.toString()
将数组转化为字符串,也可以看做是输出当前数组所有元素。下面是 toString 方法的源码:
// 这里除了int外,Arrays还提供了 double、long、char等...
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]);
// 如果数组遍历完了,加上右边],然后toString转为String
if (i == iMax)
return b.append(']').toString();
b.append(", ");
}
}
2.Arrays.sort()
数组排序, 使用了双轴快速排序算法,效率高
排序算法分为以下几种情况:
- 如果数组长度大于等于286且连续性好(有序程度高),就用归并排序
- 如果大于等于286且连续性不好(有序程度低),就用双轴快速排序
- 如果长度小于286且大于等于47,就用双轴快速排序
- 如果长度小于47,就用插入排序
- 入参支持 int、long、double 等各种基本类型的数组
同样支持自定义类的数组,但必须要有比较器
- 实现Comparable接口,重写compareTo方法
- 传入Comparator比较器,重写comparing方法
下面是一个自定义类型的代码示例:
// 自定义类
class SortDTO {
private String sortTarget;
public SortDTO(String sortTarget) {
this.sortTarget = sortTarget;
}
public String getSortTarget() {
return sortTarget;
}
@Override
public String toString() {
return "SortDTO{" +
"sortTarget='" + sortTarget + '\'' +
'}';
}
}
public void testSort(){
List<SortDTO> list = new ArrayList<SortDTO>(){
{
add(new SortDTO("test01"));
add(new SortDTO("test03"));
add(new SortDTO("test02"));
}
};
// 先把数组的大小初始化成 list 的大小,保证能够正确执行 toArray
SortDTO[] array = new SortDTO[list.size()];
list.toArray(array);
System.out.println("排序之前:" + Arrays.toString(array));
// sort 需要传入自定义比较器
// Arrays.sort(array, (a, b) -> {
// return a.getSortTarget().compareTo(b.getSortTarget());
// });
Arrays.sort(array, Comparator.comparing(SortDTO::getSortTarget));
System.out.println("排序之后:" + Arrays.toString(array));
}
执行结果
排序之前:[SortDTO{sortTarget='test01'}, SortDTO{sortTarget='test03'}, SortDTO{sortTarget='test02'}]
排序之后:[SortDTO{sortTarget='test01'}, SortDTO{sortTarget='test02'}, SortDTO{sortTarget='test03'}]
3.Arrays.binarySearch()
二分查找。
- 支持的入参类型非常多,如 byte、int、long 各种类型的数组
- 若被搜索的数组是无序的,一定要先排序,否则二分搜索很有可能搜索不到
返回参数是查找到的对应数组下标的值,如果查询不到,则返回负数
下面是一个自定义类的代码示例:
public void testBinarySearch(){
List<SortDTO> list = new ArrayList<SortDTO>(){
{
add(new SortDTO("100"));
add(new SortDTO("300"));
add(new SortDTO("500"));
add(new SortDTO("200"));
}
};
SortDTO[] array = new SortDTO[list.size()];
list.toArray(array);
System.out.println("搜索之前:" + Arrays.toString(array));
// 必须要先排序
Arrays.sort(array, Comparator.comparing(SortDTO::getSortTarget));
System.out.println("先排序,结果为:" + Arrays.toString(array));
// 将排序好的数组传入,若是自定义类型还要传入比较器
int index = Arrays.binarySearch(array, new SortDTO("200"), Comparator.comparing(SortDTO::getSortTarget));
// 返回的index小于0表示没找到
if(index<0){
throw new RuntimeException("没有找到200");
}
System.out.println("搜索结果:" + array[index]);
}
执行结果
搜索之前:[SortDTO{sortTarget='100'}, SortDTO{sortTarget='300'}, SortDTO{sortTarget='500'}, SortDTO{sortTarget='200'}]
先排序,结果为:[SortDTO{sortTarget='100'}, SortDTO{sortTarget='200'}, SortDTO{sortTarget='300'}, SortDTO{sortTarget='500'}]
搜索结果:SortDTO{sortTarget='200'}
接下来,我们来看下二分法底层代码的实现:
二分的主要意思是每次查找之前,都找到中间值,然后拿我们要比较的值和中间值比较,根据结果修改比较的上限或者下限,通过循环最终找到相等的位置索引
// a:我们要搜索的数组,fromIndex:从那里开始搜索,默认是0; toIndex:搜索到何时停止,默认是数组大小
// key:我们需要搜索的值 c:外部比较器
private static <T> int binarySearch0(T[] a, int fromIndex, int toIndex,
T key, Comparator<? super T> c) {
// 如果比较器 c 是空的,直接使用 key 的 Comparable.compareTo 方法进行排序
// 假设 key 类型是 String 类型,String 默认实现了 Comparable 接口,
// 就可以直接使用 compareTo 方法进行排序
if (c == null) {
// 这是另外一个方法,使用内部排序器进行比较的方法
return binarySearch0(a, fromIndex, toIndex, key);
}
int low = fromIndex;
int high = toIndex - 1;
// 开始位置小于结束位置,就会一直循环搜索
while (low <= high) {
// 假设 low =0,high =10,那么 mid 就是 5,所以说二分的意思主要在这里,每次都是计算索引的中间值
int mid = (low + high) >>> 1;
T midVal = a[mid];
// 比较数组中间值和给定的值的大小关系
int cmp = c.compare(midVal, 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.
}
4.Arrays.copyOf/copyOfRange()
- 拷贝整个数组我们可以使用 copyOf 方法,如 ArrayList#add
- 拷贝部分我们可以使用 copyOfRange 方法,如 ArrayList#remove(除最后一个元素)
Arrays 的拷贝方法,实际上底层调用的是 System.arraycopy 这个 native 方法,如果你自己对底层拷贝方法比较熟悉的话,也可以直接使用。
// original 原始数组数据
// from 拷贝起点
// to 拷贝终点
public static char[] copyOfRange(char[] original, int from, int to) {
// 需要拷贝的长度
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
// 初始化新数组
char[] copy = new char[newLength];
// 调用 native 方法进行拷贝,参数的意思分别是:
// 被拷贝的数组、从数组那里开始、目标数组、从目的数组那里开始拷贝、拷贝的长度
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
5.Arrays.asList()
asList 的作用是将数组转换为 List
List list = Arrays.asList("a","b","c")
这里注意, Arrays.asList 在使用时有两个坑:
- 坑 1:数组被修改后,会直接影响到新 List 的值。
坑 2:不能对新 List 进行 add、remove 等操作,否则运行时会报 UnsupportedOperationException 错误。
public void testArrayToList(){
Integer[] array = new Integer[]{
1,2,3,4,5,6};
List
list = Arrays.asList(array); // 坑1:修改数组的值,会直接影响原 list
log.info(“数组被修改之前,集合第一个元素为:{}”,list.get(0));
array[0] = 10;
log.info(“数组被修改之前,集合第一个元素为:{}”,list.get(0));// 坑2:使用 add、remove 等操作 list 的方法时,
// 会报 UnsupportedOperationException 异常
list.add(7);
}
我们就从源码中看看这两个坑从何来:
从上图中,我们可以发现Arrays.asList 方法返回的 List 并不是 java.util.ArrayList,而是自己内部的一个静态类
- 该静态类没有自己创建一个数组,而是直接持有数组的引用,所以修改数组,该list就会改变
- 并且没有实现 add、remove 等方法,所以调用add,remove时会抛出异常
若是要变成 java.util.ArrayList 可以 new ArrayList(Arrays.asList(arr))
,即 array–> Collection —> ArrayList
还没有评论,来说两句吧...