摘要
java.util.Arrays
是 Java 标准库中的一个实用工具类,它提供了各种静态方法来操作数组,包括排序、搜索、比较、填充等。这些方法简化了对数组的操作,并且在很多情况下可以提高代码的可读性和效率。以下是关于Arrays类中提供的一些主要方法的总结。
Sort(排序)
对指定的数组进行升序排序。Collection接口和List 接口中的 sort 方法都是基于Arrays.sort方法实现的。 当你调用Arrays.sort()
时,内部逻辑会自动选择最适合输入数据类型的排序算法:
- 对于对象数组(实现了
Comparable
接口的对象),它使用ComparableTimSort
。 - 对于带有显式比较器的对象数组,它也使用TimSort,但是通过提供比较器来进行元素间的比较。
- 对于基本类型的数组(例如
int[]
,double[]
等),它使用DualPivotQuicksort。
示例代码
package person.wend.javalearnexample.util;
public class ArraysExample {
public static void main(String[] args) {
numberSort();
objectSort();
}
public static void numberSort() {
// 数字排序
int[] numbers = { 3, 1, 4, 1, 5, 9, 2, 6 };
java.util.Arrays.sort(numbers);
for (int i : numbers) {
System.out.print(i + " ");
}
System.out.println();
}
public static void objectSort(){
// 对象排序
String[] strings = { "Hello", "World", "Java" };
java.util.Arrays.sort(strings);
for (String s : strings) {
System.out.print(s + " ");
}
System.out.println();
}
}
排序算法 TimSort
TimSort是一种混合排序算法,源自合并排序和插入排序。它是由Tim Peters在2002年为Python编程语言发明的,并且后来被Java采用作为默认的排序算法(从Java 7开始)。TimSort旨在利用真实世界数据中常见的模式来提高性能。它首先识别出已经有序的数据段(称为“runs”),然后以一种高效的方式合并这些段。对于大多数实际数据集来说,TimSort的表现非常好,其平均和最坏情况下的时间复杂度都是O(n log n)。
ComparableTimSort
ComparableTimSort
是TimSort的一个变体,专门用于处理实现了Comparable
接口的对象数组或列表。这意味着数组中的元素能够自行比较大小,不需要额外提供比较器。Java的Arrays.sort(T[] a)
和List.sort(Comparator<? super T> c)
(当没有提供自定义比较器时)使用这个变体。
排序算法 DualPivotQuicksort
DualPivotQuicksort是快速排序的一种变体,由Vladimir Yaroslavskiy提出,用于改进传统的单轴快速排序。与标准的快速排序不同,它选择了两个枢轴点而不是一个,从而将数组分为三个部分。这种算法通常比传统快速排序更有效率,特别是对于包含大量重复元素的数据集。从Java 7开始,Arrays.sort()
对基本类型(如int、double等)使用了DualPivotQuicksort。
binarySearch(二分搜索)
Arrays.binarySearch()
用于在一个已经排序的数组中执行二分查找(binary search)。该方法可以对基本类型数组和对象数组进行高效的搜索。对于对象数组,要求这些对象实现了 Comparable
接口或者提供了一个 Comparator
来定义元素之间的比较规则。
示例代码
public static void binarySearch() {
// 创建一个整数数组
int[] numbers = {3, 6, 2, 8, 4, 7, 5, 1};
// 使用 binarySearch 查找元素 5
int index = Arrays.binarySearch(numbers, 5);
// 打印查找结果
System.out.println("元素 5 的索引位置为:" + index);
}
注意事项
-
数组必须是已排序的。如果数组没有排序,那么结果是不确定的,可能会返回错误的结果。
-
对于对象数组,数组中的元素类型必须实现
Comparable
接口,这样才能确定元素之间的大小关系,以便进行二分查找。如果没有实现Comparable
接口,会在运行时抛出ClassCastException
异常。
equals
Arrays.equals()
用于比较两个一维数组的内容是否相等。它会逐一比较两个数组中的元素,如果所有对应位置上的元素都相等,则返回 true
;否则返回 false
。对于多维数组,equals()
只比较顶层元素的引用,而不是递归地比较内部数组的内容。
deepEquals
Arrays.deepEquals()
方法不仅比较顶层元素,还会递归地比较多维数组中每个子数组的内容,直到所有的嵌套层级都被检查完毕。这对于包含其他数组的对象数组来说是非常有用的,因为它确保了整个结构的深度相等性。
示例代码
public static void equalsTest(){
int[] array1 = {1, 2, 3};
int[] array2 = {1, 2, 3};
int[] array3 = {3, 2, 1};
// 比较两个相同内容的一维数组
System.out.println("array1 and array2 are equal: " + Arrays.equals(array1, array2)); // true
// 比较两个不同顺序的一维数组
System.out.println("array1 and array3 are equal: " + Arrays.equals(array1, array3)); // false
// 对于对象类型的数组,比较的是对象的内容(假设实现了正确的 equals 方法)
String[] stringArray1 = {"hello", "world"};
String[] stringArray2 = {"hello", "world"};
System.out.println("stringArray1 and stringArray2 are equal: " + Arrays.equals(stringArray1, stringArray2)); // true
Integer[][] array4 = {{1, 2}, {3, 4}};
Integer[][] array5 = {{1, 2}, {3, 4}};
Integer[][] array6 = {{1, 2}, {4, 3}};
// 使用 Arrays.deepEquals() 比较两个相同内容的二维数组
System.out.println("array1 and array2 are deeply equal: " + Arrays.deepEquals(array4, array5)); // true
// 使用 Arrays.deepEquals() 比较两个不同内容的二维数组
System.out.println("array1 and array3 are deeply equal: " + Arrays.deepEquals(array4, array6)); // false
// 对于更复杂的数据结构,比如包含其他数组的对象数组
Object[] complexArray1 = new Object[]{"hello", new Integer[]{1, 2}, new String[]{"a", "b"}};
Object[] complexArray2 = new Object[]{"hello", new Integer[]{1, 2}, new String[]{"a", "b"}};
System.out.println("complexArray1 and complexArray2 are deeply equal: " + Arrays.deepEquals(complexArray1, complexArray2)); // true
}
fill
Arrays.fill()
方法用于将指定的值填充到整个数组或数组的一部分。它是一个非常方便的方法,可以快速初始化数组或更新现有数组的内容,而不需要使用循环来逐个设置元素。
示例代码
public static void fillTest() {
// 创建一个整型数组并用 7 填充
int[] numbers = new int[5];
Arrays.fill(numbers, 7);
System.out.println("Filled array with 7: " + Arrays.toString(numbers)); // [7, 7, 7, 7, 7]
// 创建一个字符串数组并用 "hello" 填充
String[] greetings = new String[3];
Arrays.fill(greetings, "hello");
System.out.println("Filled string array: " + Arrays.toString(greetings)); // [hello, hello, hello]
// 创建一个整型数组并部分填充
int[] numbers2 = {1, 2, 3, 4, 5};
Arrays.fill(numbers2, 1, 4, 8); // 从索引 1 到 3(不包括 4)的元素被替换为 8
System.out.println("Partially filled array: " + Arrays.toString(numbers2)); // [1, 8, 8, 8, 5]
// 创建一个字符数组并部分填充
char[] chars = {'a', 'b', 'c', 'd', 'e'};
Arrays.fill(chars, 2, 5, 'z'); // 从索引 2 到 4(不包括 5)的元素被替换为 'z'
System.out.println("Partially filled char array: " + Arrays.toString(chars)); // [a, b, z, z, z]
}
copyOf
Arrays.copyOf
方法用于复制指定的数组,截断或填充(如有必要),以使副本具有指定的长度。如果原数组的长度小于新数组的长度,则填充 null(对于对象数组)或相应的默认值(对于基本类型数组,比如 0、false 等)。如果原数组的长度大于新数组的长度,则只复制指定长度内的元素。
copyOfRange
Arrays.copyOfRange
方法用于复制指定数组的指定范围到新数组中。这个方法允许你指定开始索引(包含)和结束索引(不包含)来复制数组的一部分。
示例代码
public static void copyOfTest() {
String[] original = {"Apple", "Banana", "Cherry", "Date"};
String[] copy = Arrays.copyOf(original, 2); // 复制前两个元素
System.out.println(Arrays.toString(copy)); // 输出: [Apple, Banana]
String[] longerCopy = Arrays.copyOf(original, 6); // 原数组长度小于新长度,填充null
System.out.println(Arrays.toString(longerCopy)); // 输出: [Apple, Banana, Cherry, Date, null, null]
// copyOfRange
String[] copyRange = Arrays.copyOfRange(original, 1, 3); // 复制索引1到索引2(不包括索引3)的元素
System.out.println(Arrays.toString(copyRange)); // 输出: [Banana, Cherry]
}
asList
Arrays.asList()
用于将一个数组转换为一个固定大小的List
集合。这个方法提供了一种便捷的方式,使得可以使用一些List
接口的方法来操作数组元素。
示例代码
- 基本数据类型数组转换:对于基本数据类型数组,如
int[]
、double[]
等,基本数据类型数组在作为可变参数传递时,被视为一个单独的对象。
int[] intArray = {1, 2, 3};
List<int[]> list = Arrays.asList(intArray);
// 此时list中只有一个元素,就是intArray这个数组本身
System.out.println(list.size()); // 输出1
- 如果要将基本数据类型数组转换为包含基本数据类型元素的
List
,可以先将基本数据类型数组转换为对应的包装类型数组,例如:
Integer[] integerArray = new Integer[intArray.length];
for (int i = 0; i < intArray.length; i++) {
integerArray[i] = intArray[i];
}
List<Integer> integerList = Arrays.asList(integerArray);
System.out.println(integerList.size()); // 输出3
- 对象数组转换示例:对于对象数组,使用就比较直接。例如,对于
String
数组:
String[] stringArray = {"apple", "banana", "cherry"};
List<String> stringList = Arrays.asList(stringArray);
System.out.println(stringList.size()); // 输出3
System.out.println(stringList.get(1)); // 输出banana
hashCode
Arrays.hashCode()
方法为单层数组计算哈希码。该方法会遍历数组中的每一个元素,并将这些元素的哈希码组合成一个单一的哈希值。这个过程确保了即使两个不同的数组实例包含相同的元素序列,它们也会有相同的哈希码,从而可以正确地用作哈希表中的键或集合中的成员。
deepHashCode
Arrays.deepHashCode()
方法则进一步处理多维数组,递归地计算每个子数组的哈希码。这对于包含其他数组的对象数组特别有用,因为它不仅考虑顶层元素,还深入到每一层嵌套的数组,确保整个结构的内容都被纳入哈希码的计算之中。
示例代码
public static void hashCodeTest(){
// 计算字符串数组的哈希码
String[] array1 = {"apple", "banana", "cherry"};
System.out.println("Hash code of array1: " + Arrays.hashCode(array1));
// 对于基本类型数组,使用相应的 hashCode 方法
int[] numbers = {1, 2, 3};
System.out.println("Hash code of numbers: " + Arrays.hashCode(numbers));
// 示例 2 deepHashCode
// 创建一个二维字符串数组并计算其深哈希码
String[][] array2D = {{"apple", "banana"}, {"cherry", "date"}};
System.out.println("Deep hash code of 2D array: " + Arrays.deepHashCode(array2D));
// 对于更复杂的数据结构,比如包含其他数组的对象数组
Object[] complexArray = new Object[]{"hello", new Integer[]{1, 2}, new String[]{"a", "b"}};
System.out.println("Deep hash code of complex array: " + Arrays.deepHashCode(complexArray));
}
toString
toString
方法用于返回指定数组内容的字符串表示形式。该方法适用于任何类型的数组,包括基本类型数组和对象数组。
deepToString
deepToString
方法用于返回多维数组(即数组的数组)的字符串表示形式。当数组是嵌套的,toString
方法只能打印出最外层数组的引用,而 deepToString
能够递归地打印出所有层的元素。
示例代码
public static void toStringTest(){
int[] array = {1, 2, 3, 4, 5};
String arrayString = Arrays.toString(array);
System.out.println(arrayString); // 输出: [1, 2, 3, 4, 5]
int[][] deepArray = {{1, 2}, {3, 4, 5}, {6}};
String deepArrayString = Arrays.deepToString(deepArray);
System.out.println(deepArrayString); // 输出: [[1, 2], [3, 4, 5], [6]]
}
JDK 8 新增方法
parallelSort(并发排序)
Arrays.parallelSort
是 Java 8+ 标准库提供的一个方法,用于对数组进行并行排序。它利用了 Java 的 Fork/Join 框架来实现多线程并行处理,可以加速大数组的排序操作。与 Arrays.sort()
不同,parallelSort()
方法旨在充分利用多核处理器的能力来提高性能。
示例代码
import java.util.Arrays;
public class ParallelSortExample {
public static void main(String[] args) {
int[] array = {5, 2, 8, 1, 9, 3};
// 使用 parallelSort() 对数组进行排序
Arrays.parallelSort(array);
// 输出排序后的数组
System.out.println(Arrays.toString(array));
}
}
ArraysParallelSortHelpers
这个类被用于辅助Arrays.parallelSort的实现,
包含了用于并行排序的辅助方法和静态内部类。ArraysParallelSortHelpers
主要用于将排序任务分割成更小的任务,并在多个处理器核心上并行执行它们。
-
任务分割:
ArraysParallelSortHelpers
将大数组分割成多个子数组,每个子数组可以由一个单独的线程进行排序。 -
任务合并:在子数组被排序后,
ArraysParallelSortHelpers
提供了合并这些子数组的方法,以生成最终排序好的大数组。 -
工作窃取算法:为了提高效率,
ArraysParallelSortHelpers
使用了工作窃取算法。这意味着空闲的线程可以从其他忙碌线程的任务队列中“窃取”任务。
parallelSort 与Sort 对比
-
单线程 vs. 多线程:
sort()
是一个单线程方法,而parallelSort()
则会创建多个线程来进行并行排序。 -
性能差异:对于较小的数组,
sort()
可能比parallelSort()
更快,因为后者需要额外的开销来设置和管理线程池。然而,对于较大的数组,parallelSort()
可以通过多线程并发执行来提供更好的性能。 -
排序算法:
sort()
对于原始类型(如 int, long 等)使用双轴快速排序算法,对于对象数组则使用 TimSort 算法。parallelSort()
在内部根据情况选择适当的算法,并且为了并行化可能会使用不同的策略。 -
使用场景
-
sort()
:适用于小规模数据集或者当你的应用程序已经有很多活跃线程时,避免引入更多线程带来的额外开销。 -
parallelSort()
:适用于大规模数据集并且运行在多核处理器上的环境,能够从多线程中受益以减少排序时间。
-
parallelPrefix(前缀计算)
Arrays.parallelPrefix()
方法是 Java 8 中引入的一个并行数组操作方法。它用于对给定的数组进行前缀计算,计算方式是基于一个指定的二元操作符。该方法可以利用多核处理器的并行计算能力,在合适的场景下提高计算效率。
工作原理
-
假设我们有一个数组
[a, b, c, d]
,在执行parallelPrefix
操作时,对于二元操作符op
,计算过程如下: -
首先,计算第一个元素和第二个元素的结果,即
newArray[1]=op.apply(array[0], array[1])
,新数组的第一个元素通常保持不变(取决于操作符)。 -
然后,计算新的第二个元素和第三个元素的结果,即
newArray[2]=op.apply(newArray[1], array[2])
。 -
以此类推,最后得到一个经过前缀计算后的数组。例如,如果操作符是加法,对于数组
[1, 2, 3, 4]
,计算后的数组为[1, 1 + 2, (1 + 2)+3, ((1 + 2)+3)+4]
,即[1, 3, 6, 10]
。
示例代码
public static void parallelPrefixTest(){
// 创建一个整数数组
int[] numbers = {1, 2, 3, 4, 5};
// 使用 parallelPrefix 计算数组的前缀和
Arrays.parallelPrefix(numbers, (left, right) -> left + right);
// 打印计算结果
System.out.println(Arrays.toString(numbers));
}
setAll(设置新值)
Arrays.setAll()
方法使用单线程来顺序地为数组中的每一个位置设置一个新值。它接受一个数组和一个 IntUnaryOperator
接口的实例作为参数,后者定义了一个操作,该操作接收一个整数参数(即数组索引)并返回一个新的整数值来填充对应的数组位置。
parallelSetAll(并行设置新值)
Arrays.parallelSetAll()
方法与 setAll()
类似,但它利用 Java 的 Fork/Join 框架来并行化任务执行,从而加速大数组的填充过程。这个方法会尝试将数组分割成多个部分,并在不同的线程上同时应用生成器函数来填充这些部分。对于小数组或者只有一个处理器核心的情况下,parallelSetAll()
可能不会提供性能优势,甚至可能比 setAll()
更慢,因为并行处理引入了额外的开销。
示例代码
public static void setAllExample(){
// 使用 setAll 初始化一个整型数组,每个元素是其索引的平方
int[] squares = new int[5];
Arrays.setAll(squares, i -> i * i);
System.out.println("Squares: " + Arrays.toString(squares)); // [0, 1, 4, 9, 16]
// 使用 setAll 初始化一个字符串数组,每个元素是其索引的字符表示
String[] indexedStrings = new String[3];
Arrays.setAll(indexedStrings, Integer::toString);
System.out.println("Indexed strings: " + Arrays.toString(indexedStrings)); // [0, 1, 2]
// 使用 parallelSetAll 并行初始化一个大整型数组,每个元素是其索引的立方
int[] cubes = new int[1000000];
Arrays.parallelSetAll(cubes, i -> i * i * i);
// 打印前五个元素作为示例
System.out.println("First five cubes: " + Arrays.toString(Arrays.copyOfRange(cubes, 0, 5))); // [0, 1, 8, 27, 64]
// 对于对象数组,可以使用更复杂的生成逻辑
String[] complexObjects = new String[10];
Arrays.parallelSetAll(complexObjects, i -> "Object-" + i);
System.out.println("Complex objects: " + Arrays.toString(complexObjects));
}
spliterator
spliterator
是 Java 8 中引入的一个方法,用于获取数组的可拆分迭代器(Spliterator)。Spliterator 是一个用于遍历和分割数据源的接口,它可以用于并行处理数据。Arrays
类中的spliterator
方法提供了一种机制,使得可以将数组元素以一种能够支持并行处理的方式进行迭代。
-
该方法返回一个
Spliterator
接口的实现对象,这个对象可以用于遍历数组元素。它支持多种操作,如tryAdvance
(逐个元素尝试前进并执行操作)、forEachRemaining
(对剩余元素执行操作)等。 -
更重要的是,Spliterator 可以被分割,这对于并行处理非常关键。例如,在并行流处理中,可以将数据源分割成多个部分,每个部分可以由不同的线程处理,从而提高处理效率。它有一个
trySplit
方法,用于尝试将 Spliterator 分割成两个部分。如果分割成功,返回一个新的 Spliterator 代表分割后的一部分,原 Spliterator 则代表另一部分;如果无法分割(如数组元素太少等情况),则返回null
。
示例代码
public static void spliteratorExample() {
// 创建一个整数数组
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 使用 spliterator() 方法创建一个 Spliterator 对象
java.util.Spliterator<Integer> spliterator = Arrays.spliterator(numbers);
// 使用 tryAdvance() 方法遍历 Spliterator
System.out.println("Traversing the Spliterator:");
spliterator.tryAdvance(System.out::println);
// 使用 forEachRemaining() 方法遍历剩余元素
spliterator.forEachRemaining(System.out::println);
// 使用 trySplit() 方法拆分 Spliterator
java.util.Spliterator<Integer> split = spliterator.trySplit();
// 使用 tryAdvance() 方法遍历拆分后的 Spliterator
System.out.println("Traversing the split Spliterator:");
if (split != null) {
split.tryAdvance(System.out::println);
split.forEachRemaining(System.out::println);
}
// 使用 estimateSize() 方法获取剩余元素数量
System.out.println("Remaining elements in the original Spliterator: " + spliterator.estimateSize());
// 使用 characteristics() 方法获取 Spliterator 的特性
System.out.println("Characteristics of the original Spliterator: " + spliterator.characteristics());
// 使用 getExactSizeIfKnown() 方法获取确切元素数量
System.out.println("Exact size of the original Spliterator: " + spliterator.getExactSizeIfKnown());
// 使用 hasCharacteristics() 方法检查 Spliterator 的特性
System.out.println("The original Spliterator is SIZED: " + spliterator.hasCharacteristics(java.util.Spliterator.SIZED));
}
stream
stream
方法是 Java 8 中非常重要的一个特性相关的方法,它允许将数组转换为流(Stream)。流是一种用于处理元素序列的高级抽象,支持一系列的中间操作(如filter
、map
等)和终端操作(如forEach
、reduce
等),可以让开发者以一种更函数式的方式来处理数据。
示例代码
public static void streamTest(){
String[] stringArray = {"apple", "banana", "cherry", "date"};
Stream<String> stream = Arrays.stream(stringArray);
stream.filter(str -> str.length() > 5)
.map(String::toUpperCase)
.forEach(System.out::println);
}
在这个示例中,首先将String
数组stringArray
转换为Stream<String>
,然后使用filter
操作过滤出长度大于5
的字符串(这里只有banana
和cherry
),接着使用map
操作将过滤后的字符串转换为大写形式,最后使用forEach
操作将结果打印出来,输出为BANANA
和CHERRY
。
JDK 9 新增方法
compare
Arrays.compare()
方法用于比较两个数组。它会根据数组中元素的顺序,从第一个元素开始逐个比较,直到找到不同的元素或者遍历完整个数组。这个方法返回一个整数值来表示两个数组的大小关系,返回值的规则遵循Comparator
接口的一般约定。
compareUnsigned
Arrays.compareUnsigned()
方法主要用于无符号比较两个数组,特别是在处理无符号数据类型(如byte
、short
、int
、long
)时非常有用。它和compare
方法类似,但在比较过程中会将数据视为无符号数。
示例代码
public static void compareTest(){
int[] array1 = {1, 2, 3};
int[] array2 = {1, 2, 4};
int result = Arrays.compare(array1, array2);
System.out.println("比较结果: " + result);
// 示例 2 compareUnsigned
int result2 = Arrays.compareUnsigned(array1, array2);
System.out.println("比较结果(无符号比较): " + result2);
}
mismatch
mismatch()
方法用于比较两个数组,并找到第一个不匹配(即元素值不同的位置)。如果两个数组在所有对应位置上的元素都相等,则返回 -1
;否则,返回第一个不同元素的索引。对于长度不同的数组,mismatch()
会返回较短数组的长度,因为超出部分默认视为不匹配。
mismatch()
方法适用于基本类型数组和对象数组,并且能够处理多维数组的情况。它提供了一种高效的方式来定位两个数组之间的差异点,这对于调试、测试以及数据验证等场景非常有用。
示例代码
public static void missMatchTest() {
// 创建两个整型数组进行比较
int[] array1 = {1, 2, 3, 4, 5};
int[] array2 = {1, 2, 0, 4, 5};
// 使用 mismatch 查找第一个不匹配的位置
int index = Arrays.mismatch(array1, array2);
System.out.println("First mismatch at index: " + index); // 输出 2,因为第三个元素不同
// 如果两个数组完全相同
int[] array3 = {1, 2, 3, 4, 5};
index = Arrays.mismatch(array1, array3);
System.out.println("First mismatch at index: " + index); // 输出 -1,表示没有不匹配的地方
}
参考文献/AIGC
通义tongyi.ai_你的全能AI助手-通义千问
豆包
相关推荐
JavaUsefulMode: 基于Java 语言的自定义实用工具集
JavaUsefulMode是小编编写Java方向学习专栏时的代码示例汇总总结,其中内容包含了该篇文章所涉及的Java代码示例。感兴趣的小伙伴可以直接下载学习。
Wend看源码-Java.util 工具类学习(上)-CSDN博客
Wend看源码-Java.util 工具类学习(下)-CSDN博客
Wend看源码-Java-Collections 工具集学习-CSDN博客
Wend看源码-Java-Executor异步执行器学习-CSDN博客
Wend看源码-Java-fork/Join并行执行任务框架学习-CSDN博客