HashSet详解

HashSet详解

       List和Set都实现自Collection,List保证元素的添加顺序,元素可重复。而Set不保证元素的添加顺序,元素不可重复。

1
2
Set不保证插入有序是指Set这个接口的规范,实现类只要遵循这个规范即可,当然也可以写有序的版本出来,比如LinkedHashSet。
而TreeSet是里面的内容有序(按照一定规则排序),但不是指元素的添加顺序。

Set在Collection中的位置

       Set接口继承自Collection。有两个很重要的实现HashSet和TreeSet。

       执行如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void main(String[] args){
Set<String> strSet = new HashSet<>();//new了一个HashSet
strSet.add("张三");
strSet.add("李四");
strSet.add("王五");
strSet.add("赵六");

System.out.println("strSet : " + strSet);
System.out.println("strSet.size() : " + strSet.size());
System.out.println("strSet里是否为空 : " + strSet.isEmpty());

System.out.println("删除王五。。。。");
boolean delFlag = strSet.remove("王五");
System.out.println("删除王五是否成功" + delFlag);
System.out.println("删除王五后的strSet : " + strSet);
System.out.println("strSet中是否包含王五:" + strSet.contains("王五"));
System.out.println("strSet中是否包含张三:" + strSet.contains("张三"));

System.out.println("clear清除元素...");
strSet.clear();
System.out.println("clear清除元素后的strSet : " + strSet);
System.out.println("strSet长度 : " + strSet.size());
System.out.println("strSet里是否为空 : " + strSet.isEmpty());

}

       第一行代码new了一个HashSet,在堆内存里开辟了一块空间,先找到HashSet的构造函数看看:

1
2
3
public HashSet() {
map = new HashMap<>();
}

       再看一下map:

1
private transient HashMap<E, Object> map;

       就是一个HashMap,如下图:

HashSet初始化

       继续执行代码,往strSet添加元素”张三”,再看add方法:

1
2
3
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}

       原来就是调用底层HashMap的put方法,把”张三”作为key,PRESENT作为value放在hashMap里,如果put时key重了,会返回被覆盖的value值(oldValue),否则返回null,这儿的HashSet又给包装了一下,如果key没有重(oldValue == null),就返回true,否则返回false。继续看PRESENT:

1
private static final Object PRESENT = new Object();

       很简单就是new了一个Object,如下图:

HashSet添加元素1

       调用底层HashMap的时候,key是传进去的“张三”,value是PRESENT,也就是一个Object对象,继续往里依次放入“李四”、“王五”、“赵六”,value都是一样的,为PRESENT,如下图:

HashSet添加元素2

       所有元素的value都指向Object对象,HashSet虽然底层是用HashMap来实现的,但由于用不到HashMap的value,所以不会为底层HashMap的每个value分配一个内存空间,因此并不会过多的占用内存,请放心使用。

       再来看看示例代码里的size()、isEmpty()、remove()、contains()、clear()等方法的实现,调用的是底层HashMap的相关方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int size() {
return map.size();
}

public boolean isEmpty() {
return map.isEmpty();
}

public boolean remove(Object o) {
return map.remove(o) == PRESENT;
}

public void clear() {
map.clear();
}

       这些方法基本上没什么逻辑代码,就是复用了HashMap里的方法而已。HashSet就是利用HashMap来实现的。同样,TreeSet也是用TreeMap来实现的。

       HashSet底层声明了一个HashMap,HashSet做了一层包装,操作HashSet里的元素时其实是在操作HashMap里的元素。TreeSet底层也是声明了一个TreeMap,操作TreeSet里的元素其实是操作TreeMap里的元素。

参考资料:
清浅池塘 HashSet的秘密

Fork me on GitHub