程序兵法:Java String 源码的排序算法(一)
這是泥瓦匠的第103篇原創(chuàng)
《程序兵法:Java String 源碼的排序算法(一)》
文章工程:
* JDK 1.8
* 工程名:algorithm-core-learning # StringComparisonDemo
* 工程地址:https://github.com/JeffLi1993/algorithm-core-learning
一、前言
Q:什么是選擇問(wèn)題?
選擇問(wèn)題,是假設(shè)一組 N 個(gè)數(shù),要確定其中第 K 個(gè)最大值者。比如 A 與 B 對(duì)象需要哪個(gè)更大?又比如:要考慮從一些數(shù)組中找出最大項(xiàng)?
解決選擇問(wèn)題,需要對(duì)象有個(gè)能力,即比較任意兩個(gè)對(duì)象,并確定哪個(gè)大,哪個(gè)小或者相等。找出最大項(xiàng)問(wèn)題的解決方法,只要依次用對(duì)象的比較(Comparable)能力,循環(huán)對(duì)象列表,一次就能解決。
那么 JDK 源碼如何實(shí)現(xiàn)比較(Comparable)能力的呢?
二、java.lang.Comparable 接口
?
Comparable?接口,從 JDK 1.2 版本就有了,歷史算悠久。Comparable?接口強(qiáng)制了實(shí)現(xiàn)類(lèi)對(duì)象列表的排序。其排序稱(chēng)為自然順序,其?compareTo?方法,稱(chēng)為自然比較法。
該接口只有一個(gè)方法?public int compareTo(T o);?,可以看出
- 入?yún)?T o :實(shí)現(xiàn)該接口類(lèi),傳入對(duì)應(yīng)的要被比較的對(duì)象
- 返回值 int:正數(shù)、負(fù)數(shù)和 0 ,代表大于、小于和等于
對(duì)象的集合列表(Collection List)或者數(shù)組(arrays) ,也有對(duì)應(yīng)的工具類(lèi)可以方便的使用:
- java.util.Collections#sort(List) 列表排序
- java.util.Arrays#sort(Object[]) 數(shù)組排序
那 String 對(duì)象如何被比較的?
三、String 源碼中的算法
String 源碼中可以看到 String JDK 1.0 就有了。那么應(yīng)該是 JDK 1.2 的時(shí)候,String 類(lèi)實(shí)現(xiàn)了 Comparable 接口,并且傳入需要被比較的對(duì)象是 String。對(duì)象如圖:
?
String 是一個(gè) final 類(lèi),無(wú)法從 String 擴(kuò)展新的類(lèi)。從 114 行,可以看出字符串的存儲(chǔ)結(jié)構(gòu)是字符(Char)數(shù)組。先可以看看一個(gè)字符串比較案例,代碼如下:
/*** 字符串比較案例** Created by bysocket on 19/5/10.*/ public class StringComparisonDemo { public static void main(String[] args) { String foo = "ABC"; // 前面和后面每個(gè)字符完全一樣,返回 0 String bar01 = "ABC"; System.out.println(foo.compareTo(bar01)); // 前面每個(gè)字符完全一樣,返回:后面就是字符串長(zhǎng)度差 String bar02 = "ABCD"; String bar03 = "ABCDE"; System.out.println(foo.compareTo(bar02)); // -1 (前面相等,foo 長(zhǎng)度小 1) System.out.println(foo.compareTo(bar03)); // -2 (前面相等,foo 長(zhǎng)度小 2) // 前面每個(gè)字符不完全一樣,返回:出現(xiàn)不一樣的字符 ASCII 差 String bar04 = "ABD"; String bar05 = "aABCD"; System.out.println(foo.compareTo(bar04)); // -1 (foo 的 'C' 字符 ASCII 碼值為 67,bar04 的 'D' 字符 ASCII 碼值為 68。返回 67 - 68 = -1) System.out.println(foo.compareTo(bar05)); // -32 (foo 的 'A' 字符 ASCII 碼值為 65,bar04 的 'a' 字符 ASCII 碼值為 97。返回 65 - 97 = -32) String bysocket01 = "泥瓦匠"; String bysocket02 = "瓦匠"; System.out.println(bysocket01.compareTo(bysocket02));// -2049 (泥 和 瓦的 Unicode 差值) } }
運(yùn)行結(jié)果如下:
0 -1 -2 -1 -32 -2049可以看出,?compareTo?方法是按字典順序比較兩個(gè)字符串。具體比較規(guī)則可以看代碼注釋。比較規(guī)則如下:
- 字符串的每個(gè)字符完全一樣,返回 0
- 字符串前面部分的每個(gè)字符完全一樣,返回:后面就是兩個(gè)字符串長(zhǎng)度差
- 字符串前面部分的每個(gè)字符存在不一樣,返回:出現(xiàn)不一樣的字符 ASCII 碼的差值
- 中文比較返回對(duì)應(yīng)的 Unicode 編碼值(Unicode 包含 ASCII)
- foo 的 ‘C’ 字符 ASCII 碼值為 67
- bar04 的 ‘D’ 字符 ASCII 碼值為 68。
- foo.compareTo(bar04),返回 67 – 68 = -1
- 常見(jiàn)字符 ASCII 碼,如圖所示
再看看 String 的?compareTo?方法如何實(shí)現(xiàn)字典順序的。源碼如圖:
?
源碼解析如下:
- 第 1156 行:獲取當(dāng)前字符串和另一個(gè)字符串,長(zhǎng)度較小的長(zhǎng)度值 lim
- 第 1161 行:如果 lim 大于 0 (較小的字符串非空),則開(kāi)始比較
- 第 1164 行:當(dāng)前字符串和另一個(gè)字符串,依次字符比較。如果不相等,則返回兩字符的 Unicode 編碼值的差值
- 第 1169 行:當(dāng)前字符串和另一個(gè)字符串,依次字符比較。如果均相等,則返回兩個(gè)字符串長(zhǎng)度的差值
所以要排序,肯定先有比較能力,即實(shí)現(xiàn) Comparable 接口。然后實(shí)現(xiàn)此接口的對(duì)象列表(和數(shù)組)可以通過(guò) Collections.sort(和 Arrays.sort)進(jìn)行排序。
還有 TreeSet 使用樹(shù)結(jié)構(gòu)實(shí)現(xiàn)(紅黑樹(shù)),集合中的元素進(jìn)行排序。其中排序就是實(shí)現(xiàn) Comparable 此接口
另外,如果沒(méi)有實(shí)現(xiàn) Comparable 接口,使用排序時(shí),會(huì)拋出 java.lang.ClassCastException 異常。詳細(xì)看《Java 集合:三、HashSet,TreeSet 和 LinkedHashSet比較》https://www.bysocket.com/archives/195
四、小結(jié)
上面也說(shuō)到,這種比較其實(shí)有一定的弊端:
- 默認(rèn) compareTo 不忽略字符大小寫(xiě)。如果需要忽略,則重新自定義 compareTo 方法
- 無(wú)法進(jìn)行二維的比較決策。比如判斷 2 * 1 矩形和 3 * 3 矩形,哪個(gè)更大?
- 比如有些類(lèi)無(wú)法實(shí)現(xiàn)該接口。一個(gè) final 類(lèi),也無(wú)法擴(kuò)展新的類(lèi)。其也有解決方案:函數(shù)對(duì)象(Function Object)
方法參數(shù):定義一個(gè)沒(méi)有數(shù)據(jù)只有方法的類(lèi),并傳遞該類(lèi)的實(shí)例。一個(gè)函數(shù)通過(guò)將其放在一個(gè)對(duì)象內(nèi)部而被傳遞。這種對(duì)象通常叫做函數(shù)對(duì)象(Funtion Object)
在接口方法設(shè)計(jì)中,?T execute(Callback?callback) 參數(shù)中使用 callback 類(lèi)似。比如在 Spring 源碼中,可以看出很多設(shè)計(jì)是:聚合優(yōu)先于繼承或者實(shí)現(xiàn)。這樣可以減少很多繼承或者實(shí)現(xiàn)。類(lèi)似 SpringJdbcTemplate 場(chǎng)景設(shè)計(jì),可以考慮到這種 Callback 設(shè)計(jì)實(shí)現(xiàn)。
代碼示例
本文示例讀者可以通過(guò)查看下面?zhèn)}庫(kù)的中: StringComparisonDemo 字符串比較案例案例:
- Github:https://github.com/JeffLi1993/algorithm-core-learning
- Gitee:https://gitee.com/jeff1993/algorithm-core-learning
如果您對(duì)這些感興趣,歡迎 star、follow、收藏、轉(zhuǎn)發(fā)給予支持!
參考資料
- 《數(shù)據(jù)結(jié)構(gòu)與算法分析:Java語(yǔ)言描述(原書(shū)第3版)》
- https://en.wikipedia.org/wiki/Unicode
- https://www.cnblogs.com/vamei/tag/%E7%AE%97%E6%B3%95/
- https://www.bysocket.com/archives/2314/algorithm
以下專(zhuān)題教程也許您會(huì)有興趣
- 《程序兵法:算法與數(shù)據(jù)結(jié)構(gòu)》?https://www.bysocket.com/archives/2314/algorithm
- 《Spring Boot 2.x 系列教程》
https://www.bysocket.com/springboot - 《Java 核心系列教程》
https://www.bysocket.com/archives/2100
(關(guān)注微信公眾號(hào),領(lǐng)取 Java 精選干貨學(xué)習(xí)資料)?
(添加我微信:bysocket01。加入純技術(shù)交流群,成長(zhǎng)技術(shù))
轉(zhuǎn)載于:https://www.cnblogs.com/Alandre/p/10899359.html
超強(qiáng)干貨來(lái)襲 云風(fēng)專(zhuān)訪:近40年碼齡,通宵達(dá)旦的技術(shù)人生總結(jié)
以上是生活随笔為你收集整理的程序兵法:Java String 源码的排序算法(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: vi在一般指令模式下几个实用的命令
- 下一篇: Java 语法糖详解