Set接口
Set接口继承自Collection接口,表示一组不重复的元素。Set接口的特点是不允许包含重复的元素,且没有定义元素的顺序。
HashSet<String> hashSet = new HashSet<>();
hashSet.add("java");
hashSet.add("python");
hashSet.add("php");
hashSet.add("c++");
System.out.println(hashSet);
输出:[python, c++, java, php]
Set接口有以下常用的实现类:
- HashSet:使用哈希表实现,不保证元素的顺序,允许使用null元素。
- LinkedHashSet:使用哈希表和链表实现,保证元素按照插入顺序排序。
- TreeSet:使用红黑树实现,保证元素按照自然顺序或指定的Comparator进行排序。
Set接口定义了一系列常用的方法,包括:
- add(E e):向集合中添加元素,如果元素已存在则不添加。
- remove(Object o):从集合中移除指定元素。
- contains(Object o):判断集合中是否包含指定元素。
- size():返回集合中元素的个数。
- isEmpty():判断集合是否为空。
- clear():清空集合中的所有元素。
- iterator():返回一个迭代器,用于遍历集合中的元素。
HashSet
HashSet的实现原理主要依赖于HashMap。
HashSet的实现原理:
- 内部数据结构:HashSet内部使用一个HashMap来存储元素,其中元素作为HashMap的键,值则为一个固定的Object对象。
- 哈希码和哈希表:当向HashSet中添加元素时,HashSet会先调用元素的hashCode()方法来获取元素的哈希码,然后根据哈希码计算元素在哈希表中的存储位置。
- 解决哈希冲突:由于不同的元素可能会有相同的哈希码,所以可能会发生哈希冲突。当发生哈希冲突时,HashSet会使用链表的方式来解决,即在冲突的位置上,将新元素添加到链表的末尾。
- 初始容量和负载因子:HashSet的初始容量默认为16,负载因子默认为0.75。当HashSet中的元素数量超过了负载因子与容量的乘积时,HashSet会自动进行扩容,即重新调整哈希表的大小。
- 扩容:扩容操作会创建一个新的哈希表,并将原哈希表中的元素重新分配到新的哈希表中。这个过程涉及到重新计算元素的哈希码和重新分配存储位置,以保证元素在新哈希表中的位置正确。
- 迭代顺序:由于HashSet使用哈希表存储元素,所以HashSet中的元素没有固定的顺序,不会按照元素的插入顺序或者排序规则进行存储和访问。
HashSet类的常用方法基本上都是通过调用HashMap的相应方法来实现的,比如add()方法会调用HashMap的put()方法,remove()方法会调用HashMap的remove()方法,contains()方法会调用HashMap的containsKey()方法等。
LinkedHashSet
LinkedHashSet的实现原理基本上与HashSet相同,都是使用HashMap来存储元素。不同之处在于LinkedHashSet使用LinkedHashMap来实现,LinkedHashMap是HashMap的子类,它在HashMap的基础上增加了一个双向链表来维护元素的插入顺序。
HashSet<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("java");
linkedHashSet.add("python");
linkedHashSet.add("php");
linkedHashSet.add("c++");
System.out.println(linkedHashSet);
输出:[java, python, php, c++] //按插入顺序输出
具体实现原理如下:
- 内部数据结构:LinkedHashSet内部使用一个LinkedHashMap来存储元素,其中元素作为LinkedHashMap的键,值则为一个固定的Object对象。
- 哈希码和哈希表:当向LinkedHashSet中添加元素时,LinkedHashSet会先调用元素的hashCode()方法来获取元素的哈希码,然后根据哈希码计算元素在哈希表中的存储位置。
- 解决哈希冲突:由于不同的元素可能会有相同的哈希码,所以可能会发生哈希冲突。当发生哈希冲突时,LinkedHashSet会使用链表的方式来解决,即在冲突的位置上,将新元素添加到链表的末尾。
- 双向链表:LinkedHashSet使用双向链表来维护元素的插入顺序。在LinkedHashMap中,每个节点除了包含键值对信息外,还包含了前驱节点和后继节点的引用,通过这些引用可以实现元素的有序访问。
LinkedHashSet类的常用方法基本上都是通过调用LinkedHashMap的相应方法来实现的,比如add()方法会调用LinkedHashMap的put()方法,remove()方法会调用LinkedHashMap的remove()方法,contains()方法会调用LinkedHashMap的containsKey()方法等。
TreeSet
TreeSet基于红黑树数据结构来存储元素,并且可以根据元素的自然顺序或者指定的Comparator来进行排序。
Set<String> linkedHashSet = new TreeSet<>();
linkedHashSet.add("java");
linkedHashSet.add("python");
linkedHashSet.add("php");
linkedHashSet.add("c++");
System.out.println(linkedHashSet);
输出:[c++, java, php, python]
也可自定义排序方式:
Set<String> linkedHashSet = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length()-o2.length(); // 按字符串长度排序
}
});
linkedHashSet.add("java");
linkedHashSet.add("python");
linkedHashSet.add("php");
linkedHashSet.add("c++");
System.out.println(linkedHashSet);
TreeSet会将次序相同的元素看作同一元素并去重:
输出:[php, java, python]
TreeSet的实现原理如下:
- 内部数据结构:TreeSet内部使用的是TreeMap来存储元素,其中元素作为TreeMap的键,值则为一个固定的Object对象。
- 红黑树:TreeSet使用红黑树数据结构来存储元素。红黑树是一种自平衡的二叉搜索树,它保持了元素的有序性,并且在插入、删除和查找操作上具有较好的性能。
- 排序方式:TreeSet根据元素的自然顺序(通过实现Comparable接口)或者指定的Comparator来进行排序。在构造TreeSet对象时,可以传入一个Comparator对象来指定排序规则。
- 添加和删除操作:当向TreeSet中添加或删除元素时,TreeSet会根据排序规则将元素插入或删除到红黑树的适当位置。
- 迭代顺序:由于TreeSet使用红黑树存储元素,所以TreeSet中的元素会按照排序规则进行遍历,即按照元素的自然顺序或者指定的Comparator进行遍历。