1. 程式人生 > >JDK不同版本的Collections.Sort方法實現

JDK不同版本的Collections.Sort方法實現

一句話總結:

JDK7中的Collections.Sort方法實現中,應用了比較運算的基本屬性:若A大於B,則B小於A,若A等於B,則B等於A。所以要求傳入compare方法在傳入引數交換時,返回值正負也需要交換,或恆為0,否則可能會在排序時拋錯。

現象:

昨晚偶然發現XX業務線上介面呼叫返回伺服器內部異常。而呼叫模擬環境介面返回正常。檢視日誌發現報錯如下:

2015-01-14 22:14:17 291 [WARN] ApiServletApiServlet process error! act:query_join_groups, PARAMS:{}

java.lang.IllegalArgumentException:Comparison method violates its general contract!

 at java.util.TimSort.mergeHi(TimSort.java:868)

  atjava.util.TimSort.mergeAt(TimSort.java:485)

  atjava.util.TimSort.mergeCollapse(TimSort.java:408)

at java.util.TimSort.sort(TimSort.java:214)

  atjava.util.TimSort.sort(TimSort.java:173)

  at java.util.Arrays.sort(Arrays.java:659)

  atjava.util.Collections.sort(Collections.java:217)

atcn.sina.groupchat.processor.QueryJoinGroupsProcessor.process(QueryJoinGroupsProcessor.java:54)

排查過程:

查看出錯的業務程式碼如下

      Collections.sort(infos, new Comparator<UserGroupInfo>() {

           @Override

           public int compare(UserGroupInfo info1, UserGroupInfo info2) {

               return info2.joinTime > info1.joinTime ? 1 : -1;

           }

       });

查看了具體報錯的位置,發現只有如下程式碼:

if (len2== 0) {

     throw new IllegalArgumentException("Comparison method violates its generalcontract!");

    }

Google了一下出錯,發現JDK6和JDK7的sort實現不同,對於JDK7才會有上述問題。解決的辦法是compare方法在傳入物件相等時必須返回0,但是沒有詳細描述出錯的原因。

查了一下JDK版本1.6到1.7的改動,Collections.Sort方法實現從普通歸併排序改成了TimSort排序。不太理解為什麼更換排序會導致相同輸入報錯,並且報錯的地方判斷邏輯很突兀。於是簡單的瞭解的了一下java timsort的實現,和大家分享一下。

TimSort排序是一種優化的歸併排序,對於降序和升降序片段混合的輸入有很大的效能提升。OpenJDK關於TimSort的實現如下:

1.      遍歷陣列,將陣列分為若干個升序或降序的片段,反轉降序的片段使其變為升序,每個片段成為一個Runtask

2.      將切分好的RunTask壓棧

3.      對棧中相鄰的RunTask做歸併,歸併過程相對普通的歸併排序做了一定的優化,主要有兩步

a)        設做歸併的兩段分別為A,B,A段的起點為base1,長度為len1,B段起點為base2,長度為len2。取B點的起點值B[base2],在A段中進行二分查詢,將A段中小於等於B[base2]的段作為merge結果的起始部分;再取A段的終點值a[base1 + len1 - 1],在B段中二分查詢,將B段中大於等於a[base1 + len1 - 1]值的段作為merge結果的結束部分。

b)        之後進行普通歸併,將兩段終點標為cursor1和cursor2,倒序歸併

這裡有一個優化,如果連續N(圖中假設為4)次某段的cursor指向的值都大於另一段,則可以預期該段的平均值大於另一段,仿照(a)中的方法,用cursor的值分別對另一段進行切割,提高歸併速度。如下圖中所示,cursor1指向的值(10,10,11,11)已經連續4次大於cursor2指向的值(8),觸發切割邏輯,用cursor2指向的值去切割1段,使cursor1左移,然後用cursor1切割2段,由於cursor1指向值(10)大於2段中所有的值,沒有進行實際切割。

最終,將2段的值arraycopy到1段0~3的位置,歸併結束。

程式碼如下:

// 普通歸併過程,count記錄連續大於的次數,到達一個閾值時,進行二分法切割,提高歸併速度

do {

                         // tmp為B段的複製

               if (c.compare(tmp[cursor2], a[cursor1]) < 0) {

                   a[dest--] = a[cursor1--];

                   count1++;

                   count2 = 0;

                   if (--len1 == 0)

                       break outer;

               } else {

                                              …

               }

            }while ((count1 | count2) < minGallop);

                           // 到達閾值後,用預期平均值較小的段的最大值去切割另一段,方法和(a)中類似

                  do {

               count1 = len1 - gallopRight(tmp[cursor2], a, base1, len1, len1 - 1, c);

               if (count1 != 0) {

                   dest -= count1;

                   cursor1 -= count1;

                   len1 -= count1;

                   System.arraycopy(a, cursor1 + 1, a, dest + 1, count1);

                  if (len1 == 0)

                       break outer;

               }

               a[dest--] = tmp[cursor2--];

               if (--len2 == 1)

                   break outer;

                                    // 出問題的地方,gallopLeft是在B段中查詢A[Cursor1]的位置,如有相等的情況,取最左的位置,如果B段全部大於A[Cursor1],則返回0

               count2 = len2 - gallopLeft(a[cursor1], tmp, 0, len2, len2 - 1, c);

               if (count2 != 0) {

                                              …

                  len2-= count2;

                   …

                  }

                               …

            }while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP);

                           …

                           // 最終對len2進行合法檢測

                  if (len2 == 0) {

                      throw newIllegalArgumentException("Comparison method violates its generalcontract!");

            }

關於程式碼中最後的len2值檢測,是因為(a)中切割後,A中所有的值都大於B段的起點B[base2]。在之後的普通歸併中,如果出現count2>=minGallop的情況,進行加速歸併優化時,按照之前的推論,gallopLeft返回值大於等於1(cursor1必然大於B[base2]),從而推出len2 > 0。

當傳入的比較方法返回有問題時,會破壞以上推論,以出現問題的程式碼為例,當傳入兩個相等值時,返回-1(交換引數後還是返回-1,違背了之前的要求)。過程如下所示:

1.      歸併前的A段和B段

2.      用B段的起點和A段的終點互相切割之後,由於compare方法的問題,A段中的1和2位置的5被保留,破壞了A段中所有值都大於B[base2]的條件

3.      之後是普通的歸併過程

----à

4.      之後由於連續的A段大於B段,觸發了切割,B段的cursor2將A段切割到cursor1的位置;當用cursor1對B段進行切割時,由於compare方法的問題,gallopLeft會返回0,從而導致len2值等於0,引起報錯。

後續

1.      測試環境的JDK版本需要和線上環境保持一致

2.      排查程式碼,修復此類問題

相關推薦

JDK不同版本Collections.Sort方法實現

一句話總結: JDK7中的Collections.Sort方法實現中,應用了比較運算的基本屬性:若A大於B,則B小於A,若A等於B,則B等於A。所以要求傳入compare方法在傳入引數交換時,返回值正負也需要交換,或恆為0,否則可能會在排序時拋錯。 現象: 昨晚偶然發現X

實現List集合排序的兩種方法(使用Collections.sort方法

1:實現comparable package core.java.collection.collections;      public class User implements Comparable<User>{              private i

Python 冒泡排序只適用位數相同,位數不同用a.sort()方法

ever 調用 () 降序排序 spa class nbsp Coding odi 數組內容雙位數排序: #coding:utf-8 print u"中文" a = [‘15‘,‘11‘,‘33‘,‘52‘,‘61‘,‘43‘] b = 0 c = 0 print a i

04-java.util.Collections+Collections.sort()方法的練習

1、java.util.Collections:集合框架的工具類,裡面的方法都是靜態方法。此類完全由在collection上進行操作或返回collection的靜態方法組成。如果為此類的方法所提供的collection或類物件為null,則這些方法都將丟擲NullPointerException

JAVA Collections.sort方法在SSH三大框架中使用中的問題

     最近,一同學在開發中遇到了SSH三大框架中使用到了Collections.sort方法。然而,他開發環境中的JDK 是1.7.0_64,網站部署的JDK版本是1.7.0_80,他通過開發環境中產生的.class直接去更新網站部署環境中的.class 檔案後,程式

(好使)用Java集合中的Collections.sort方法對list排序的兩種方法

      ret = String.valueOf(m2.invoke(((E)b), null).toString().length()).compareTo(String.valueOf(m1.invoke(((E)a), null).toString().length()));          if

同一個jar包不同版本衝突解決方法

一個專案裡面同一個jar包一般不能有兩個版本的 不然可能會出現jar包衝突的情況 常見報錯有這三個: 1、java.lang.ClassNotFoundException(找不到類) 2、java.lang.NoSuchMethodError(找不到具體方

jdk7 Collections.sort()方法報錯分析

問題背景 起因 前些天測試給提了一個專案裡的bug,在檢視專案的一個線上資料的列表的時候發生了崩潰.然後我根據bugly定位發現是在使用Collection.sort()對list排序的時候產生Comparison method violates its general cont

Linux下不同使用者使用不同版本gcc的方法

Linux下,有時經常需要使用不同版本gcc進行測試,尤其是有些大型程式,例如,如果第一次編譯版本是gcc5.0,修改了其中一個檔案,如果編譯器改為gcc6.0,則執行時會出一些奇怪錯誤,甚至無法debug。我曾經遇到,類似一個問題,編譯時不報錯,執行時把其中一個int型資料

jdk不同版本對String拼接的優化分析

對比jdk5-8所有版本下的反編譯位元組碼,發現結果相同,證明字串拼接從jdk5開始就已經完成了優化,並且沒有進行新的優化。 詳細看反編譯後的位元組碼,8開始進入for迴圈比較階段,11new一個新的StringBuilder,為了優化之後的String+操作。34迴圈結束,重新到5,將會在11重新new一個

jdk1.7和jdk1.6的Collections.sort方法不一樣

Collections.sort(list, new Comparator<AAAVo>() {                           @Override                           public int compare(AAAVo vo1, AAA vo2

使用Collections.sort方法對list排序的兩種方法

    使用 Collections.sort 方法對 list 排序有兩種方法第一種是 list 中的物件實現Comparable介面,如下:/** * 根據order對User排序 */ public class User implements Comparable {

JS使用sort方法實現氣泡排序和亂序

預設的sort排序依據是ASCⅡ碼,所以從小到大排列遇到11可能排在個位數前面去了。 因此在數字進行排序時,需要新增函式入口點到sort函式中,就是改變sort函式的排序依據,通過不同的函式入口點,實現不同的排序效果。這裡介紹一下氣泡排序和亂序。 var a

java Collections.sort()實現List排序的默認方法和自定義方法

public get object 順序 text main 輸出 any 比較 1.java提供的默認list排序方法 主要代碼: List<String> list = new ArrayList();list.add("劉媛媛"); list.add("王

關於"一個作業系統下如何安裝多個不同版本JDK?"、並實現不同版本之間的相互切換使用

本文章將教你如何在一個作業系統下安裝多個不同版本的JDK, 並且實現不同版本直接的相互切換使用。 JDK的各個版本如下:  由於以前我們總是習慣直接在Oracle官網上找到所需的版本進行直接下載JDK, 但是筆者我今天也是埋頭直接去了官網, 奈何Oracle官方已經限制了

Collections.sort()方法和lambda表示式結合實現集合的排序

1.使用Collections.sort()實現集合的排序,這裡的方法具體指的是:   Collections.sort(List list, Compatator c)       第一個引數:為要進行排序的集合。     第二個引數:Compatator的實現,指定排序的方式。   2

java Collections.sort()實現List排序的預設方法和自定義方法

1.java提供的預設list排序方法 主要程式碼: List<String> list = new ArrayList();list.add("劉媛媛"); list.add("王碩"); list.add("李明"); list.add("劉迪");

同時安裝不同版本jdk引起的衝突解決方法

現象 由於工作原因, 之前用的jdk1.8版本,因為線上生產環境都是jdk1.6,記得上一次就是因為不清楚線上環境的jdk版本問題,把自己編譯的1.8版本給釋出到線上環境,導致啟動報java.lang unsupported classversion 經過這

不同版本JDK中HashMap的實現的區別以及原因

轉載:http://blog.csdn.net/vking_wang/article/details/14166593 1. HashMap的資料結構 資料結構中有陣列和連結串列來實現對資料的儲存,但這兩者基本上是兩個極端。       陣列 陣列儲存區間是連續的,佔

Comparable、Iterator接口和Collections類的實現方法

left com 讓其 eve 集合 移除 () iter reverse   Comparable接口:     此接口強行對實現它的每個類的對象進行整體排序。這種排序被稱為類的自然排序,類的 compareTo 方法被稱為它的自然比較方法。     實現此接口的對象列表