@@ -2477,17 +2477,270 @@ Java标准库中使用的排序算法被设计为最适合您正在排序的类
2477
2477
< ! -- Sorting in Parallel -- >
2478
2478
## 并行排序
2479
2479
2480
+ 如果排序性能是一个问题,那么可以使用 ** Java 8 parallelSort()** ,它为所有不可预见的情况(包括数组的排序区域或使用了比较器)提供了重载版本。为了查看相比于普通的sort(), ** parallelSort()** 的优点,我们使用了用来验证代码时的 ** JMH ** :
2481
+
2482
+ ```java
2483
+ // arrays/jmh/ParallelSort.java
2484
+ package arrays. jmh;
2485
+
2486
+ import onjava. * ;
2487
+ import org. openjdk. jmh. annotations. * ;
2488
+
2489
+ import java.util. Arrays ;
2490
+
2491
+ @State (Scope . Thread )
2492
+ public class ParallelSort {
2493
+ private long [] la;
2494
+
2495
+ @Setup
2496
+ public void setup () {
2497
+ la = new Rand .Plong (). array(100_000 );
2498
+ }
2499
+
2500
+ @Benchmark
2501
+ public void sort () {
2502
+ Arrays . sort(la);
2503
+ }
2504
+
2505
+ @Benchmark
2506
+ public void parallelSort () {
2507
+ Arrays . parallelSort(la);
2508
+ }
2509
+ }
2510
+ ```
2511
+
2512
+ ** parallelSort()** 算法将大数组拆分成更小的数组,直到数组大小达到极限,然后使用普通的 ** Arrays .sort()** 方法。然后合并结果。该算法需要不大于原始数组的额外工作空间。
2513
+
2514
+ 您可能会看到不同的结果,但是在我的机器上,并行排序将速度提高了大约3 倍。由于并行版本使用起来很简单,所以很容易考虑在任何地方使用它,而不是
2515
+ ** Arrays . sort ()** 。当然,它可能不是那么简单—看看微基准测试。
2516
+
2517
+
2480
2518
< ! -- Searching with Arrays . binarySearch() -- >
2481
2519
## binarySearch二分查找
2482
2520
2521
+ 一旦数组被排序,您就可以通过使用 ** Arrays . binarySearch()** 来执行对特定项的快速搜索。但是,如果尝试在未排序的数组上使用 ** binarySearch()** ,结果是不可预测的。下面的示例使用 ** Rand . Pint ** 类来创建一个填充随机整形值的数组,然后调用 ** getAsInt()** (因为 ** Rand . Pint ** 是一个 ** IntSupplier ** )来产生搜索值:
2522
+
2523
+ ```JAVA
2524
+ // arrays/ArraySearching.java
2525
+ // Using Arrays.binarySearch()
2526
+
2527
+ import onjava. * ;
2528
+
2529
+ import java.util. Arrays ;
2530
+
2531
+ import static onjava. ArrayShow . * ;
2532
+
2533
+ public class ArraySearching {
2534
+ public static void main (String [] args ) {
2535
+ Rand . Pint rand = new Rand .Pint ();
2536
+ int [] a = new Rand .Pint (). array(25 );
2537
+ Arrays . sort(a);
2538
+ show(" Sorted array" , a);
2539
+ while (true ) {
2540
+ int r = rand. getAsInt();
2541
+ int location = Arrays . binarySearch(a, r);
2542
+ if (location >= 0 ) {
2543
+ System . out. println(" Location of " + r + " is " + location + " , a[" + location + " ] is " + a[location]);
2544
+ break ; // Out of while loop
2545
+ }
2546
+ }
2547
+ }
2548
+ }
2549
+ /* Output:
2550
+ Sorted array: [125, 267, 635, 650, 1131, 1506, 1634, 2400, 2766,
2551
+ 3063, 3768, 3941, 4720, 4762, 4948, 5070, 5682,
2552
+ 5807, 6177, 6193, 6656, 7021, 8479, 8737, 9954]
2553
+ Location of 635 is 2, a[2] is 635
2554
+ */
2555
+ ```
2556
+
2557
+ 在while 循环中,随机值作为搜索项生成,直到在数组中找到其中一个为止。
2558
+
2559
+ 如果找到了搜索项,** Arrays . binarySearch()** 将生成一个大于或等于零的值。否则,它将产生一个负值,表示如果手动维护已排序的数组,则应该插入元素的位置。产生的值是 - (插入点) - 1 。插入点是大于键的第一个元素的索引,如果数组中的所有元素都小于指定的键,则是 ** a. size()** 。
2560
+
2561
+ 如果数组包含重复的元素,则无法保证找到其中的那些重复项。搜索算法不是为了支持重复的元素,而是为了容忍它们。如果需要没有重复元素的排序列表,可以使用 ** TreeSet ** (用于维持排序顺序)或 ** LinkedHashSet ** (用于维持插入顺序)。这些类自动为您处理所有的细节。只有在出现性能瓶颈的情况下,才应该使用手工维护的数组替换这些类中的一个。
2562
+
2563
+ 如果使用比较器(原语数组不允许使用比较器进行排序)对对象数组进行排序,那么在执行 ** binarySearch()** (使用重载版本的binarySearch())时必须包含相同的比较器。例如,可以修改 ** StringSorting . java** 来执行搜索:
2564
+
2565
+ ```JAVA
2566
+ // arrays/AlphabeticSearch.java
2567
+ // Searching with a Comparator
2568
+
2569
+ import onjava. * ;
2570
+
2571
+ import java.util. Arrays ;
2572
+
2573
+ import static onjava. ArrayShow . * ;
2574
+
2575
+ public class AlphabeticSearch {
2576
+ public static void main (String [] args ) {
2577
+ String [] sa = new Rand .String (). array(30 );
2578
+ Arrays . sort(sa, String . CASE_INSENSITIVE_ORDER );
2579
+ show(sa);
2580
+ int index = Arrays . binarySearch(sa, sa[10 ], String . CASE_INSENSITIVE_ORDER );
2581
+ System . out. println(" Index: " + index + " \n " + sa[index]);
2582
+ }
2583
+ }
2584
+ /* Output:
2585
+ [anmkkyh, bhmupju, btpenpc, cjwzmmr, cuxszgv, eloztdv, ewcippc,
2586
+ ezdeklu, fcjpthl, fqmlgsh, gmeinne, hyoubzl, jbvlgwc, jlxpqds,
2587
+ ljlbynx, mvducuj, qgekgly, skddcat, taprwxz, uybypgp, vjsszkn,
2588
+ vniyapk, vqqakbm, vwodhcf, ydpulcq, ygpoalk, yskvett, zehpfmm,
2589
+ zofmmvm, zrxmclh]
2590
+ Index: 10 gmeinne
2591
+ */
2592
+ ```
2593
+ 比较器必须作为第三个参数传递给重载的 ** binarySearch()** 。在本例中,成功是有保证的,因为搜索项是从数组本身中选择的。
2483
2594
2484
2595
< ! -- Accumulating with parallelPrefix() -- >
2485
2596
## parallelPrefix并行前缀
2486
2597
2598
+ 没有“prefix()”方法,只有 ** parallelPrefix()** 。这类似于 ** Stream ** 类中的 ** reduce()** 方法: 它对前一个元素和当前元素执行一个操作,并将结果放入当前元素位置:
2599
+
2600
+ ```JAVA
2601
+ // arrays/ParallelPrefix1.java
2602
+
2603
+ import onjava. * ;
2604
+
2605
+ import java.util. Arrays ;
2606
+
2607
+ import static onjava. ArrayShow . * ;
2608
+
2609
+ public class ParallelPrefix1 {
2610
+ public static void main (String [] args ) {
2611
+ int [] nums = new Count .Pint (). array(10 );
2612
+ show(nums);
2613
+ System . out. println(Arrays . stream(nums). reduce(Integer :: sum). getAsInt());
2614
+ Arrays . parallelPrefix(nums, Integer :: sum);
2615
+ show(nums);
2616
+ System . out. println(Arrays . stream(new Count .Pint (). array(6 )). reduce(Integer :: sum). getAsInt());
2617
+ }
2618
+ }
2619
+ /* Output:
2620
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2621
+ 45
2622
+ [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
2623
+ 15
2624
+ */
2625
+ ```
2626
+
2627
+ 这里我们对数组应用Integer :: sum。在位置0 中,它将先前计算的值(因为没有先前的值)与原始数组位置0 中的值组合在一起。在位置1 中,它获取之前计算的值(它只是存储在位置0 中),并将其与位置1 中先前计算的值相结合。依次往复。
2628
+
2629
+ 使用 ** Stream . reduce()** ,您只能得到最终结果,而使用 ** Arrays . parallelPrefix()** ,您还可以得到所有中间计算,以确保它们是有用的。注意,第二个 ** Stream . reduce()** 计算的结果已经在 ** parallelPrefix()** 计算的数组中。
2630
+
2631
+ 使用字符串可能更清楚:
2632
+
2633
+ ```JAVA
2634
+ // arrays/ParallelPrefix2.java
2635
+
2636
+ import onjava. * ;
2637
+
2638
+ import java.util. Arrays ;
2639
+
2640
+ import static onjava. ArrayShow . * ;
2641
+
2642
+ public class ParallelPrefix2 {
2643
+ public static void main (String [] args ) {
2644
+ String [] strings = new Rand .String (1 ). array(8 );
2645
+ show(strings);
2646
+ Arrays . parallelPrefix(strings, (a, b) - > a + b);
2647
+ show(strings);
2648
+ }
2649
+ }
2650
+ /* Output:
2651
+ [b, t, p, e, n, p, c, c]
2652
+ [b, bt, btp, btpe, btpen, btpenp, btpenpc, btpenpcc]
2653
+ */
2654
+ ```
2655
+
2656
+ 如前所述,使用流进行初始化非常优雅,但是对于大型数组,这种方法可能会耗尽堆空间。使用 ** setAll()** 执行初始化更节省内存:
2657
+
2658
+ ```JAVA
2659
+ // arrays/ParallelPrefix3.java
2660
+ // {ExcludeFromTravisCI}
2661
+
2662
+ import java.util. Arrays ;
2663
+
2664
+ public class ParallelPrefix3 {
2665
+ static final int SIZE = 10_000_000 ;
2666
+
2667
+ public static void main (String [] args ) {
2668
+ long [] nums = new long [SIZE ];
2669
+ Arrays . setAll(nums, n - > n);
2670
+ Arrays . parallelPrefix(nums, Long :: sum);
2671
+ System . out. println(" First 20: " + nums[19 ]);
2672
+ System . out. println(" First 200: " + nums[199 ]);
2673
+ System . out. println(" All: " + nums[nums. length - 1 ]);
2674
+ }
2675
+ }
2676
+ /* Output:
2677
+ First 20: 190
2678
+ First 200: 19900
2679
+ All: 49999995000000
2680
+ */
2681
+ ```
2682
+ 因为正确使用 ** parallelPrefix()** 可能相当复杂,所以通常应该只在存在内存或速度问题(或两者都有)时使用。否则,** Stream . reduce()** 应该是您的首选。
2683
+
2487
2684
2488
2685
< ! -- Summary -- >
2489
2686
## 本章小结
2490
2687
2688
+ Java 为固定大小的低级数组提供了合理的支持。这种数组强调的是性能而不是灵活性,就像C 和c++ 数组模型一样。在Java 的最初版本中,固定大小的低级数组是绝对必要的,这不仅是因为Java 设计人员选择包含原生类型(也考虑到性能),还因为那个版本对集合的支持非常少。因此,在早期的Java 版本中,选择数组总是合理的。
2689
+
2690
+ 在Java 的后续版本中,集合支持得到了显著的改进,现在集合在除性能外的所有方面都优于数组,即使这样,集合的性能也得到了显著的改进。正如本书其他部分所述,无论如何,性能问题通常不会出现在您设想的地方。
2691
+
2692
+ 使用自动装箱和泛型,在集合中保存原生类型是毫不费力的,这进一步鼓励您用集合替换低级数组。由于泛型产生类型安全的集合,数组在这方面也不再有优势。
2693
+
2694
+ 如本章所述,当您尝试使用泛型时,您将看到泛型对数组是相当不友好的。通常,即使可以让泛型和数组以某种形式一起工作(在下一章中您将看到),在编译期间仍然会出现“unchecked”警告。
2695
+
2696
+ 有几次,当我们讨论特定的例子时,我直接从Java 语言设计人员那里听到我应该使用集合而不是数组(我使用数组来演示特定的技术,所以我没有这个选项)。
2697
+
2698
+ 所有这些问题都表明,在使用Java 的最新版本进行编程时,应该“优先选择集合而不是数组”。只有当您证明性能是一个问题(并且切换到一个数组实际上会有很大的不同)时,才应该重构到数组。这是一个相当大胆的声明,但是有些语言根本没有固定大小的低级数组。它们只有可调整大小的集合,而且比C / C ++ / java风格的数组功能多得多。例如,Python 有一个使用基本数组语法的列表类型,但是具有更大的功能—您甚至可以从它继承:
2699
+
2700
+ ```Python
2701
+ # arrays/ PythonLists . py
2702
+
2703
+ aList= [1 ,2 ,3 ,4 ,5 ]print(type(aList)) #< type ' list' >
2704
+ print(aList) # [1 ,2 ,3 ,4 ,5 ]
2705
+ print(aList[4 ]) # 5Basic list indexing
2706
+ aList. append(6 ) # lists can be resized
2707
+ aList+= [7 ,8 ] # Add a list to a list
2708
+ print(aList) # [1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ]
2709
+ aSlice= aList[2 : 4 ]
2710
+ print(aSlice) # [3 ,4 ]
2711
+
2712
+ class MyList (list): # Inherit from list
2713
+ # Define a method;'this'pointer is explicit:
2714
+ def getReversed(self):
2715
+ reversed=self[:] # Copy list using slices
2716
+ reversed.reverse() # Built-in list method
2717
+ return reversed
2718
+ # No'new'necessary for object creation:
2719
+ list2=MyList(aList)
2720
+ print(type(list2)) #<class '__main__.MyList'>
2721
+ print(list2.getReversed()) # [8,7,6,5,4,3,2,1]
2722
+ output="""
2723
+ <class 'list'>
2724
+ [1, 2, 3, 4, 5]
2725
+ 5
2726
+ [1, 2, 3, 4, 5, 6, 7, 8]
2727
+ [3, 4]
2728
+ <class '__main__.MyList'>
2729
+ [8, 7, 6, 5, 4, 3, 2, 1]
2730
+ """
2731
+
2732
+ ```
2733
+ 前一章介绍了基本的Python语法。在这里,通过用方括号包围以逗号分隔的对象序列来创建列表。结果是一个运行时类型为list的对象(print语句的输出显示为同一行中的注释)。打印列表的结果与在Java中使用Arrays.toString()的结果相同。
2734
+ 通过将 : 操作符放在索引操作中,通过切片来创建列表的子序列。list类型有更多的内置操作,通常只需要序列类型。
2735
+ MyList是一个类定义;基类放在括号内。在类内部,def语句生成方法,该方法的第一个参数在Java中自动与之等价,除了在Python中它是显式的,而且标识符self是按约定使用的(它不是关键字)。注意构造函数是如何自动继承的。
2736
+
2737
+ 虽然一切在Python中真的是一个对象(包括整数和浮点类型),你仍然有一个安全门,因为你可以优化性能关键型的部分代码编写扩展的C, c++或使用特殊的工具设计容易加速您的Python代码(有很多)。通过这种方式,可以在不影响性能改进的情况下保持对象的纯度。
2738
+
2739
+ PHP甚至更进一步,它只有一个数组类型,既充当int索引数组,又充当关联数组(Map)。
2740
+
2741
+ 在经历了这么多年的Java发展之后,我们可以很有趣地推测,如果重新开始,设计人员是否会将原生类型和低级数组放在该语言中(同样在JVM上运行的Scala语言不包括这些)。如果不考虑这些,就有可能开发出一种真正纯粹的面向对象语言(尽管有这样的说法,Java并不是一种纯粹的面向对象语言,这正是因为它的底层缺陷)。关于效率的最初争论总是令人信服的,但是随着时间的推移,我们已经看到了从这个想法向更高层次的组件(如集合)的演进。此外,如果集合可以像在某些语言中一样构建到核心语言中,那么编译器就有更好的机会进行优化。
2742
+
2743
+ 撇开““Green-fields”的推测不谈,我们肯定会被数组所困扰,当你阅读代码时就会看到它们。然而,集合几乎总是更好的选择。
2491
2744
2492
2745
2493
2746
<!-- 分页 -->
0 commit comments