版权声明:转载原创博客请附上原文链接 https://blog.csdn.net/weixin_43495590/article/details/89313825
一:继承结构
- Set系列三个具体实现子类分别是TreeSet、HashSet、LinkedHashSet
- Set与List一样同为Collection系列
二:重点结构描述
- Set接口继承Collection接口,并没有像List那样提供特殊方法
- AbstractSet与AbstractList抽象类相似,继承AbstractColletion实现Set接口
- SortedSet接口继承Set接口,提供额外数据截取操作
- NavigableSet接口则是针对SortedSet接口方法补充重载边界包含,headSet()默认false不包含最后一个元素,tailSet()默认true包含第一个节点
三:TreeSet
- 相对于List而言Set最大的特点就是
去重
,接下来第一点就是查看Set怎么在添加元素的时候实现的去重 - 讨论TreeSet是否可以储存
null值
- TreeSet如何实现的有序储存即按照
某种规则顺序
储存数据
3.1 去重实现
- 查看TreeSet属性NavigableMap<E,Object> m,该属性赋值在构造函数初始化时进行,默认使用TreeMap,也就是TreeSet底层采用
TreeMap结构储存数据
- add()方法作为入口查看元素作为TreeMap的key值保存,value值为Object实例。调用TreeMap类put()
- 通过TreeMap的put()分析,
当key值相同时仅仅修改固定值value,即不会创建新的Entry节点
// 储存数据结构
private transient NavigableMap<E,Object> m;
// 常量值value
private static final Object PRESENT = new Object();
public TreeSet() {
this(new TreeMap<E,Object>());
}
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
// TreeMap类put方法片段
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
3.2 Null值处理
- 针对Null值处理还是需要将TreeMap的put方法作为入口跟进
- TreeSet实例化
未传入Comparator比较器对象则空指针异常
// TreeMap的put方法如果第一次储存元素调用compare方法
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
// 截取片段put方法,else表示comparator属性为空即初始化未传入Comparator
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
3.3 顺序规则
通过上面展示的代码段可以看出TreeSet储存数据时都会根据储存元素实现的Compaable比较器comparaTo()方法,亦或是实例化传入Comparator比较器的compara()方法制定顺序规则。也就说TreeSet储存的元素要么自身实现Comparable接口,要么就需要传入Comparator比较器
四:HashSet
- 相较于TreeSet采用HashMap作为底层实现储存数据
- HashSet对于Null值处理又是什么规则
4.1 数据储存
- HashSet相对于TreeSet来讲少了null值限制,也就是
可以储存null
- HashSet使用HashMap的kley值储存数据,HashMap中putVal()可以看到当key相同
hash值必定相同的情况下是直接覆盖处理
,也就是不会有重复值
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab;
Node<K,V> p;
int n, i;
// HashMap储存数据采用Node数组,该数组未实例化则调用resize()实例化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; // n记录数组容量
// 根据哈希算法确定元素在数组中下标位置储存
// 前提是该位置没有元素
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 判断hash值与key则直接覆盖(这里就使得HashSet达到了去重目的)
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
}
......
}
五:LinkedHashSet
继承HashSet,相对于HashSet区别在于底层采用LinkedHashMap实现。也就是可以归结为HashMap与LinkedHashMap的区别,LinkedHashMap使用Entry内部类继承HashMap内部类Node,从而在里面提供了双向链表维护数据插入顺序