目录
Set 集合是一个不能包含重复元素的集合。Set接口只包含从Collection继承的方法,并添加了禁止重复元素的限制。Set还为equals和hashCode操作的行为添加了更强的约定,允许对Set实例进行有意义的比较,即使它们的实现类型不同。如果两个Set实例包含相同的元素,则它们是相等的。
Java平台包含三种通用的集合实现:HashSet、TreeSet 和 LinkedHashSet。
- HashSet将其元素存储在哈希表中,是性能最好的实现;但是它不能保证迭代的顺序。
- TreeSet将元素存储在红黑树中,它根据元素的值对元素进行排序;它比HashSet慢得多。
- LinkedHashSet 的底层实现为哈希表+链表,它根据元素插入到集合的顺序对元素进行排序。LinkedHashSet 实现了元素排序,但代价比 HashSet 略高一些。
如果想要通过一个集合创建另一个包含相同元素的集合,但要消除所有重复项。可通过以下代码实现:
// c 表示原有集合
Collection<Type> noDups = new HashSet<Type>(c);
如果使用的是JDK8或更高版本,可以使用流操作:
c.stream()
.collect(Collectors.toSet()); // no duplicates
如果使用的是 TreeSet,则通过以下方式进行设置:
Set<String> set = people.stream()
.map(Person::getName)
.collect(Collectors.toCollection(TreeSet::new));
下边程序使用了 LinkedHashSet,它保留了原始集合的顺序,同时删除了重复的元素:
Collection<Type> noDups = new LinkedHashSet<Type>(c);
下面是一个泛型方法,它封装了前面的用法,返回一个与传递的泛型类型相同的Set。
public static <E> Set<E> removeDups(Collection<E> c) {
return new LinkedHashSet<E>(c);
}
1、基础操作 Basic Operations
- size 操作返回集合中元素的个数。
- isEmpty 判断指定的元素不存在,返回 true。
- add 方法将元素添加到Set中,并返回一个布尔值,指示是否添加了该元素。
- remove方法从Set中删除指定的元素,并返回一个布尔值。
- iterator 方法返回一个遍历集合的迭代器。
下面的程序打印出参数列表中所有不同的单词。
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
public class FindDups {
public static void main(String[] args) {
args = "i came i saw i left".split(" ");
Set<String> distinctWords = Arrays.asList(args).stream()
.collect(Collectors.toSet());
System.out.println(distinctWords.size() + " distinct words: " + distinctWords);
}
}
执行结果:
4 distinct words: [left, came, saw, i]
注意,代码总是通过接口类型(Set)引用集合,而不是通过其实现类型。这是一种强烈推荐的编程实践,因为它提供了仅通过更改构造函数来更改实现的灵活性。
在上面的例子中,Set的实现类型是HashSet,它不保证Set中元素的顺序。如果希望程序按字母顺序打印单词列表,只需将Set的实现类型从HashSet更改为TreeSet。更改后执行结果如下:
4 distinct words: [came, i, left, saw]
2、批量操作 Bulk Operations
Set 同样适合于批量操作,假设 s1 和 s2 是 Set 集合。以下是批量操作的作用:
s1.containsAll(s2)
— 如果s2是s1的子集,返回trues1.addAll(s2)
— 将s1变换为s1和s2的并集s1.retainAll(s2)
— 把s1变换成s1和s2的交集s1.removeAll(s2)
— 将s1转换为s1和s2的差集
如果要非破坏性地(不修改集合)计算两个集合的并、交或集差,调用方必须在调用批量操作之前复制一个集合。下边是习惯用法:
Set<Type> union = new HashSet<Type>(s1); // copy set
union.addAll(s2); // 并集
Set<Type> intersection = new HashSet<Type>(s1);
intersection.retainAll(s2); // 交集
Set<Type> difference = new HashSet<Type>(s1);
difference.removeAll(s2); // 差集
回到FindDups程序,需要统计参数列表中只出现过一次的单词和出现过多次的单词,同时不重复打印任何单词。
这个效果可以通过生成两个Set 集合来实现,一个包含参数列表中的每个单词,另一个只包含重复的单词。只出现一次的单词是这两个集合的集合差,我们知道如何计算。下面是结果程序的实现:
import java.util.HashSet;
import java.util.Set;
public class FindDups {
public static void main(String[] args) {
args = "i came i saw i left".split(" ");
Set<String> uniques = new HashSet<String>();
Set<String> dups = new HashSet<String>();
for (String a : args) {
if (!uniques.add(a)) {
dups.add(a);
}
}
// Destructive set-difference
uniques.removeAll(dups);
System.out.println("Unique words: " + uniques);
System.out.println("Duplicate words: " + dups);
}
}
程序执行结果:
Unique words: [left, came, saw]
Duplicate words: [i]
计算对称集合差
什么是对称集合差?即元素包含在两个指定集合中的任意一个,但不同时包含在两个集合中的元素集合。下面的代码非破坏性地计算两个集合的对称集差。
Set<Type> symmetricDiff = new HashSet<Type>(s1);
symmetricDiff.addAll(s2); // 1-先求并集
Set<Type> tmp = new HashSet<Type>(s1);
tmp.retainAll(s2); // 2-然后求交集
symmetricDiff.removeAll(tmp); // 3-最后求差集