5.4 集合的排序(Java學習筆記)(Collections.sort(),及Arrays.sort()底層分析)

来源:https://www.cnblogs.com/huang-changfan/archive/2018/10/10/9734468.html
-Advertisement-
Play Games

1.Comparable介面 這個介面顧名思義就是用於排序的,如果要對某些對象進行排序,那麼該對象所在的類必須實現 Comparabld介面。Comparable介面只有一個方法CompareTo(),這個方法可以看做是指定的排序規則。 內置類已經實現了CompareTo方法,例如long 小於返回 ...


1.Comparable介面

這個介面顧名思義就是用於排序的,如果要對某些對象進行排序,那麼該對象所在的類必須實現

Comparabld介面。Comparable介面只有一個方法CompareTo(),這個方法可以看做是指定的排序規則。

 

內置類已經實現了CompareTo方法,例如long

小於返回-1,等於返回0,大於返回1.

這裡只舉一個例子,例如int,double,Date等可以排序的內置類都已經實現了CompareTo方法,即指定了排序規則。

 

2.Collections.sort()和Arrays.sort()方法。

Colections是對集合進行操作的工具類,Collection.sort是對集合排序的方法。

Collections.sort()的底層實質是是調用Arrays.sort();

我們來看下Collections.sort()的源碼:

首先傳遞進去的集合必須是Comparable介面及其子類,而且後續會用到Comparable介面中的CompareTo方法。所以進行排序的類必須實現Comparable,

可以看出如果沒有實現Comparable介面,參數傳遞會出現錯誤。

我們點進list.sort()方法,就會看到下麵代碼:

我們可以看到,它是先將集合轉換成數組,然後調用Arrays.sort()方法。

排序排完後又將數組中元素逐個放入集合。

 

我們接下點進Arrays.sort()方法的源碼:

參數有兩個,有一個之前轉換成數組的集合a,c是一種排序規則,後面會講到。沒有指定的話,會自動設置為null。

然後判斷是否有排序規則,有就調用使用排序規則的排序方法。這裡看出當設置了排序規則c時,會進入使用排序規則的比較方法進行比較,

此時compareTo()是不起作用的,起作用的是排序規則c中的compare()方法。

接著進入sort().

我們可以看到這裡有兩個排序演算法,使用LegacyMergeSort.userRequested控制,第一個是歸併排序,也就是LegacyMergeSort.userRequested為true時進入的語句,

註意預設狀態下LegcyMergeSort是false,所以預設進去的是ComparableTimSort.sort()。這時一種優化的排序演算法,有興趣可以去查詢下,當然這種演算法比較複雜,

不便於理解。legactMergeSort()便於理解些,我們就先分析這個排序演算法。

預設是進入ComparableTimSort(),要想進入legacyMergeSort()我們就需要設置下LegacyMergeSort.userRequested的值。

這時我們可以自己設置LegacyMergeSort.userRequested的值,這句話是加在main中Sort調用之前的。

System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");

設置為true後我們進入的就是legcyMergeSort();

/*
註意:
這裡只是對排序演算法的一種優化,排序時的規則是不變的的。
(例如我現在要進行升序排序,可以用很多演算法,但是最後的結果一定是升序的。)
這裡我們主要看CompareTo()方法返回值(1,0,-1)在排序中起到的作用,不關心具體演算法的優化,有興趣的可以自行查閱相關演算法。
*/

 

那麼我們這時就會進入gacyMergeSort();

我們點擊該方法,看它的代碼:

首先將存放集合數組的數組克隆,然後將克隆後的數組作為參數傳遞進aux,這裡c為null進入第一個mergeSort();

進入mergeSort()我們可以看到:

我們先看傳遞進去的參數,srt,dest都是存放數據的數組,str是克隆的數組,dest是原數組,low=0,high=數組長度,off = 0;

首先定義了一個legth = high - low,得到的其實也是數組長度,

一開始進行一個判斷,長度小於INSERTIONSORT_THRESHOLD(常量7)就採用更適合長度小於7的數組的插入排序演算法,

我們可以看到代碼中排序的依據是CompareTo()的返回值,如果返回值大於0則交換位置,反之則不交換。

如果不小於則採用適合數組長度大於7的其他排序方法。

後面還有更適合數組長度大於7的演算法,這裡沒有貼出這部分代碼,有興趣可自己去瞭解下。

(這裡只是對排序演算法執行次數的一種優化,排序時的規則是不變的的。我們這裡主要看CompareTo方法起到作用,不關心排序演算法的優化。)

 

這裡我們舉一個簡單的例子,假設現在我在鏈表裡面存放了三個long型數據312;

我們從調用Collections.Sort()方法開始分析源碼執行的過程,理解CompareTo方法的作用。

 進入mergeSort()方法後,我們先來看下傳遞進來的參數:

dest[] = {3,1,2}, low = 0, high = 3(數組長度);

 

接下來分析代碼執行過程:

首先i=0,然後j = 0,不滿足條件直接跳出,然後i++

此時i = 1,j=1,此時將元素轉換為Comparable介面調用CompareTo方法根據之前看的long裡面的Compare方法(Comparable)dest[0].compareTo(dest[1]) = 1.

如果沒有實現Comparable介面,這裡轉型也會出現錯誤。

//調用ComparTo()返回值不清楚的,回到前面去看long裡面CompareTo方法

交換dest[0],dest[1]位置,此時數組順序:132.,然後j--,不滿足迴圈條件直接跳出,執行i++。

此時i=2,j=2,(Comparable)dest[1].compareTo(dest[2]) = 1,交換dest[1],dest[2],此時數組順序:123.執行i++;

i=3,不滿足迴圈條件,退出執行retun,結束排序,最終數組順序:123;

大家可以自己結合CompareTo方法的返回值分析分析排序過程。

 

瞭解了CompareTo在排序中起到的作用後,平常我們在實現Comparable介面,

實現其中的CompareTo方法時我們就可以明確CompareTo的返回值。

例如,a<b return 1.再結合CompareTo方法為1則交換位置,此時就是降序排列,

如果是a>b return 1,就變成升序。分析源碼後我們可以自己分析排序到是升序還是降序。

我們也可以這樣理解,如果返回1就代表交換這兩個元素位置。

 

//一般情況下大於返回1,等於返回0,小於返回-1,更符合我們的思維習慣,這時排序是升序

建議一般情況下這樣寫,此時是升序,如果是要求降序排列,改變返回值,將a>b return -1就是降序了。

 

再次聲明,要使用排序方法,必須實現Comparable介面。

 

我們來舉一個具體的例子。例如新聞,我們首先要看時間降序(即最近發生的新聞出現在最前面),

其次按點擊量降序(即點擊量多的出現在前面)。

那麼我們結合CompareTo方法在排序時的作用來考慮時間和點擊量比較的返回值。

首先,最近發生的新聞排在前面,如果A新聞時間在B新聞時間後面(也就是A新聞發生的時間離我們最近),

越在後面的時間的數值越大,Date本質上也是一個long型數。那麼最近發生的排在前面應該降序。也就是if(a時間> b時間)return -1;

對Date這一塊理解有問題的,可參考https://www.cnblogs.com/huang-changfan/p/9495067.html

如果時間相同,我們再來考慮點擊量,點擊量大的排在前面。

那麼這也是一個降序,if(點擊量a > 點擊量b) return -1;

這裡也可以用之前如果返回1,就代表交換位置,-1就不交換位置這樣來理解,理解的方式有很多種,

但歸根結底都是對源碼執行過程的分析,把握這一點就可以以不變應萬變。

 

接下來我們來看具體的代碼。

NewS類代碼

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class News implements Comparable<News>{
    private String newsName;
    private Date date;
    private int click;
    private String timeS;
    public News(){}
    public News(String newsName, Date date, int click){
        setNewsName(newsName);
        setDate(date);
        setClick(click);
    }
    public String getNewsName() {
        return newsName;
    }     public void setNewsName(String newsName) {
        this.newsName = newsName;
    }     public Date getDate() {
        return date;
    }     public void setDate(Date date) {
        this.date = date;
        DateFormat china_time = new SimpleDateFormat("yyyy-MM-dd hh-mm-ss");
       timeS = china_time.format(date);
       
    }
    public int getClick() {
        return click;
    }     public void setClick(int click) {
        this.click = click;
    }
    public int compareTo(News o) {
        // TODO Auto-generated method stub
        if(this.getDate().after(o.getDate()))//這句話就是如果this.getDate()的值 > o.getDate(),就降序排列。
            return -1;                       //也可以理解為如果this.getDate()的值 >o.getDate(),則不交換位置,即降序。
        if(this.getClick() > o.click)//如果this的點擊數大於o的點擊數則降序排列,即點擊量大的排在前面
            return -1;               //如果this的點擊量大於o的點擊量,則不交換位置,即降序排列
        return 1;
    }
       
    public String toString(){
        return "標題:" + this.newsName + "時間" + timeS
        + "點擊量" + this.click + "\n";
    } }

 

運行代碼

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.List; public class TestCompare {
    public static void main(String[] args) {
        List<News> newsList = new LinkedList<>();
        newsList.add(new News("國慶高峰堵車,各景點爆滿!",new Date(),1000));//設置為當前時間
        newsList.add(new News("國慶仍有大部分家裡蹲!",new Date(System.currentTimeMillis()-60*60*1000),100));//設置為當前時間前一小時
        newsList.add(new News("驚呆,游客竟在做這種事!!!",new Date(),10000));//設置為當前時間,點擊量10000
        System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");//設置為true進入legacySort() 
        System.out.println(newsList);
        Collections.sort(newsList);
        System.out.println(newsList);
    }
}
運行結果:
//排序前新聞
[標題:國慶高峰堵車,各景點爆滿!時間2018-10-07 09-23-53點擊量1000
, 標題:國慶仍有大部分家裡蹲!時間2018-10-07 08-23-54點擊量100
, 標題:驚呆,游客竟在做這種事!!!時間2018-10-07 09-23-54點擊量10000
]

//排序後新聞
[標題:驚呆,游客竟在做這種事!!!時間2018-10-07 09-23-54點擊量10000
, 標題:國慶高峰堵車,各景點爆滿!時間2018-10-07 09-23-53點擊量1000
, 標題:國慶仍有大部分家裡蹲!時間2018-10-07 08-23-54點擊量100
]

我們可以看到,首先按時間(距離我們最近發生的新聞排在最前面)進行排序,時間相同按點擊量排序。

 

我們可以看出mergeSort中使用Compareto方法只是判斷是否大於0,

當我們的CompareTo()返回的是0或-1時,for()語句中判斷都是false,那麼我們能否在返回時不要-1,只返回0,1呢?

在當前方法(LeacySort()中的mergeSort())顯然是可以的,因為0 > 0,-1>0的結果是相同的。

你要確定你進去的是LeacySort()中的mergeSort(),最好設置如下語句:

System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");

如果沒有設置的話LegcyMergeSort.userRequested預設為false,進去的就是另外一種排序演算法,在另外一種演算法中這樣設置是不行的。

 

當預設為false時,我們進入另外一個演算法看下:(這個演算法只是簡單點的過一遍,不分析具體步驟)

 為false時進去的是ComparebleTimSort.sort();我們進入這個演算法看下:

首先一個斷言,一般情況下都沒有問題,

然後nRemaing = hi - lo,hi是數組長度,lo是0.

MIN_MERGE是32。

接著就是根據數組a計算initRunLen的值,

我們先進入countRunAndMakeAscending();

但我們將-1也設置為0時,else後面的語句判斷的是返回值>=0。-1和0去判斷得到的結果是不同的。

這裡會造成這個值有問題,而這個值需要傳遞到排序演算法中,就會導致排序出現問題。News中的CompareTo方法的

返回值只有0,1的話這裡的runHi - lo是3,如果是按照正常的1,0,-1來的話runHi - lo是2。這個可以結合源碼自己分析一下。

這個執行完之後就要將得到的值,傳入我們的排序演算法中開始排序了。(上述分析都是基於之前代碼中創建的三個新聞對象來分析的)

返回runHi-lo後,我們回到上一級

我們將值傳入binarSort()中,然後點擊binarySort:

這個具體過程就不一一分析了,有興趣的可以分析下這些演算法。

start就是之前的返回值(runHi - lo),如有隻有1,0返回的是3,如果是正常的-1,0,1返回的是2.

我們來看第一個for語句,如果start是3就直接跳出迴圈(hi是數組長度,這裡為3)不進行排序,原有數據不會發生變化。

如果是2,進行排序,最後輸出是按照規則排序好的數據。

 

大家可以試下修改上面代碼中Nwes的compareTo()方法的返回值和運行代碼中設置true,false的部分:

(1)將 return -1改成return 0,LegacyMergeSort.userRequested為false時,輸出未正常排序結果。

                                                    LegacyMergeSort.userRequested為true時,輸出正常排序結果。

結合上述內容理解下整個的過程。

無論是哪種排序方法,嚴格按照(1,0,-1)返回,都可以輸出對應邏輯排序結果。

結合例子、源碼與上述內容分析分析,就可以清楚的知道排序執行的流程以及compareTo()返回值的作用。

 

 

3.Comparator介面

Comparator和Comparable類似,底層實現是一樣的,理解了Comparable中CompareTo()方法的作用後

理解Comparator就很簡單了。

 

我們之前說的實現Comparable介面的CompareTo方法是寫在需要進行排序的類裡面的,可以說這種方法就從屬於這個類。

但Comparator就不一樣了,它只是制定了一種排序規則,不需要從屬於誰,誰需要用這個規則拿來用就可以了。

具體的操作是,新建一個規則類,例如我要按照新聞點擊量排序,那麼我可以把這個類命名為NwesClickSort

這個類需要實現Comparator介面,並且實現裡面的ompare()方法。 

 

我們來看下Comparator介面中compare方法:

這個作用和compareTo()方法的作用一樣,也是比較兩個值(o1,o2),然後返回1,0,-1.

使用compare排序時,就是調用這個方法來進行排序。

 

使用時,我們需要為NewsClickSort這個類實例化一個對象,這個對象代表一種排序規則。

然後將集合和對象(排序規則)傳遞到Collections.sort()中。

 

我們先來看個例子://這裡面我們指定了一種排序規則,按照點擊量來排序,Nwes類還是之前的代碼,沒有任何修改。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.List; public class TestCompare {
    public static void main(String[] args) {
        List<News> newsList = new LinkedList<>();
        newsList.add(new News("國慶高峰堵車,各景點爆滿!",new Date(),1000));//設置為當前時間
        newsList.add(new News("國慶仍有大部分家裡蹲!",new Date(System.currentTimeMillis()-60*60*1000),100));//設置為當前時間前一小時
        newsList.add(new News("驚呆,游客竟在做這種事!!!",new Date(),10000));//設置為當前時間,點擊量10000
        System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");//設置為true進入legacySort() 
        System.out.println(newsList);
        Collections.sort(newsList,new NewsClickSort());//傳遞進去的集合,和排序規則
        System.out.println(newsList);
    }
} class NewsClickSort implements Comparator<News>{    @Override
   public int compare(News o1, News o2) {
     if(o1.getClick() < o2.getClick()){
        return 1;//這裡寫得不是很規範,最好是加上等於返回0,最後小於返回-1.
      }else{
        return -1;
      }
    }
}
運行結果:
//
排序前結果 [標題:國慶高峰堵車,各景點爆滿!時間2018-10-10 08-40-50點擊量1000 , 標題:國慶仍有大部分家裡蹲!時間2018-10-10 07-40-50點擊量100 , 標題:驚呆,游客竟在做這種事!!!時間2018-10-10 08-40-50點擊量10000 ] //排序後結果 [標題:驚呆,游客竟在做這種事!!!時間2018-10-10 08-40-50點擊量10000 , 標題:國慶高峰堵車,各景點爆滿!時間2018-10-10 08-40-50點擊量1000 , 標題:國慶仍有大部分家裡蹲!時間2018-10-10 07-40-50點擊量100 ]

排序規則中只指定了點擊量,所以排序後的結果是按點擊量來排序的。

我們可以看到上面代碼也設置了 

System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");//

 我們就來分析下使用comparator介面中的compare方法的執行過程。

第一步當然是點進main中的

Collections.sort

註意那個c,就是實例化的比較規則。

我們繼續點擊sort():

我們會發現和之前看Comparable介面排序的執行過程是一樣的。

只是這時候c是實例化的排序規則,不是null了。

繼續點擊sort()

這時c不等於null,所以進入後面的if(LegcyMergeSort.userRequested)進行判斷

前面說了預設LegayMergeSort.userRequested是false,這裡我們設置了為true,

所以我們點進legacyMergeSort():

c不等於null,我們點進第二個分支:

我們會發現和之前sort排序過程及CompareTo()的實現思路是一樣的,只是一個調用的是compareTo方法,

一個調用的是c.compare()方法。

 

LegayMergeSort.userRequested為false時,也和之前分析的sort排序過程及compareTo()的實現思路是一樣的,區別隻是一個調用

的是compareTo()方法,一個調用的是c.compare()方法。

LegayMergeSort.userRequested為false時的後續執行過程有興趣可以去看下,會發現和前面講的一樣。

到這裡整個比較過程就很清楚了。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在數據分析當中的東西還是很多的,我在這裡只是啟髮式的介紹一下,瞭解到這方面的東西之後,使用的時候可以更快的找到解決辦法,希望能對大家有所幫助。 這次,依然是使用的sklearn中的iris數據集,對其進行通過熱圖來展示。 預處理 sklearn.preprocessing是機器學習庫中預處理的模塊, ...
  • 一、前言 Thymeleaf 的出現是為了取代 JSP,雖然 JSP 存在了很長時間,併在 Java Web 開發中無處不在,但是它也存在一些缺陷: 1、JSP 最明顯的問題在於它看起來像HTML或XML,但它其實上並不是。大多數的JSP模板都是採用HTML的形式,但是又摻雜上了各種JSP標簽庫的標 ...
  • 在賬戶表的基礎上,我新建了一個賬戶account_session表,用來記錄登錄賬戶的account_id和最新一次登錄成功用戶的session_id,然後首先要修改登錄方法:每次登錄成功後,要將登錄用戶信息寫入Session的同時還要更新account_session表裡相應賬戶的session_ ...
  • 本次和大家分享的主要是docker搭建es和springboot操作es的內容,也便於工作中或將來使用方便,因此從搭建es環境開始到代碼插入信息到es中;主要節點如下: 1.elasticsearch啟動 我本機環境是windows10,要掛載es的配置文件需要在本機上創建配置文件,因此這裡創建配置 ...
  • 直接gdb pgname 參數1 這種方式,參數1是不會帶到gdb里的 1,首先啟動程式 2,設置程式的參數 ...
  • 在上文 設計一個百萬級的消息推送系統 中提到消息流轉採用的是 Kafka 作為中間件。 其中有朋友咨詢在大量消息的情況下 Kakfa 是如何保證消息的高效及一致性呢? ...
  • spring-boot-starter-web:spring-boot-starter:spring-boot場景啟動器;幫我們導入了web模塊 正常運行所依賴的組件; Spring Boot將所有的功能場景都抽取出來,做成一個個的starters(啟動器), 只需要在項目裡面引入這些starter ...
  • springboot多數據源配置,代碼如下 配置文件 application.properties 測試代碼如下 在運行的時候會出現如下異常問題,運行失敗,報出java.lang.IllegalArgumentException: jdbcUrl is required with driverCla ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...