数组
在现在的stream和collections下,数组似乎没有什么优势了,但是不可否认它存在的必要。
数组的几种创建方式
// arrays/ArrayOptions.java // Initialization & re-assignment of arrays import java.util.*; import static onjava.ArrayShow.*; public class ArrayOptions { public static void main(String[] args) { // Arrays of objects: BerylliumSphere[] a; // Uninitialized local BerylliumSphere[] b = new BerylliumSphere[5]; // The references inside the array are // automatically initialized to null: show("b", b); BerylliumSphere[] c = new BerylliumSphere[4]; for(int i = 0; i < c.length; i++) if(c[i] == null) // Can test for null reference c[i] = new BerylliumSphere(); // Aggregate initialization: BerylliumSphere[] d = { new BerylliumSphere(), new BerylliumSphere(), new BerylliumSphere() }; // Dynamic aggregate initialization: a = new BerylliumSphere[]{ new BerylliumSphere(), new BerylliumSphere(), }; // (Trailing comma is optional) System.out.println("a.length = " + a.length); System.out.println("b.length = " + b.length); System.out.println("c.length = " + c.length); System.out.println("d.length = " + d.length); a = d; System.out.println("a.length = " + a.length); // Arrays of primitives: int[] e; // Null reference int[] f = new int[5]; // The primitives inside the array are // automatically initialized to zero: show("f", f); int[] g = new int[4]; for(int i = 0; i < g.length; i++) g[i] = i*i; int[] h = { 11, 47, 93 }; // Compile error: variable e not initialized: //- System.out.println("e.length = " + e.length); System.out.println("f.length = " + f.length); System.out.println("g.length = " + g.length); System.out.println("h.length = " + h.length); e = h; System.out.println("e.length = " + e.length); e = new int[]{ 1, 2 }; System.out.println("e.length = " + e.length); } } /* Output: b: [null, null, null, null, null] a.length = 2 b.length = 5 c.length = 4 d.length = 3 a.length = 3 f: [0, 0, 0, 0, 0] f.length = 5 g.length = 4 h.length = 3 e.length = 3 e.length = 2 */
唯一的不同之处就是对象数组存储的是对象的引用,而基元数组则直接存储基本数据类型的值。
// arrays/RaggedArray.java import java.util.*; public class RaggedArray { static int val = 1; public static void main(String[] args) { SplittableRandom rand = new SplittableRandom(47); // 3-D array with varied-length vectors: int[][][] a = new int[rand.nextInt(7)][][]; for(int i = 0; i < a.length; i++) { a[i] = new int[rand.nextInt(5)][]; for(int j = 0; j < a[i].length; j++) { a[i][j] = new int[rand.nextInt(5)]; Arrays.setAll(a[i][j], n -> val++); // [1] } } System.out.println(Arrays.deepToString(a)); } } /* Output: [[[1], []], [[2, 3, 4, 5], [6]], [[7, 8, 9], [10, 11, 12], []]] */
倘若你不对基元数组进行显式的初始化,它的值会自动初始化。而对象数组将被初始化为 null 。
泛型数组
一般来说,数组和泛型并不能很好的结合。你不能实例化参数化类型的数组:
Peel<Banana>[] peels = new Peel<Banana>[10]; // Illegal
泛型有类型擦除,而数组必须强制的知道保存的类型,所以不能初始化。但是可以泛型化数组本身。
// arrays/ParameterizedArrayType.java class ClassParameter<T> { public T[] f(T[] arg) { return arg; } } class MethodParameter { public static <T> T[] f(T[] arg) { return arg; } } public class ParameterizedArrayType { public static void main(String[] args) { Integer[] ints = { 1, 2, 3, 4, 5 }; Double[] doubles = { 1.1, 2.2, 3.3, 4.4, 5.5 }; Integer[] ints2 = new ClassParameter<Integer>().f(ints); Double[] doubles2 = new ClassParameter<Double>().f(doubles); ints2 = MethodParameter.f(ints); doubles2 = MethodParameter.f(doubles); } }
Arrays.fill()
// arrays/FillingArrays.java // Using Arrays.fill() import java.util.*; import static onjava.ArrayShow.*; public class FillingArrays { public static void main(String[] args) { int size = 6; boolean[] a1 = new boolean[size]; byte[] a2 = new byte[size]; char[] a3 = new char[size]; short[] a4 = new short[size]; int[] a5 = new int[size]; long[] a6 = new long[size]; float[] a7 = new float[size]; double[] a8 = new double[size]; String[] a9 = new String[size]; Arrays.fill(a1, true); show("a1", a1); Arrays.fill(a2, (byte)11); show("a2", a2); Arrays.fill(a3, 'x'); show("a3", a3); Arrays.fill(a4, (short)17); show("a4", a4); Arrays.fill(a5, 19); show("a5", a5); Arrays.fill(a6, 23); show("a6", a6); Arrays.fill(a7, 29); show("a7", a7); Arrays.fill(a8, 47); show("a8", a8); Arrays.fill(a9, "Hello"); show("a9", a9); // Manipulating ranges: Arrays.fill(a9, 3, 5, "World"); show("a9", a9); } }gedan /* Output: a1: [true, true, true, true, true, true] a2: [11, 11, 11, 11, 11, 11] a3: [x, x, x, x, x, x] a4: [17, 17, 17, 17, 17, 17] a5: [19, 19, 19, 19, 19, 19] a6: [23, 23, 23, 23, 23, 23] a7: [29.0, 29.0, 29.0, 29.0, 29.0, 29.0] a8: [47.0, 47.0, 47.0, 47.0, 47.0, 47.0] a9: [Hello, Hello, Hello, Hello, Hello, Hello] a9: [Hello, Hello, Hello, World, World, Hello] */
我们要杜绝用遍历来对数组对象添加同一个值,fill方法会在底层走指令级操作。
Arrays.setAll()
// arrays/SimpleSetAll.java import java.util.*; import static onjava.ArrayShow.*; class Bob { final int id; Bob(int n) { id = n; } @Override public String toString() { return "Bob" + id; } } public class SimpleSetAll { public static final int SZ = 8; static int val = 1; static char[] chars = "abcdefghijklmnopqrstuvwxyz" .toCharArray(); static char getChar(int n) { return chars[n]; } public static void main(String[] args) { int[] ia = new int[SZ]; long[] la = new long[SZ]; double[] da = new double[SZ]; Arrays.setAll(ia, n -> n); // [1] Arrays.setAll(la, n -> n); Arrays.setAll(da, n -> n); show(ia); show(la); show(da); Arrays.setAll(ia, n -> val++); // [2] Arrays.setAll(la, n -> val++); Arrays.setAll(da, n -> val++); show(ia); show(la); show(da); Bob[] ba = new Bob[SZ]; Arrays.setAll(ba, Bob::new); // [3] show(ba); Character[] ca = new Character[SZ]; Arrays.setAll(ca, SimpleSetAll::getChar); // [4] show(ca); } } /* Output: [0, 1, 2, 3, 4, 5, 6, 7] [0, 1, 2, 3, 4, 5, 6, 7] [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] [1, 2, 3, 4, 5, 6, 7, 8] [9, 10, 11, 12, 13, 14, 15, 16] [17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0] [Bob0, Bob1, Bob2, Bob3, Bob4, Bob5, Bob6, Bob7] [a, b, c, d, e, f, g, h] */
并行化
流 实际上可以存储到将近1000万,但是之后就会耗尽堆空间。常规的 setAll() 是有效的,但是如果我们能更快地处理如此大量的数字,那就更好了。 我们可以使用 setAll() 初始化更大的数组。如果速度成为一个问题,Arrays.parallelSetAll() 将(可能)更快地执行初始化(请记住并行性中描述的问题)。
// arrays/ParallelSetAll.java import onjava.*; import java.util.Arrays; public class ParallelSetAll { static final int SIZE = 10_000_000; static void intArray() { int[] ia = new int[SIZE]; Arrays.setAll(ia, new Rand.Pint()::get); Arrays.parallelSetAll(ia, new Rand.Pint()::get); } static void longArray() { long[] la = new long[SIZE]; Arrays.setAll(la, new Rand.Plong()::get); Arrays.parallelSetAll(la, new Rand.Plong()::get); } public static void main(String[] args) { intArray(); longArray(); } }
Arrays工具类
-
asList(): 获取任何序列或数组,并将其转换为一个 列表集合 (集合章节介绍了此方法)。
-
copyOf():以新的长度创建现有数组的新副本。
-
copyOfRange():创建现有数组的一部分的新副本。
-
equals():比较两个数组是否相等。
-
deepEquals():多维数组的相等性比较。
-
stream():生成数组元素的流。
-
hashCode():生成数组的哈希值(您将在附录中了解这意味着什么:理解equals()和hashCode())。
-
deepHashCode(): 多维数组的哈希值。
-
sort():排序数组
-
parallelSort():对数组进行并行排序,以提高速度。
-
binarySearch():在已排序的数组中查找元素。
-
parallelPrefix():使用提供的函数并行累积(以获得速度)。基本上,就是数组的reduce()。
-
spliterator():从数组中产生一个Spliterator;这是本书没有涉及到的流的高级部分。
-
toString():为数组生成一个字符串表示。你在整个章节中经常看到这种用法。
-
deepToString():为多维数组生成一个字符串。你在整个章节中经常看到这种用法。对于所有基本类型和对象,所有这些方法都是重载的。
流和数组
// arrays/StreamFromArray.java import java.util.*; import onjava.*; public class StreamFromArray { public static void main(String[] args) { String[] s = new Rand.String().array(10); Arrays.stream(s).skip(3).limit(5).map(ss -> ss + "!").forEach(System.out::println); int[] ia = new Rand.Pint().array(10); Arrays.stream(ia).skip(3).limit(5) .map(i -> i * 10).forEach(System.out::println); Arrays.stream(new long[10]); Arrays.stream(new double[10]); // Only int, long and double work: // - Arrays.stream(new boolean[10]); // - Arrays.stream(new byte[10]); // - Arrays.stream(new char[10]); // - Arrays.stream(new short[10]); // - Arrays.stream(new float[10]); // For the other types you must use wrapped arrays: float[] fa = new Rand.Pfloat().array(10); Arrays.stream(ConvertTo.boxed(fa)); Arrays.stream(new Rand.Float().array(10)); } } /* Output: eloztdv! ewcippc! ygpoalk! ljlbynx! taprwxz! 47200 61770 84790 66560 37680 */
数组排序
// arrays/Reverse.java // The Collections.reverseOrder() Comparator import onjava.*; import java.util.Arrays; import java.util.Collections; import static onjava.ArrayShow.*; public class Reverse { public static void main(String[] args) { CompType[] a = new CompType[12]; Arrays.setAll(a, n -> CompType.get()); show("Before sorting", a); Arrays.sort(a, Collections.reverseOrder()); show("After sorting", a); } } /* Output: Before sorting: [[i = 35, j = 37], [i = 41, j = 20], [i = 77, j = 79] , [i = 56, j = 68], [i = 48, j = 93], [i = 70, j = 7], [i = 0, j = 25], [i = 62, j = 34], [i = 50, j = 82] , [i = 31, j = 67], [i = 66, j = 54], [i = 21, j = 6] ] After sorting: [[i = 77, j = 79], [i = 70, j = 7], [i = 66, j = 54] , [i = 62, j = 34], [i = 56, j = 68], [i = 50, j = 82] , [i = 48, j = 93], [i = 41, j = 20], [i = 35, j = 37] , [i = 31, j = 67], [i = 21, j = 6], [i = 0, j = 25] ] */
自定义比较器
// arrays/ComparatorTest.java // Implementing a Comparator for a class import onjava.*; import java.util.Arrays; import java.util.Comparator; import static onjava.ArrayShow.*; class CompTypeComparator implements Comparator<CompType> { public int compare(CompType o1, CompType o2) { return (o1.j < o2.j ? -1 : (o1.j == o2.j ? 0 : 1)); } } public class ComparatorTest { public static void main(String[] args) { CompType[] a = new CompType[12]; Arrays.setAll(a, n -> CompType.get()); show("Before sorting", a); Arrays.sort(a, new CompTypeComparator()); show("After sorting", a); } } /* Output: Before sorting:[[i = 35, j = 37], [i = 41, j = 20], [i = 77, j = 79] , [i = 56, j = 68], [i = 48, j = 93], [i = 70, j = 7] , [i = 0, j = 25], [i = 62, j = 34], [i = 50, j = 82], [i = 31, j = 67], [i = 66, j = 54], [i = 21, j = 6] ] After sorting: [[i = 21, j = 6], [i = 70, j = 7], [i = 41, j = 20] , [i = 0, j = 25], [i = 62, j = 34], [i = 35, j = 37] , [i = 66, j = 54], [i = 31, j = 67], [i = 56, j = 68] , [i = 77, j = 79], [i = 50, j = 82], [i = 48, j = 93] ] */
并行排序
// arrays/jmh/ParallelSort.java package arrays.jmh; import onjava.*; import org.openjdk.jmh.annotations.*; import java.util.Arrays; @State(Scope.Thread) public class ParallelSort { private long[] la; @Setup public void setup() { la = new Rand.Plong().array(100_000); } @Benchmark public void sort() { Arrays.sort(la); } @Benchmark public void parallelSort() { Arrays.parallelSort(la); } }
parallelSort() 算法将大数组拆分成更小的数组,直到数组大小达到极限,然后使用普通的 Arrays .sort() 方法。然后合并结果。该算法需要不大于原始数组的额外工作空间。
parallelPrefix()并行前缀
// arrays/ParallelPrefix2.java import onjava.*; import java.util.Arrays; import static onjava.ArrayShow.*; public class ParallelPrefix2 { public static void main(String[] args) { String[] strings = new Rand.String(1).array(8); show(strings); Arrays.parallelPrefix(strings, (a, b) -> a + b); show(strings); } } /* Output: [b, t, p, e, n, p, c, c] [b, bt, btp, btpe, btpen, btpenp, btpenpc, btpenpcc] */
还是鼓励使用集合,因为低级数组实际上不太符合面向对象的思想。