导航:首页 > 源码编译 > 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源码分析相关的资料

热点内容
vue类似电脑文件夹展示 浏览:111
后备服务器有什么功效 浏览:268
连不上服务器怎么连 浏览:600
什么构架的可以刷安卓系统 浏览:771
爱奇艺APP怎么兑换CDK 浏览:994
程序员买4k显示器还是2k显示器 浏览:144
python多进程怎么多窗口 浏览:818
电脑文件夹怎么取消类别 浏览:47
cad拉线段命令 浏览:924
如何用电脑清理手机没用的文件夹 浏览:100
储存层次结构对程序员的意义 浏览:477
微信文件夹查看器 浏览:952
android视频聊天开源 浏览:552
思科iso命令 浏览:944
手机网页源码里的视频地址 浏览:681
哈利波特魔法觉醒要怎么选服务器 浏览:993
情感交友网站php 浏览:942
id下载不了app怎么回事 浏览:995
有什么好看的服务器小说 浏览:293
程序员四级没过有什么影响 浏览:540