導航:首頁 > 源碼編譯 > rabixtree源碼分析

rabixtree源碼分析

發布時間:2023-02-02 18:43:26

① 深入淺出的分析 Set集合

Set集合的特點主要有:元素不重復、存儲無序的特點。

打開 Set 集合,主要實現類有 HashSet、LinkedHashSet 、TreeSet 、EnumSet( RegularEnumSet、JumboEnumSet )等等,總結 Set 介面實現類,圖如下:

由圖中的繼承關系,可以知道,Set 介面主要實現類有 AbstractSet、HashSet、LinkedHashSet 、TreeSet 、EnumSet( RegularEnumSet、JumboEnumSet ),其中 AbstractSet、EnumSet 屬於抽象類,EnumSet 是在 jdk1.5 中新增的,不同的是 EnumSet 集合元素必須是枚舉類型。

HashSet 是一個輸入輸出無序的集合,集合中的元素基於 HashMap 的 key 實現,元素不可重復;
LinkedHashSet 是一個輸入輸出有序的集合,集合中的元素基於 LinkedHashMap 的 key 實現,元素也不可重復;
TreeSet 是一個排序的集合,集合中的元素基於 TreeMap 的 key 實現,同樣元素不可重復;
EnumSet 是一個與枚舉類型一起使用的專用 Set 集合,其中 RegularEnumSet 和 JumboEnumSet 不能單獨實例化,只能由 EnumSet 來生成,同樣元素不可重復;
下面咱們來對各個主要實現類進行一一分析!

HashSet 是一個輸入輸出無序的集合,底層基於 HashMap 來實現,HashSet 利用 HashMap 中的key元素來存放元素,這一點我們可以從源碼上看出來,閱讀源碼如下:

public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable{

}

打開HashSet的add()方法,源碼如下:

public boolean add(E e) {
//向 HashMap 中添加元素
return map.put(e, PRESENT)==null;
}

其中變數PRESENT,是一個非空對象,源碼部分如下:
private static final Object PRESENT = new Object();

可以分析出,當進行add()的時候,等價於
HashMap map = new HashMap<>();
map.put(e, new Object());//e 表示要添加的元素

在之前的集合文章中,咱們了解到 HashMap 在添加元素的時候 ,通過equals()和hashCode()方法來判斷傳入的key是否相同,如果相同,那麼 HashMap 認為添加的是同一個元素,反之,則不是。

從源碼分析上可以看出,HashSet 正是使用了 HashMap 的這一特性,實現存儲元素下標無序、元素不會重復的特點。

HashSet 的刪除方法,同樣如此,也是基於 HashMap 的底層實現,源碼如下:

public boolean remove(Object o) {
//調用HashMap 的remove方法,移除元素
return map.remove(o)==PRESENT;
}

HashSet 沒有像 List、Map 那樣提供 get 方法,而是使用迭代器或者 for 循環來遍歷元素,方法如下:

public static void main(String[] args) {
Set<String> hashSet = new HashSet<String>();
System.out.println("HashSet初始容量大小:"+hashSet.size());
hashSet.add("1");
hashSet.add("2");
hashSet.add("3");
hashSet.add("3");
hashSet.add("2");
hashSet.add(null);

}

輸出結果:
HashSet初始容量大小:0
HashSet容量大小:4
null,1,2,3,
===========
null,1,2,3,

需要注意的是,HashSet 允許添加為null的元素。

LinkedHashSet 是一個輸入輸出有序的集合,繼承自 HashSet,但是底層基於 LinkedHashMap 來實現。

如果你之前了解過 LinkedHashMap,那麼你一定知道,它也繼承自 HashMap,唯一有區別的是,LinkedHashMap 底層數據結構基於循環鏈表實現,並且數組指定了頭部和尾部,雖然數組的下標存儲無序,但是卻可以通過數組的頭部和尾部,加上循環鏈表,依次可以查詢到元素存儲的過程,從而做到輸入輸出有序的特點。

如果還不了解 LinkedHashMap 的實現過程,可以參閱集合系列中關於 LinkedHashMap 的實現過程文章。

閱讀 LinkedHashSet 的源碼,類定義如下:

public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {

}

查詢源碼,super調用的方法,源碼如下:

HashSet(int initialCapacity, float loadFactor, boolean mmy) {
//初始化一個 LinkedHashMap
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

LinkedHshSet沒有重寫add方法,而是直接調用HashSet的add()方法,因為map的實現類是LinkedHashMap,所以此處是向LinkedHashMap中添加元素,當進行add()的時候,等價於
HashMap map = new LinkedHashMap<>();
map.put(e, new Object());//e 表示要添加的元素

LinkedHashSet也沒有重寫remove方法,而是直接調用HashSet的刪除方法,因為LinkedHashMap沒有重寫remove方法,所以調用的也是HashMap的remove方法,源碼如下:

public boolean remove(Object o) {
//調用HashMap 的remove方法,移除元素
return map.remove(o)==PRESENT;
}

同樣的,LinkedHashSet 沒有提供 get 方法,使用迭代器或者 for 循環來遍歷元素,方法如下:

public static void main(String[] args) {
Set<String> linkedHashSet = new LinkedHashSet<String>();
System.out.println("linkedHashSet初始容量大小:"+linkedHashSet.size());
linkedHashSet.add("1");
linkedHashSet.add("2");
linkedHashSet.add("3");
linkedHashSet.add("3");
linkedHashSet.add("2");
linkedHashSet.add(null);
linkedHashSet.add(null);

}

輸出結果:
linkedHashSet初始容量大小:0
linkedHashSet容量大小:4
1,2,3,null,
===========
1,2,3,null,
可見,LinkedHashSet 與 HashSet 相比,LinkedHashSet 輸入輸出有序。

TreeSet 是一個排序的集合,實現了NavigableSet、SortedSet、Set介面,底層基於 TreeMap 來實現。TreeSet 利用 TreeMap 中的key元素來存放元素,這一點我們也可以從源碼上看出來,閱讀源碼,類定義如下:

public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable {

}

new TreeSet<>()對象實例化的時候,表達的意思,可以簡化為如下:
NavigableMap<E,Object> m = new TreeMap<E,Object>();

因為TreeMap實現了NavigableMap介面,所以沒啥問題。

public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable{
......
}

打開TreeSet的add()方法,源碼如下:

public boolean add(E e) {
//向 TreeMap 中添加元素
return m.put(e, PRESENT)==null;
}

其中變數PRESENT,也是是一個非空對象,源碼部分如下:
private static final Object PRESENT = new Object();

可以分析出,當進行add()的時候,等價於
TreeMap map = new TreeMap<>();
map.put(e, new Object());//e 表示要添加的元素

TreeMap 類主要功能在於,給添加的集合元素,按照一個的規則進行了排序,默認以自然順序進行排序,當然也可以自定義排序,比如測試方法如下:

public static void main(String[] args) {
Map initMap = new TreeMap();
initMap.put("4", "d");
initMap.put("3", "c");
initMap.put("1", "a");
initMap.put("2", "b");
//默認自然排序,key為升序
System.out.println("默認 排序結果:" + initMap.toString());
//自定義排序,在TreeMap初始化階段傳入Comparator 內部對象
Map comparatorMap = new TreeMap<String, String>(new Comparator<String>() {
@Override
public int compare(String o1, String o2){
//根據key比較大小,採用倒敘,以大到小排序
return o2.compareTo(o1);
}
});
comparatorMap.put("4", "d");
comparatorMap.put("3", "c");
comparatorMap.put("1", "a");
comparatorMap.put("2", "b");
System.out.println("自定義 排序結果:" + comparatorMap.toString());
}

輸出結果:
默認 排序結果:{1=a, 2=b, 3=c, 4=d}
自定義 排序結果:{4=d, 3=c, 2=b, 1=a}

相信使用過TreeMap的朋友,一定知道TreeMap會自動將key按照一定規則進行排序,TreeSet正是使用了TreeMap這種特性,來實現添加的元素集合,在輸出的時候,其結果是已經排序好的。

如果您沒看過源碼TreeMap的實現過程,可以參閱集合系列文章中TreeMap的實現過程介紹,或者閱讀 jdk 源碼。

TreeSet 的刪除方法,同樣如此,也是基於 TreeMap 的底層實現,源碼如下:

public boolean remove(Object o) {
//調用TreeMap 的remove方法,移除元素
return m.remove(o)==PRESENT;
}

TreeSet 沒有重寫 get 方法,而是使用迭代器或者 for 循環來遍歷元素,方法如下:

public static void main(String[] args) {
Set<String> treeSet = new TreeSet<>();
System.out.println("treeSet初始容量大小:"+treeSet.size());
treeSet.add("1");
treeSet.add("4");
treeSet.add("3");
treeSet.add("8");
treeSet.add("5");

}

輸出結果:
treeSet初始容量大小:0
treeSet容量大小:5
1,3,4,5,8,
===========
1,3,4,5,8,

使用自定義排序,有 2 種方法,第一種在需要添加的元素類,實現Comparable介面,重寫compareTo方法來實現對元素進行比較,實現自定義排序。

/**

創建一個Person實體類,實現Comparable介面,重寫compareTo方法,通過變數age實現自定義排序 測試方法如下:

public static void main(String[] args) {
Set<Person> treeSet = new TreeSet<>();
System.out.println("treeSet初始容量大小:"+treeSet.size());
treeSet.add(new Person("李一",18));
treeSet.add(new Person("李二",17));
treeSet.add(new Person("李三",19));
treeSet.add(new Person("李四",21));
treeSet.add(new Person("李五",20));

}

輸出結果:
treeSet初始容量大小:0
treeSet容量大小:5
按照年齡從小到大,自定義排序結果:
李二:17,李一:18,李三:19,李五:20,李四:21,

第二種方法是在TreeSet初始化階段,Person不用實現Comparable介面,將Comparator介面以內部類的形式作為參數,初始化進去,方法如下:

public static void main(String[] args) {
//自定義排序
Set<Person> treeSet = new TreeSet<>(new Comparator<Person>(){
@Override
public int compare(Person o1, Person o2) {
if(o1 == null || o2 == null){
//不用比較
return 0;
}
//從小到大進行排序
return o1.getAge() - o2.getAge();
}
});
System.out.println("treeSet初始容量大小:"+treeSet.size());
treeSet.add(new Person("李一",18));
treeSet.add(new Person("李二",17));
treeSet.add(new Person("李三",19));
treeSet.add(new Person("李四",21));
treeSet.add(new Person("李五",20));

}

輸出結果:
treeSet初始容量大小:0
treeSet容量大小:5
按照年齡從小到大,自定義排序結果:
李二:17,李一:18,李三:19,李五:20,李四:21,
需要注意的是,TreeSet不能添加為空的元素,否則會報空指針錯誤!

EnumSet 是一個與枚舉類型一起使用的專用 Set 集合,繼承自AbstractSet抽象類。與 HashSet、LinkedHashSet 、TreeSet 不同的是,EnumSet 元素必須是Enum的類型,並且所有元素都必須來自同一個枚舉類型,EnumSet 定義源碼如下:

public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
implements Cloneable, java.io.Serializable {
......
}

EnumSet是一個虛類,不能直接通過實例化來獲取對象,只能通過它提供的靜態方法來返回EnumSet實現類的實例。

EnumSet的實現類有兩個,分別是RegularEnumSet、JumboEnumSet兩個類,兩個實現類都繼承自EnumSet。

EnumSet會根據枚舉類型中元素的個數,來決定是返回哪一個實現類,當 EnumSet元素中的元素個數小於或者等於64,就會返回RegularEnumSet實例;當EnumSet元素個數大於64,就會返回JumboEnumSet實例。

這一點,我們可以從源碼中看出,源碼如下:

public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
//當元素個數小於或者等於 64 的時候,返回 RegularEnumSet
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
//大於64,返回 JumboEnumSet
return new JumboEnumSet<>(elementType, universe);
}
noneOf是EnumSet中一個靜態方法,用於判斷是返回哪一個實現類。

我們來看看當元素個數小於等於64的時候,使用RegularEnumSet的類,源碼如下:

class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {

}
RegularEnumSet 通過二進制運算得到結果,直接使用long來存放元素。

我們再來看看當元素個數大於64的時候,使用JumboEnumSet的類,源碼如下:

class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {

}
JumboEnumSet 也是通過二進制運算得到結果,使用long來存放元素,但是它是使用數組來存放元素。

二者相比,RegularEnumSet 效率比 JumboEnumSet 高些,因為操作步驟少,大多數情況下返回的是 RegularEnumSet,只有當枚舉元素個數超過 64 的時候,會使用 JumboEnumSet。

添加元素:
//新建一個EnumEntity的枚舉類型,定義2個參數
public enum EnumEntity {
WOMAN,MAN;
}

創建一個空的 EnumSet:
//創建一個 EnumSet,內容為空
EnumSet<EnumEntity> noneSet = EnumSet.noneOf(EnumEntity.class);
System.out.println(noneSet);
輸出結果:
[]

創建一個 EnumSet,並將枚舉類型的元素全部添加進去:
//創建一個 EnumSet,將EnumEntity 元素內容添加到EnumSet中
EnumSet<EnumEntity> allSet = EnumSet.allOf(EnumEntity.class);
System.out.println(allSet);

輸出結果:
[WOMAN, MAN]

創建一個 EnumSet,添加指定的枚舉元素:
//創建一個 EnumSet,添加 WOMAN 到 EnumSet 中
EnumSet<EnumEntity> customSet = EnumSet.of(EnumEntity.WOMAN);
System.out.println(customSet);

查詢元素
EnumSet與HashSet、LinkedHashSet、TreeSet一樣,通過迭代器或者 for 循環來遍歷元素,方法如下:

EnumSet<EnumEntity> allSet = EnumSet.allOf(EnumEntity.class);
for (EnumEntity enumEntity : allSet) {
System.out.print(enumEntity + ",");
}

輸出結果:
WOMAN,MAN,

HashSet 是一個輸入輸出無序的 Set 集合,元素不重復,底層基於 HashMap 的 key 來實現,元素可以為空,如果添加的元素為對象,對象需要重寫 equals() 和 hashCode() 方法來約束是否為相同的元素。

LinkedHashSet 是一個輸入輸出有序的 Set 集合,繼承自 HashSet,元素不重復,底層基於 LinkedHashMap 的 key來實現,元素也可以為空,LinkedHashMap 使用循環鏈表結構來保證輸入輸出有序。

TreeSet 是一個排序的 Set 集合,元素不可重復,底層基於 TreeMap 的 key來實現,元素不可以為空,默認按照自然排序來存放元素,也可以使用 Comparable 和 Comparator 介面來比較大小,實現自定義排序。

EnumSet 是一個與枚舉類型搭配使用的專用 Set 集合,在 jdk1.5 中加入。EnumSet 是一個虛類,有2個實現類 RegularEnumSet、JumboEnumSet,不能顯式的實例化改類,EnumSet 會動態決定使用哪一個實現類,當元素個數小於等於64的時候,使用 RegularEnumSet;大於 64的時候,使用JumboEnumSet類,EnumSet 其內部使用位向量實現,擁有極高的時間和空間性能,如果元素是枚舉類型,推薦使用 EnumSet。

1、JDK1.7&JDK1.8 源碼

2、程序園 - java集合-EnumMap與EnumSet

3、 Java極客技術 - https://blog.csdn.net/javageektech/article/details/103077788

② nginx 源代碼分析 (一)

ngx_pool_t提供內存分配介面。

ngx_pool_t對大塊內存和小塊內存的分配使用不同的策略。

對於大塊內存(超過指定閾值,一般是內存的頁大小4096),調用malloc()動態分配。

對於小塊內存,則是先預分配大的內存塊,然後根據需要從中動態分配小塊的。

ngx_create_pool()創建這個單向鏈表的第一個元素。

ngx_palloc()從指定的ngx_pool_t實例中分配內存。如果要求大塊內存,則調用ngx_palloc_large(),否則調用ngx_palloc_small()。

對於ngx_palloc_small(),

對於ngx_palloc_large(),

ngx_free()負責釋放內存。它只是調用free()釋放大塊內存。實際上小塊內存是不能一個一個釋放的,nginx的策略是釋放就釋放全部。

ngx_reset_pool()釋放所有大塊內存,將ngx_pool_t鏈表置於未使用狀態。

ngx_destroy_pool()釋放所有大塊內存,也釋放ngx_pool_t鏈表自身佔用的內存。

nginx_pool_t還用於保存「待清理的任務」,這個任務保存在ngx_pool_cleanup_t結構中,從預分配內存中分配。這也是一個單向鏈表,保存在ngx_pool_t的成員cleanup中。

ngx_pool_cleanup_t的成員handler是任務的處理函數,成員data是處理函數的參數。

ngx_array_t實現了數組。

ngx_array_create()創建數組。

ngx_array_push()從數組中得到一個可用的元素。

ngx_list_create()實現單向鏈表。與一般鏈表不同的是,它的鏈表中每一個元素中保存的是一個數組。下圖是包含兩個ngx_list_part_t元素的鏈表。

ngx_list_create()創建一個鏈表。

ngx_list_push()從鏈表中得到一個可用的位置。

ngx_queue_t是一個雙向鏈表。

使用ngx_queue_t的方式是,在結構中包含ngx_queue_t,就可以把結構串聯起來。如下面的示意圖。

ngx_queue_data()可以根據ngx_queue_t成員在結構中的位置偏移,從成員地址計算結構地址。

ngx_rbtree_t實現紅黑樹,ngx_rbtree_node_t是樹上的節點。

對於ngx_rbtree_node_t,

對於ngx_rbtree_t,

③ 哈希演算法從原理到實戰

引言 

       將任意長度的二進制字元串映射為定長二進制字元串的映射規則我們稱為散列(hash)演算法,又叫哈希(hash)演算法,而通過原始數據映射之後得到的二進制值稱為哈希值。哈希表(hash表)結構是哈希演算法的一種應用,也叫散列表。用的是數組支持按照下標隨機訪問數據的特性擴展、演化而來。可以說沒有數組就沒有散列表。

哈希演算法主要特點

        從哈希值不能反向推導原始數據,也叫單向哈希。

        對輸入數據敏感,哪怕只改了一個Bit,最後得到的哈希值也大不相同。

        散列沖突的概率要小。

        哈希演算法執行效率要高,散列結果要盡量均衡。

哈希演算法的核心應用

         安全加密 :對於敏感數據比如密碼欄位進行MD5或SHA加密傳輸。

         唯一標識 :比如圖片識別,可針對圖像二進制流進行摘要後MD5,得到的哈希值作為圖片唯一標識。

         散列函數 :是構造散列表的關鍵。它直接決定了散列沖突的概率和散列表的性質。不過相對哈希演算法的其他方面應用,散列函數對散列沖突要求較低,出現沖突時可以通過開放定址法或鏈表法解決沖突。對散列值是否能夠反向解密要求也不高。反而更加關注的是散列的均勻性,即是否散列值均勻落入槽中以及散列函數執行的快慢也會影響散列表性能。所以散列函數一般比較簡單,追求均勻和高效。

        *負載均衡 :常用的負載均衡演算法有很多,比如輪詢、隨機、加權輪詢。如何實現一個會話粘滯的負載均衡演算法呢?可以通過哈希演算法,對客戶端IP地址或會話SessionID計算哈希值,將取得的哈希值與伺服器列表大小進行取模運算,最終得到應該被路由到的伺服器編號。這樣就可以把同一IP的客戶端請求發到同一個後端伺服器上。

        *數據分片 :比如統計1T的日誌文件中「搜索關鍵詞」出現次數該如何解決?我們可以先對日誌進行分片,然後採用多機處理,來提高處理速度。從搜索的日誌中依次讀取搜索關鍵詞,並通過哈希函數計算哈希值,然後再跟n(機器數)取模,最終得到的值就是應該被分到的機器編號。這樣相同哈希值的關鍵詞就被分到同一台機器進行處理。每台機器分別計算關鍵詞出現的次數,再進行合並就是最終結果。這也是MapRece的基本思想。再比如圖片識別應用中給每個圖片的摘要信息取唯一標識然後構建散列表,如果圖庫中有大量圖片,單機的hash表會過大,超過單機內存容量。這時也可以使用分片思想,准備n台機器,每台機器負責散列表的一部分數據。每次從圖庫取一個圖片,計算唯一標識,然後與機器個數n求余取模,得到的值就是被分配到的機器編號,然後將這個唯一標識和圖片路徑發往對應機器構建散列表。當進行圖片查找時,使用相同的哈希函數對圖片摘要信息取唯一標識並對n求余取模操作後,得到的值k,就是當前圖片所存儲的機器編號,在該機器的散列表中查找該圖片即可。實際上海量數據的處理問題,都可以藉助這種數據分片思想,突破單機內存、CPU等資源限制。

        *分布式存儲 :一致性哈希演算法解決緩存等分布式系統的擴容、縮容導致大量數據搬移難題。

         JDK集合工具實現 :HashMap、 LinkedHashMap、ConcurrentHashMap、TreeMap等。Map實現類源碼分析,詳見  https://www.jianshu.com/p/602324fa59ac

總結

        本文從哈希演算法的原理及特點,總結了哈希演算法的常見應用場景。

        其中基於余數思想和同餘定理實現的哈希演算法(除留取余法),廣泛應用在分布式場景中(散列函數、數據分片、負載均衡)。由於組合數學中的「鴿巢」原理,理論上不存在完全沒有沖突的哈希演算法。(PS:「鴿巢」原理是指有限的槽位,放多於槽位數的鴿子時,勢必有不同的鴿子落在同一槽內,即沖突發生。同餘定理:如果a和b對x取余數操作時a%x = b%x,則a和b同餘)

        構造哈希函數的常規方法有:數據分析法、直接定址法、除留取余法、折疊法、隨機法、平方取中法等  。

        常規的解決哈希沖突方法有開放定址法(線性探測、再哈希)和鏈表法。JDK中的HashMap和LinkedHashMap均是採用鏈表法解決哈希沖突的。鏈表法適合大數據量的哈希沖突解決,可以使用動態數據結構(比如:跳錶、紅黑樹等)代替鏈表,防止鏈表時間復雜度過度退化導致性能下降;反之開放定址法適合少量數據的哈希沖突解決。

④ 求java學習路線圖

/*回答內容很長,能看完的少走一個月彎路,絕不抖機靈*/

提前預警:本文適合Java新手閱讀(老手可在評論區給下建議),希望大家看完能有所收獲。

廢話不多少了,先了解一下Java零基礎入門學習路線:

第一階段:JavaSE階段

變數、數據類型、運算符

控制語句

面向對象編程-基礎

面向對象編程-進階

異常機制

Java常用類

Wrapper包裝類

第二階段:資料庫

第三階段:JavaEE階段

第四階段:框架階段

第五階段:前後端分離階段

第六階段:微服務架構

第七階段:雲服務階段

閱讀全文

與rabixtree源碼分析相關的資料

熱點內容
怎麼雙向傳輸伺服器 瀏覽:286
電腦如何實現跨網段訪問伺服器 瀏覽:549
模塊化網頁源碼位元組跳動 瀏覽:485
梯度下降演算法中遇到的問題 瀏覽:605
伺服器連接電視怎麼接 瀏覽:323
phploop語句 瀏覽:500
交叉編譯工具鏈里的庫在哪 瀏覽:781
安卓手q換號怎麼改綁 瀏覽:399
nba球星加密貨幣 瀏覽:789
命令看網速 瀏覽:124
java堆分配 瀏覽:161
linuxbuiltin 瀏覽:560
cstpdf 瀏覽:941
texstudio編譯在哪 瀏覽:352
國家反詐中心app注冊登記表怎麼注冊 瀏覽:972
加密機默認埠 瀏覽:101
有哪個網站有免費的python源代碼 瀏覽:305
蘋果手機如何導入安卓電話 瀏覽:915
奧利奧雙重解壓 瀏覽:388
安卓賬號怎麼在蘋果手機上玩 瀏覽:798