在Java的Set体系中,根据实现方式不同主要分为两大类。HashSet和TreeSet。
在HashSet中,基本的操作都是有HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashCode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置为空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。
TreeSet的底层是TreeMap的keySet(),而TreeMap是基于红黑树实现的,红黑树是一种平衡二叉查找树,它能保证任何一个节点的左右子树的高度差不会超过较矮的那棵的一倍。
TreeMap是按key排序的,元素在插入TreeSet时compareTo()方法要被调用,所以TreeSet中的元素要实现Comparable接口。TreeSet作为一种Set,它不允许出现重复元素。TreeSet是用compareTo()来判断重复元素的。
顾名思义,BitSet是位集合,通常来说,位集合的底层的数据结构是一个bit数组,如果第n位为1,则表明数字n在该数组中。
举个例子,如果调用BitSet#set(10),业务语意是把10放到BitSet中,内部的操作则是通过把二进制的第十位(低位)置为1。这样,就代表BitSet中包含了10这个数字。
不过,对于Java中的BitSet来讲,因为Java不知道bit类型,所以它的底层结构并不是一个bit类型数组,但是也不是一个byte类型数组,而是一个long类型的数组,这样设置的目的是因为long有64位,每次可以读取64位,在进行set或者or操作的时候,for循环的次数会更少,提高了性能。
它最大的好处就是对于多个数字来说,降低了存储空间,如正常情况下,将每一个int类型(32bit)的数字存储到内存中需要 4B * (2^31-1) = 8 GB,但是如果用BitSet的话,就会节省到原来的1/32。
一个整型占4个字节,一共有2^31-1个。
BitSet常见的使用例子往往和大数相关:
但是BitSet也有缺点,譬如集合中存储一些差值比较大的数,如1亿和1两个数,就会导致内存的严重浪费