JAVA集合框架之Set集合

集合框架

包括一系列的接口和类 存放集合数据

接口继承关系图

Collection接口 父接口,提供ListSet等共同的一些方法,例如:addremovecleariteratorsize

Set接口 包含一组不重复的数据,Set中数据是无序的,没有下标

List接口 包含一组有序的数组,List中的数据都是有下标的,可以包含重复的数据

Queue接口 ”先进先出“

Deque接口 ”双端队列“

Map接口 “键值对” K-V key就是键 value就是值(数据)

实现

Set接口:

(1)HashSet

(2)TreeSet(与HashSet几乎相同,唯一不同是它会对存放的数据进行排序,默认只能存放能排序的数据)

(3)LinkedHashSet 链表 查询速度非常快,但添加和删除慢(不常用)

HashSet:
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.fyypll.set;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class TestHashSet {

public static void main(String[] args) {

// 1. 如何实例化(3种)
// ①
HashSet hs1 = new HashSet(); // 没有泛型,hs1里面就可以存放任意类型的数据,不推荐这种方法
hs1.add("abcd");
hs1.add(123465);

// ②
HashSet<String> hs2 = new HashSet<>();

// ③
// HashSet 实现了Set接口,可以向上转型
Set<String> hs3 = new HashSet<>();
hs3.add("诸葛亮");
hs3.add("刘备");
hs3.add("关羽");
hs3.add("张飞");
hs3.add("刘备");
hs3.add(null); // HashSet中元素可以为null,但也只能有一个空值,重复的会被覆盖
hs3.add(null);

System.out.println(hs3.size()); // 集合中元素的个数
// 打印集合中的全部数据,因为HashSet重写了toString方法
// 所以这里我们可以直接用toString即可打印出HashSet中的元素
System.out.println(hs3.toString());

System.out.println("----------遍历HashSet----------");

// 2.如果要取出hs3中的数据该如何做(2种)
// ①
for(int i=0;i<hs3.size();i++) {
// 这个方法循环遍历是不行的,因为set集合中的数据没有下标,那就没法通过下标取出数据喽
}

// ② 使用增强for,增强for取数据不需要下标哦
for(String s : hs3) {
System.out.println(s);
}

// ③
System.out.println("----------Iterator 迭代器---------");

// 调用HashSet类中的iterator方法获取迭代器
// 迭代器泛型类型跟HashSet(hs3)的泛型类型一致
Iterator<String> iterator = hs3.iterator();

// 使用迭代器
while(iterator.hasNext()) { // hasNext判断迭代器中是否还有数据,如果有,返回true

// next方法从迭代器中取出一个数据(取出的是哪一个我们不知道,因为set数据没有顺序)
// 每取出一个数据,就将该数据从迭代器中删除(所以数据只能拿出来一次,因为一拿出来从迭代器中就删除了)
// 因为迭代器是String类型,所以这里用String类型的s来接收数据
String s = iterator.next();
System.out.println(s);
}

System.out.println("----------删除数据-----------");
// 删除数据
// 因为set中的数据没有下标,所以要删除哪个数据就需要传哪个数据
hs3.remove("刘备");
System.out.println("删除之后:"+hs3.toString());

System.out.println("----------批量操作-----------");
// 批量操作(添加、删除)
// 先创建两个集合,以便后面使用
Set<String> hs4 = new HashSet<>();
hs4.add("三国");
hs4.add("演义");

Set<String> hs5 = new HashSet<>();
hs5.add("关羽");
hs5.add("张飞");

// 批量添加:使用addAll方法添加整个集合hs4到hs3中(即合并两个集合)
hs3.addAll(hs4);
System.out.println("批量添加之后:"+hs3.toString());

// 批量删除:使用removeAll方法将会删除所有hs3中与hs5中相同的数据
// 即hs3中的所有数据,只要是和hs5中的数据相同的,都会被删除
hs3.removeAll(hs5);
System.out.println("批量删除之后:"+hs3.toString());

// 把hs3集合转换为数组
String[] a = hs3.toArray(new String[hs3.size()]);
}

}
HashSet代码运行结果为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
5
[关羽, null, 张飞, 刘备, 诸葛亮]
----------遍历HashSet----------
关羽
null
张飞
刘备
诸葛亮
----------Iterator 迭代器---------
关羽
null
张飞
刘备
诸葛亮
----------删除数据-----------
删除之后:[关羽, null, 张飞, 诸葛亮]
----------批量操作-----------
批量添加之后:[关羽, null, 张飞, 诸葛亮, 演义, 三国]
批量删除之后:[null, 诸葛亮, 演义, 三国]
HashSet代码解析:

代码31行运行结果为5set中的数据是不重复的,所以虽然传入的是7个值,但是输出的元素统计个数为5(输入的值中刘备null重复了)。代码32行运行结果为[关羽, null, 张飞, 刘备, 诸葛亮],输出顺序和我们输入的顺序并不一样,可见set中的数据是无序的。

注释中说过因为HashSettoString方法重写了,所以我们能直接使用toString方法将HashSet中的数据直接打印出来。通过查看JDK源代码可以找到重写代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";

StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}

这里可以很清楚的看出来,如果数据集里面没有数据,就会直接输出[],如果有数据那么就会先输出一个[然后后面用append方法将逐个输出的数据拼接起来,直到数据输完然后拼接上]

HashSet总结:

HashSet中:

add 添加元素

remove 删除元素

size() 元素个数

遍历HashSet使用增强for或迭代器iterator

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.fyypll.set;

import java.util.Set;
import java.util.TreeSet;

public class TestTreeSet {

public static void main(String[] args) {

// 实例化方式和HashSet一样,有3种
// ①
TreeSet ts1 = new TreeSet();
// ②
TreeSet<Integer> ts2 = new TreeSet<>();

// ③ 因为TreeSet实现了Set接口,所以可以向上转型
Set<Integer> ts3 = new TreeSet<>();

ts3.add(888);
ts3.add(666);
ts3.add(777);
// toString方法是Object类中的方法
// 因为TreeSet重写了toString方法,所以用toString就能打印出结果
// Integer类型输出结果是从小到大
System.out.println(ts3.toString()); // [666, 777, 888]

Set<String> ts4 = new TreeSet<>();
ts4.add("bbb");
ts4.add("ab");
ts4.add("ccc");
ts4.add("Ddd");
// String类型中字母是按字母顺序排的,字符串长度不一就按首字母排,首字母一样就按第二个字母排
// 大写字母优先排
System.out.println(ts4.toString()); // [Ddd, ab, bbb, ccc]

Set<String> ts5 = new TreeSet<>();
ts5.add("可"); // \u53ef
ts5.add("能"); // \u80fd
ts5.add("是"); // \u662f
// String类型中汉字是按照 Unicode 编码来排序的,首字母都是u,判断第二位,第二位都是数字,
// 按小到大排就是 “\u53ef \u662f \u80fd”,即 “可 是 能”,如果第二位有数字和字母那怎么排?
// 那就按16进制来比大小,即将字母看成数字,然后比大小,即a=10,b=11,c=12,d=13,e=14,f=15
System.out.println(ts5.toString()); // [可, 是, 能]

}

}
TreeSet代码运行结果为:
1
2
3
[666, 777, 888]
[Ddd, ab, bbb, ccc]
[可, 是, 能]
TreeSet总结:

HashSet几乎相同,唯一不同是它会对存放的数据进行排序,默认只能存放能排序的数据。