HashSet
Java中的HashSet的底层原理主要基于哈希表(HashMap)来实现。以下是HashSet底层原理的简要总结:
数据结构:
HashSet内部使用哈希表(实际上是一个HashMap的实例)来存储元素。哈希表由一个数组(桶)和多个链表(或红黑树,在Java 8及以后版本中,当链表长度超过某个阈值时,会转换为红黑树)组成。
哈希函数:
当向HashSet中添加元素时,首先会计算该元素的哈希码(通过调用对象的hashCode()方法)。哈希码是一个整数,用于确定元素在哈希表中的位置。
使用哈希码和哈希表的大小(即桶的数量)来计算元素应该存放在哪个桶中。这通常是通过哈希码与哈希表大小取模(hash % size)来实现的。
存储元素:
如果计算出的桶是空的,那么该元素就直接存放在这个桶中。
如果计算出的桶已经包含了一个或多个元素(即哈希冲突),那么这些元素会形成一个链表(或红黑树),并将新元素添加到链表的末尾(或红黑树中)。
查找元素:
当从HashSet中检索元素时,也会首先计算该元素的哈希码,然后使用哈希码来确定应该查找哪个桶。
接着,在对应的桶中遍历链表(或红黑树)来查找是否存在相同的元素。由于哈希码相同的元素被存放在同一个桶中,因此需要遍历链表(或红黑树)来找到具体的元素。
处理哈希冲突:
哈希冲突是指不同的元素具有相同的哈希码。HashSet使用链表(或红黑树)来处理哈希冲突,即将哈希码相同的元素存放在同一个链表(或红黑树)中。
动态扩容:
当哈希表中的元素数量达到某个阈值时(通常是哈希表大小的某个比例,如0.75),哈希表会自动扩容,以减小哈希冲突的可能性。扩容通常是通过创建一个新的、更大的哈希表,并将原哈希表中的元素重新分配到新的哈希表中来实现的。
无序性:
HashSet不保证元素的迭代顺序与插入顺序相同。这是因为元素在哈希表中的位置取决于其哈希码,而哈希码的计算与元素的插入顺序无关。
总之,HashSet通过哈希表和哈希函数实现了快速添加、删除和查找元素的功能,并且支持动态扩容来处理哈希冲突和保持性能。
LinkedHashSet
Java中的LinkedHashSet的底层原理主要基于HashMap和双向链表(doubly linked list)来实现。以下是对LinkedHashSet底层原理的简要总结:
数据结构:
LinkedHashSet内部使用一个特殊的HashMap实例(实际上是LinkedHashMap)来存储元素。这个HashMap不仅保存了键值对信息,还通过双向链表维护了元素的插入顺序。
双向链表:
与普通的HashMap不同,LinkedHashMap在HashMap的基础上增加了一个双向链表。这个双向链表用于维护元素的插入顺序(或访问顺序,取决于LinkedHashMap的构造参数)。
每个在LinkedHashMap中的节点(即Entry对象)都包含指向其前一个节点和后一个节点的指针(before和after属性),从而形成一个双向链表。
哈希表与双向链表的结合:
在LinkedHashSet中,元素的存储和查找主要通过哈希表来实现,以提供快速的插入、删除和查找操作。
同时,双向链表则用于维护元素的插入顺序。当元素被添加到LinkedHashSet时,它会被添加到双向链表的尾部(或头部,取决于LinkedHashMap的访问顺序策略)。
迭代顺序:
由于LinkedHashSet维护了元素的插入顺序,因此其迭代器(iterator)会按照元素的插入顺序进行遍历。这是通过遍历底层双向链表来实现的。
性能特点:
LinkedHashSet的插入、删除和查找操作的平均时间复杂度都是O(1),这与普通的HashSet相同。但是,由于LinkedHashSet需要维护双向链表,因此在内存使用上可能会稍高于HashSet。
另一方面,由于LinkedHashSet提供了有序的迭代顺序,因此在某些需要保持元素顺序的场景下,使用LinkedHashSet可能会更加方便。
继承关系:
LinkedHashSet继承自HashSet,并实现了Set接口。它的底层实现依赖于LinkedHashMap,因此LinkedHashSet在方法操作上与HashSet相同,但在迭代顺序上有所不同。
总的来说,LinkedHashSet通过结合哈希表和双向链表的结构,实现了具有可预知迭代顺序的集合。
TreeSet
Java中的TreeSet底层原理主要基于红黑树(Red-Black Tree)数据结构来实现。以下是TreeSet的底层原理的简要总结:
数据结构:
TreeSet使用红黑树作为其内部的数据结构。红黑树是一种自平衡的二叉搜索树,它能够在添加、删除和查找元素时保持较好的性能。
元素排序:
TreeSet中的元素按照其自然顺序(如果元素实现了Comparable接口)或根据提供的Comparator对象进行排序。默认情况下,元素按照升序排序。
对于数值类型(如Integer、Double),TreeSet默认按照数值本身的大小进行升序排序。
对于字符串类型,TreeSet默认按照首字符的Unicode编码进行升序排序。
对于自定义类型(如自定义对象),如果对象没有实现Comparable接口或没有提供Comparator对象,则无法直接添加到TreeSet中。
唯一性保证:
TreeSet通过红黑树的特性保证了元素的唯一性。在添加元素时,TreeSet会遍历红黑树,找到元素应该插入的位置。如果在这个位置上已经存在相同的元素(根据排序规则判断),则不会添加新的元素。
性能特点:
TreeSet的插入、删除和查找操作的平均时间复杂度都是O(log n),其中n是集合中元素的数量。这是因为红黑树在插入、删除和查找元素时,能够保持树的平衡,使得树的高度相对较低,从而提高了操作的效率。
与HashSet相比,TreeSet在插入和删除元素时可能需要更多的计算量来维护红黑树的平衡,但在需要保持元素顺序的场景下,TreeSet提供了更好的性能。
遍历方式:
TreeSet提供了多种遍历方式,包括升序遍历(自然顺序或根据提供的Comparator对象)和降序遍历(使用descendingSet()方法获取降序视图)。
继承关系:
TreeSet实现了NavigableSet接口,该接口扩展了SortedSet接口,提供了更多的导航和排序方法。因此,TreeSet不仅具有Set接口的基本功能,还提供了更丰富的排序和遍历功能。
总的来说,TreeSet通过红黑树数据结构实现了元素的排序和唯一性保证,同时提供了高效的插入、删除和查找操作以及丰富的遍历方式。