Java集合框架图析(Collection-Set)
一、前言
前面在Java集合框架图析(Collection-List)中主要总结了List相关的ArrayList 和LinkedList ,这章总结Set 知识点。
Set 集合也实现了 Collection 接口,它主要有两个实现类:HashSet 类和 TreeSet 类。Set 集合中的对象不按特定的方式排序,只是简单地把对象加入集合,集合中不能包含重复的对象,并且最多只允许包含一个 null 元素。
1.Set框架图:
HashSet 和TreeSet 是Set的两个典型实现,HashSet 的性能总是比TreeSet 要好,因为TreeSet 需要额外的红黑树算法来维护集合元素的次序。只有当需要一个保持次序的Set时,才应该使用TreeSet,否则都应该使用HashSet。HashSet的子类LinkedHashSet 由于需要维护链表,普通操作比如插入、删除会比HashSet稍微慢一些,但是遍历LinkedHashSet会更快。 HashSet有一个LinkedHashSet子类,用hashCode 决定存储位置,同时采用链表维护元素次序。当遍历LinkedHashSet时,元素的顺序跟存储时的顺序一致。
二、HashSet 类
实现Set 接口,由哈希表(实际上是HashMap实例)支持。 它不保证集合的迭代顺序; 特别是,它不保证订单会随着时间的推移保持不变。 此类允许null元素。 HashSet 利用 HashMap 中的key 元素来存放元素,源码可以看出:
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable{
private transient HashMap<E,Object> map;
private transient HashMap<E,Object> map;
public HashSet() {
map = new HashMap<>();
}
}
2.1 构造方法
构造器 | 描述 |
---|
HashSet() | 构造一个新的空集; 支持HashMap实例具有默认初始容量(16)和加载因子(0.75) | HashSet?(int initialCapacity) | 构造一个新的空集; 支持HashMap实例具有指定的初始容量和默认加载因子(0.75) | HashSet?(int initialCapacity, float loadFactor) | 构造一个新的空集; 支持HashMap实例具有指定的初始容量和指定的加载因子 | HashSet?(Collection<? extends E> c) | 构造一个包含指定集合中元素的新集合 |
2.2 常用方法
变量和类型 | 方法 | 描述 |
---|
boolean | add?(E e) | 如果指定的元素尚不存在,则将其添加到此集合中 | void | clear() | 从该集中删除所有元素 | boolean | contains?(Object o) | 如果此set包含指定的元素,则返回 true | boolean | isEmpty() | 如果此集合不包含任何元素,则返回 true | Iterator | iterator() | 返回此set中元素的迭代器 | boolean | remove?(Object o) | 如果存在,则从该集合中移除指定的元素 | int | size() | 返回此集合中的元素数(基数) |
2.3 add()方法
源码中:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();
可以看出在HashSet 中,进行add() 添加元素等等价于
HashMap map = new HashMap<>();
map.put(e, new Object());
HashMap 在添加元素的时候 ,通过equals() 和hashCode() 方法来判断传入的key 是否相同,如果相同,那么 HashMap 认为添加的是同一个元素,反之,则不是。
从源码上可以分析出,HashSet 正是使用了 HashMap 的这一特性,实现存储元素下标无序、元素不会重复的特点。
2.4 remove()方法
HashSet 的删除方法,同样如此,也是基于 HashMap 的底层实现 源码:
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
2.5 查询方法
HashSet 没有像 List 、Map 那样提供 get 方法,但是可以通过使用迭代器或者 for 循环来遍历元素
public class demo01 {
public static void main(String[] args) {
HashSet<String> bookSet = new HashSet<String>();
String book1 = new String("Java");
String book2 = new String("Python");
String book3 = new String("C++");
String book4 = new String("PHP");
bookSet.add(book1);
bookSet.add(book2);
bookSet.add(book3);
bookSet.add(book3);
bookSet.add(book4);
bookSet.add(book4);
bookSet.add(null);
System.out.println("新进图书有:");
for (String s : bookSet) {
System.out.println(s);
}
Iterator<String> it = bookSet.iterator();
while (it.hasNext()) {
System.out.println("《" + (String) it.next() + "》");
}
System.out.println("共采购 " + bookSet.size() + " 本图书!");
}
}
输出结果:
新进图书有:
《null》
《Java》
《C++》
《PHP》
《Python》
共采购 5 本图书!
Process finished with exit code 0
需要注意的是HashSet 是可以添加null 元素的。
三、LinkedHashSet类
3.1 LinkedHashSet简介
LinkedHashSet 是Set 集合的一个实现,继承自HashSet ,但是底层基于 LinkedHashMap 来实现具有set 集合不重复的特点,同时具有可预测的迭代顺序,也就是我们插入的顺序。
3.2 构造方法
构造器 | 描述 |
---|
LinkedHashSet() | 使用默认初始容量(16)和加载因子(0.75)构造一个新的空链接哈希集 | LinkedHashSet?(int initialCapacity) | 使用指定的初始容量和默认加载因子(0.75)构造一个新的空链接哈希集。 | LinkedHashSet?(int initialCapacity, float loadFactor) | 使用指定的初始容量和加载因子构造一个新的空链接哈希集。 | LinkedHashSet?(Collection<? extends E> c) | 构造一个新的链接哈希集,其具有与指定集合相同的元素。 |
结合源码看一下:
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
public LinkedHashSet() {
super(16, .75f, true);
}
}
从源码看super() 调用的方法:
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
3.3 add()方法
LinkedHashSet 没有重写add 方法,而是直接调用HashSet 的add() 方法,又因为map 的实现类是LinkedHashMap ,所以此处是向LinkedHashMap 中添加元素,当进行add() 的时候,等价于
HashMap map = new LinkedHashMap<>();
map.put(e, new Object());
3.4 remove()方法
LinkedHashSet 中同样没有重写remove()方法,因此直接调用HashSet 的删除方法,因为LinkedHashMap 没有重写remove 方法,所以调用的也是HashMap 的remove 方法,也可以选择实现Set 类中的remove() 方法,因为LinkedHashSet 继承于HashSet ,实现Set<E> 接口,源码如下:
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
3.5 查询方法
同样的,LinkedHashSet 没有提供 get 方法,使用迭代器或者 for 循环来遍历元素,实例如下:
public class demo01 {
public static void main(String[] args) {
Set<String> linkedHashSet = new LinkedHashSet<String>();
System.out.println("linkedHashSet初始容量大小:"+linkedHashSet.size());
linkedHashSet.add("111111");
linkedHashSet.add("222");
linkedHashSet.add("3333");
linkedHashSet.add("3333");
linkedHashSet.add("222");
linkedHashSet.add(null);
linkedHashSet.add(null);
System.out.println("linkedHashSet容量大小:"+linkedHashSet.size());
Iterator<String> iterator = linkedHashSet.iterator();
while (iterator.hasNext()){
String str = iterator.next();
System.out.print(str + ",");
}
System.out.println("\n===========");
for (String str : linkedHashSet) {
System.out.print(str + ",");
}
}
}
测试结果:
linkedHashSet初始容量大小:0
linkedHashSet容量大小:4
111111,222,3333,null,
===========
111111,222,3333,null,
Process finished with exit code 0
可以看出同HashSet 相比,LinkedHashSet 是输入输出有序的。
四、TreeSet
4.1 TreeSet简介
TreeSet 是一个排序的集合,实现了NavigableSet 、SortedSet 、Set 接口,底层基于 TreeMap 来实现。源码如下:
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable {
private transient NavigableMap<E,Object> m;
public TreeSet() {
this(new TreeMap<E,Object>());
}
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
}
4.2 构造方法
构造器 | 描述 |
---|
TreeSet() | 构造一个新的空树集,根据其元素的自然顺序进行排序 | TreeSet?(Collection<? extends E> c) | 构造一个新的树集,其中包含指定集合中的元素,并根据其元素的 自然顺序进行排序 | TreeSet?(Comparator<? super E> comparator) | 构造一个新的空树集,根据指定的比较器进行排序 | TreeSet?(SortedSet s) | 构造一个包含相同元素并使用与指定有序集相同排序的新树集 |
4.3 常用方法
变量和类型 | 方法 | 描述 |
---|
boolean | add?(E e) | 如果指定的元素尚不存在,则将其添加到此集合中 | boolean | addAll?(Collection<? extends E> c) | 将指定集合中的所有元素添加到此集合中 | void | clear() | 从该集中删除所有元素 | boolean | contains?(Object o) | 如果此set包含指定的元素,则返回 true | Iterator | iterator() | 以升序返回此集合中元素的迭代器。 | boolean | remove?(Object o) | 如果存在,则从该集合中移除指定的元素 | int | size() | 返回此集合中的元素数(基数) |
4.4 add()方法
源码中的add() 方法如下:
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
其中变量PRESENT ,是一个Object 对象,源码如下:
private static final Object PRESENT = new Object();
其实就等同于用了底层的TreeMap ,向TreeMap 中add 了一个元素;
TreeMa map = new TreeMap<>();
map.put(e, new Object());
TreeSet 的自动排序,其实是根据TreeMap 会自动将Key 按照一定规则排序,因此TreeSet 在add 添加元素到集合,输出的时候结果已经排序了。
TreeSet是不能添加空元素的,如果添加null的话将会报错:
4.5 remove()方法
同样是基于TreeMap 的底层实现的:
public boolean remove(Object o) {
return m.remove(o)==PRESENT;
}
4.6 查询方法
同样没有重写get 方法,需要迭代器或者for循环遍历元素 实例测试:
public class demo01 {
public static void main(String[] args) {
TreeSet<String> treeSet = new TreeSet<>();
System.out.println("treeSet初始容量大小:"+treeSet.size());
treeSet.add("11");
treeSet.add("22");
treeSet.add("33");
treeSet.add("44");
treeSet.add("55");
System.out.println("treeSet容量大小:"+treeSet.size());
Iterator<String> iterator = treeSet.iterator();
while (iterator.hasNext()){
String str = iterator.next();
System.out.print(str + ",");
}
System.out.println("\n===========");
for (String str : treeSet) {
System.out.print(str + ",");
}
}
}
测试结果:
treeSet初始容量大小:0
treeSet容量大小:5
11,22,33,44,55,
===========
11,22,33,44,55,
Process finished with exit code 0
五、总结
功能 | HashSet | LinkedHashSet | TreeSet |
---|
元素能否为空 | 能 | 能 | 不能 | 读写有序无序 | 输入输出无序 | 输入输出有序 | 经过排序 | 底层原理 | 基于HashMap | 基于LinkedHashMap | 基于TreeMap |
HashSet 为快速查找设计的Set 。存入HashSet 的对象必须定义hashCode() ,对象需要重写 equals() 和 hashCode() 方法来约束是否为相同的元素。LinkedHashSe t能实现输入输出有序,元素可以为空,底层原理是LinkedHashMap 保存次序的Set , 底层为树结构。使用它可以从Set 中提取有序的序列。TreeSet 具有HashSet 的查询速度,且内部使用链表维护元素的顺序(插入的次序)底层基于 TreeMap 的 key来实现,元素不可以为空,默认按照自然排序来存放元素,也可以使用 Comparable 和 Comparator 接口来比较大小,实现自定义排序。
|