Java HashSet和TreeSet【笔记】
PS:HashSet、TreeSet 两个类是在 Map 的基础上组装起来的类
HashSet
类注释
1.底层实现基于 HashMap,所以迭代时不能保证按照插入顺序,或者其它顺序进行迭代
2.add、remove、contanins、size 等方法的耗时性能,是不会随着数据量的增加而增加的,这个主要跟 HashMap 底层的数组数据结构有关,不管数据量多大,不考虑 hash 冲突的情况下,时间复杂度都是 O (1)
3.线程不安全的,如果需要安全请自行加锁,或者使用 Collections.synchronizedSet
4.迭代过程中,如果数据结构被改变,会快速失败的,会抛出 ConcurrentModificationException 异常
HashSet结构
HashSet使用的就是组合 HashMap,组合就是把 HashMap 当作自己的一个局部变量
在 Java 中,要想基于基础类进行创新实现的话,有两种办法:
第一种是继承基础类,覆写基础类的方法,比如说继承 HashMap , 覆写其 add 的方法
第二种是组合基础类,通过调用基础类的方法,来复用基础类的能力
其优点如下:
1.继承表示父子类是同一个事物,而 Set 和 Map 本来就是想表达两种事物,所以继承不妥,而且 Java 语法限制,子类只能继承一个父类,后续难以扩展
2.组合更加灵活,可以任意的组合现有的基础类,并且可以在基础类方法的基础上进行扩展、编排等,而且方法命名可以任意命名,无需和基础类的方法名称保持一致
HashSet 初始化
HashSet 的初始化直接 new HashMap 即可,有意思的是,在有原始数据进行初始化的情况下,会对 HashMap 的初始容量进行计算(取括号中两个数的最大值,Math.max((int) (c.size()/.75f) + 1, 16))
源码:
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
可以看出两方面的事情
第一,和 16 比较大小的意思是说,如果给定 HashMap 初始容量小于 16 ,就按照 HashMap 默认的 16 初始化好了,如果大于 16,就按照给定值初始化
第二,HashMap 扩容的伐值的计算公式是:Map 的容量 * 0.75f,一旦达到阀值就会扩容,此处用 (int) (c.size ()/.75f) + 1 来表示初始化的值,这样使我们期望的大小值正好比扩容的阀值还大 1,就不会扩容,符合 HashMap 扩容的公式
HashSet 其他方法
其他方法就是对 Map 的 api 进行了一些包装,如下的 add 方法实现,直接使用 HashMap 的 put 方法,进行一些简单的逻辑判断
代码:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
从 add 方法中,我们就可以看到组合的好处,方法的入参、名称、返回值都可以自定义,如果是继承的话就不能自定义了
HashSet值得参考的地方
1.对组合和继承的分析和把握
2.对复杂逻辑的包装,要让放出去的接口尽量简单好用
3.尽量多对组合的 api 多些了解,这样在组合其他 api 时,才能更好的使用 api
TreeSet
TreeSet基本结构
TreeSet 的结构和 HashSet 相似,底层组合的是 TreeMap,所以其继承了 TreeMap key 能够排序的功能,在迭代的时候,也可以按照 key 的排序顺序进行迭代
TreeSet 组合 TreeMap 实现的思路
TreeSet 组合 TreeMap 实现的思路有两种
第一种,TreeSet 直接使用 TreeMap 的某些功能,自己包装成新的 api(add方法),适合用于简单的场景
add方法源码:
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
第二种,TreeSet 定义自己想要的 api,自己定义接口规范,让 TreeMap 去实现(NavigableSet 接口),也就是说,TreeSet 把接口定义出来后,让 TreeMap 去实现内部逻辑,TreeSet 负责接口定义,TreeMap 负责具体实现,适合用于复杂的场景,这种思想比较重要,很多都是这种复用思想
NavigableSet 接口源码:
public interface NavigableSet<E> extends SortedSet<E> {
Iterator<E> iterator();
E lower(E e);
}
public Iterator<E> iterator() {
return m.navigableKeySet().iterator();
}
比较重要的就是HashSet 小结以及TreeSet 两种复用思路