java Set 的一些总结

来源:互联网 发布:2016年网络新书排行榜 编辑:程序博客网 时间:2024/06/10 04:21
最近学习了一下java set 总结如下:

1. set接口 : 此接口定义了一个不包含重复元素的 collection接口,set定义了不包含满足 e1.equals(e2) 的元素,并且最多包含一个null元素。此接口的实现类引入了不同的因素影响着 e1.equals(e2) 的结果,所以具体涉及到的影响因子要是具体的实现类而定。

2. 实现类HashSet: 

这个实现类实现了 set接口,set接口定义 集合中不包含 e1.equals(e2)的元素,但我们知道HashSet 要保存不同的值,则需要重写其 hashCode方法和 equals方法。这个和HashSet的内部实现原理密切相关的,在HashSet的内部用了一个HashMap来存储元素。

在HashSet的 JDK接口中添加方法值这样定义的:
public boolean add(E e) {
     return map.put(e, PRESENT)== null;
}
// Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
PRESENT 是一个虚假的值,其实其对象存在了HashMap的key上。
那我们在来看HashMap的存储的这段代码:


画红框的这段代码就觉得了我们在用HashSet 在存储对象的时候必须要重写其hashcode 方法及 equals方法。
如果HashCode 和equals都不相等或其中一个不想等会怎么存储呢?先说结论然后我们在研究一下其实现的代码:
     如果equals相同,hashCode不同,两个相同的值的对象存储在不同的位置; 如果hashCode相同,equals 不同,则会采用链式接口将2个对象存储在同一个位置。这种开散列的方法想必我们大学里都学过了。
但是具体的实现是怎么样的呢,我们还是回过头来看HeshMap(因为HashSet测存储用的就是HashMap) 我们看一下上面那段代码。 如果画红框的那个条件校验失败,则会调用 addEntry 方法,那其实现就在
这个addEntry方法里了。addEntry的定义如下:


Entry的构造函数如下:


由此可见Entry就是一个链式存储结构,当table[bucketIndex] 这个槽为空的时候 e就是空值,那么传入的这个对象就占用了这个槽,如果table[bucketIndex] 不为空的时候就新添加的元素就会以开散列的方式
以链表的方式挂在前一个对象的后面。 
ps: 这里需要注意的一点就是 bucketIndex 是通过hashCode算出来

3.LinkedHashSet
    LinkedHashSet是HashSet的子类,相同点是它继承了set的基因就是它的元素不能有重复的,但是它和HashSet的不同是因为它是有序的,它以一个链表的方式维护着元素的顺序。
    那么它到底是怎么是实现的呢,那我们还是一起来分析一下JDK的代码:
    LinkedHashSet 的实现就只是写了几个构造函数,但是这个构造函数却让LinkedHashSet 和HashSet有了很大的不同,HashSet 的下层存储采用的是HashMap,但LinkedHashSet通过定义的构造函数将底层
的存储容器换成了LinkedHashMap,由此可见这个集合有序性的还是靠底层的存储LinkedHashMap来保证的。那就让我们来看一下LinkeHashMap的代码。
   LinkedHashMap 继承自HashMap,不同点是他维护了一个Entry的双重链表。可以说LinkedHashMap扬长避短的发挥了哈希表和链表的特点。

这里维护了一个链表的header。当向LinkedHashMap中插入一个元素的时候,方法继承自HashMap



  其不同的部分是LinkedHashMap中定义的Entry对象重写了recordAccess方法,通过这个recordAccess方法实现了链表的创建。
    LinkedHashMap 定义的Entry关键代码如下所示:


画红框的部分则实现了双重链表的实现逻辑。
     再来看LinkedHashMap当中实现的迭代器类的实现便利的就是其维护的双重链表,这就保证了其存储的元素的有序性。
     核心代码如下:

 
4. SortedSet
    这个接口定义了一个关于元素总体排序的Set。 这个接口要求加入到这个容器的每个元素都是可比较的。这接口提供的方法感觉可操作性更强,提供的比较能力更强。

5. TreeSet
     这个接口使用compareTo方法对所有元素进行比较,从Set的观点来看,此方法认为相等的两个元素就是相等的。即使Set的顺序与equals不一致。简单来说这个接口违背了Set接口的常规协定,
其实两个对象equals比较的对象不一样,但只要Comparator比较出来的值一样那也认为是一样的元素。总结一下, TreeSet 采用排序的存储结构是红黑树,支持2种排序方式:自然排序和定制排序。
自然排序,元素需要实现Comparable接口或者为元素指定比较器,元素需要为比较器所接受。
     TreeSet底层存储采用了是TreeMap,所以TreeMap的底层红黑树的存储结构就决定TreeSet的存储结构。
     TreeMap的节点定义关键代码是:




以上这段代码标红框的部分就决定了元素要么实现Comparable接口 要么定义Comparator。
     
     PS:以上提到的几种set都是现成不安全的Set类型。如果需要使用线程安全的Set,则需要对其进行包装一层。
     例如:  SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));       Set s = Collections.synchronizedSet(new HashSet(...)); 
     以上是抽个空学习总结了一下 set的知识,有什么不对的地方,请大家多多指正!

原创粉丝点击