自力更生Collections.sort發現比較結果混亂?Comparator的鍋還是強轉類型導致?

  • 2019 年 10 月 23 日
  • 筆記

近日開發任務時間充裕一些,於是有時間回顧一下項目。

我關注到了項目中使用的七牛雲的對象存儲服務。

作為測試需要上傳了一些圖片,但七牛的控制台卻無法將內容按照上傳時間排序或者是按照日期查詢,由於bucket當中內容較多,我無法看到今日上傳的圖片記錄。

點開控制台給出的幫助文檔,沒怎麼找到控制台的說明,卻在下一行中看到了這樣一個小句,由愣而轉笑。

又參考了segmentfault等問答,不知是自身演算法實力強大還是怎樣,反正七牛的確就是由一個【快】字拒絕提供一切排序和篩選服務。。

包括控制台、包括API。

因為存儲使用key-value按照key的ascii排序,性能最好,展示時也決意不肯做任何變通,這是哪門子邏輯?

 

端詳了好一會快字,又到api的文檔中遨遊,迫不得已結合了服務端的sdk才摸清了該使用哪個域名、以及api的驗證token邏輯。

終於我可以藉助api獲取到一個bucket底下的所有文件列表。(當然也只能按key的字母排序)

沒有排序自己排吧。

由於七牛記錄的上傳時間單位是100納秒。。(醉了,下面放一張珍藏的圖,可能是為了匹配記憶體的處理速度吧,轉為毫秒要除以10000)毫無疑問會用long來表示了

先開始為了降序排序直接把compare方法直接重寫為return (int)o2.putTime – o1.putTime,結果排序結果顛三倒四。

由於習慣上compare的結果都類比數學符號函數返回1/0/-1,試著修改,排序結果正常。

我慌了。印象中sort源碼沒有去判斷1/0/-1啊。。查閱了一下,的確無論是mergeSort還是TimSort源碼中只做了compare方法的結果< 0和>= 0的判斷。

遂開始意識到是強轉出了問題。不過,long強轉為int時超過了int的範圍,你們猜會怎麼樣?變為0?一開始我確實認為是變為0導致比較器認為很多對象相等,進而導致排序結果混亂。

實際上呢,放一些例子給大家感受一下:

原始long794056495088 強轉int-512454672
原始long889116247210 強轉int58016938
原始long-42421670980704 強轉int-278998112
原始long-13603247092059 強轉int-1085665627
原始long-42159956036917 強轉int-557059381
原始long1150831190997 強轉int-220044331
原始long-15560683307530 強轉int-16794122
原始long-10186805000959 強轉int857425153
原始long6059356417962 強轉int-842436694
原始long16453936148484 強轉int-83562492
原始long631537897167 強轉int177704655
原始long17343052395694 強轉int-25545554
原始long-9434266088448 強轉int1777060864
原始long-3033381814 強轉int1261585482
原始long4651744348343 強轉int294766775
原始long-13649116168272 強轉int289898416

出乎意料,沒有0,但是有一些強轉後維持了原符號,另一些正負顛倒。

是時候學習一下強轉原理了。

java中long為8位元組64位,int為4位元組32位。

如果long強轉為int:直接取低位32位作為值,但是看做補碼。

在電腦中,數值以補碼形式存儲,正數的補碼為其二進位表示。負數的補碼為其模的二進位表示取反加一(或者記為符號位不變,剩餘數位取反+1)。

負數的補碼轉原碼時:符號位不變,剩餘數位取反+1。和負數原碼轉補碼的操作步驟相同喲,這裡列出百度百科補碼的三個特性方便理解:

1、一個負整數(或原碼)與其補數(或補碼)相加,和為模。
2、對一個整數的補碼再求補碼,等於該整數自身。
3、補碼的正零與負零表示方法相同。

例1:

原始long 794056495088

原始long轉為二進位 1011100011100001011101001000111111110000

取低位32位作為補碼(結果是負數):11100001011101001000111111110000

符號位不變,剩餘數位取反+1:10011110100010110111000000010000

轉為帶符號10進位:-512454672

 

再看一個強轉後符號不改變的,這種事不多練兩遍記不住。

例2:

原始long 889116247210

原始long轉為二進位 1100111100000011011101010100010010101010

取低位32位作為補碼(結果是正數):00000011011101010100010010101010

 

轉為帶符號10進位:58016938

 

所以,原始long低32位上的那個數字很重要,但是人家原來不是表示符號的(不一定會跟原始long的符號位數字相同啊),強轉後就按符號位處理了,大概率要出問題。

當然,明白原理後程式上做處理就可以了。不要直接反強轉結果,要麼自己寫下,要麼使用Long.compare這個靜態方法。

 

另外說到long,我就想到Long這些包裝類,雖然本次問題與包裝類無關。

寫了一些測試程式碼,結論與大家分享:

Long l1=12345l;
Long l2=12345l;
Long l3=1234l;
System.out.println(l1>l2);
System.out.println(l1>=l2);
System.out.println(l1==l2);
System.out.println();
System.out.println(l1>l3);
System.out.println(l1>=l3);
System.out.println(l1==l3);

運行結果:

false
true
false

true
true
false

1、如果使用包裝類進行等於(==)的比較,比較的是Long對象的地址,故可以使用equals或者.longvalue比較數值。

2、如果使用包裝類進行包含大於小於(>、<、>=、<=)的比較,比較時jvm會進行自動拆箱,因此直接比就好了。

打破砂鍋不是為了吹毛求疵,而是為了不受【常識】所限的、更好的定位問題。

 

最後,感謝不完善、感謝未完成,讓我每日都有動力去打破砂鍋、進步一點。

 

參考資料:

https://developer.qiniu.com/kodo/kb/1336/upload-download-instructions

https://blog.csdn.net/gdhgr/article/details/79604250

https://baike.baidu.com/item/%E8%A1%A5%E7%A0%81/6854613?fr=aladdin