HashSet
-
这个类实现了 Set 接口,由一个哈希表(实际上是一个 HashMap 实例)支持。 它不保证 set 的迭代顺序; 特别是它不保证该顺序恒久不变。此类允许 null 元素。
-
假设哈希函数在桶中正确地分散元素,那么这个类为基本操作(
add
、remove
、contains
和size
)提供稳定性能。 迭代这个 set 需要的时间与 HashSet 实例的大小(元素数)和底层HashMap
实例(桶的数量)的“容量”的和成比例。 因此,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低),这一点非常重要。 -
请注意,此实现不是同步的。 如果多个线程同时访问一个哈希 set,并且至少有一个线程修改了该 set,则必须在外部进行同步。 这通常是通过同步一些自然封装集合的对象来实现的。 如果不存在这样的对象,则应使用 Collections.synchronizedSet 方法“包装” set。 这最好在创建时完成,以防止对集合的意外不同步访问:
Set s = Collections.synchronizedSet(new HashSet(...));
-
此类的
iterator
方法返回的迭代器是快速失败 的:如果在迭代器创建后的任何时间,除了通过迭代器自己的 remove 方法之外的任何方式修改了 set ,迭代器都会抛出 ConcurrentModificationException。 因此,面对并发修改,迭代器很快就会完全失败,而不是冒着在未来不确定的时间出现任意、非确定性行为的风险。 -
请注意,无法保证迭代器的快速失败行为,因为一般而言,在存在非同步并发修改的情况下不可能做出任何硬保证。 快速失败的迭代器会尽最大努力抛出 ConcurrentModificationException。 因此,编写一个依赖此异常来确保其正确性的程序是错误的:迭代器的快速失败行为应该仅用于检测 bug。
HashSet构造器
HashSet()
- 构造一个新的空集; 底层 HashMap 实例具有默认初始容量 (16) 和负载因子 (0.75)。
HashSet (int initialCapacity)
- 构造一个新的空 set; 底层 HashMap 实例具有指定的初始容量和默认负载因子 (0.75)。
HashSet (int initialCapacity, float loadFactor)
- 构造一个新的空set; 其底层
HashMap
实例具有指定的初始容量和指定的加载因子。HashSet (Collection<? extends E> c)
- 构造一个包含指定 collection 中的元素的新 set。
HashSet方法
Modifier and Type | Method | Description |
---|---|---|
boolean | add (E e) | 如果指定元素尚不存在,则将其添加到此set中。 |
void | clear() | 从此set中删除所有元素。 |
Object | clone() | 返回这个 HashSet 实例的浅拷贝:并没有复制这些元素本身。 |
boolean | contains (Object o) | 如果此set合包含指定的元素,则返回 true。 |
boolean | isEmpty() | 如果此set不包含任何元素,则返回 true。 |
Iterator | iterator() | 返回对此 set 中元素进行迭代的迭代器。 |
boolean | remove (Object o) | 如果指定的元素存在,则从该set中移除该元素。 |
int | size() | 返回此set中的元素数(set 的容量)。 |
Spliterator | spliterator() | 在此set中的元素上创建一个后期绑定和快速失败的 Spliterator。 |
Object[] | toArray() | 返回一个包含此set中所有元素的数组。 |
T[] | toArray (T[] a) | 返回一个包含此set中所有元素的数组; 返回数组的运行时类型是指定数组的类型。 |
HashSet底层简要原理
以Integer类型为例,Integer类型计算出的hash值就是其自身value值
public int hashCode() {
return Integer.hashCode(value);
}
public static int hashCode(int value) {
return value;
}
集合中存入的数据 | 29 | 51 | 95 | 29 | 67 | 73 | 25 |
---|---|---|---|---|---|---|---|
hashCode值 | 29 | 51 | 95 | 29 | 67 | 73 | 25 |
通过hash值和一个表达式 计算在数组中的位置(假设) |
3 | 2 | 5 | 3 | 3 | 4 | 0 |
底层数组
索引 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
内容 | 25 | 51 | 29–>67 链表连接 |
73 | 95 |
要放入HashSet中的数据一定要重写hashCode()
和equals
方法,equals比较相同的元素不会用链表连接
HashSet底层原理:数组 + 链表 = 哈希表
相关实践
import java.util.HashSet;
import java.util.Objects;
public class HashT {
public static void main(String[] args) {
//创建一个HashSet集合:
HashSet<Integer> hsi = new HashSet<>();
System.out.println(hsi.add(9));//true
hsi.add(5);
System.out.println(hsi.add(9));//false 这个9没有放入到集合中
hsi.add(8);
System.out.println(hsi.size());//3 唯一,无序
System.out.println(hsi); // [5, 8, 9]
HashSet<String> hss = new HashSet<>();
hss.add("hello");
hss.add("world");
hss.add("hello");
System.out.println(hss.size()); //2
System.out.println(hss); //[world, hello]
//自定义的类不满足唯一,无序的特点。
HashSet<Student> hst = new HashSet<>();
hst.add(new Student(19,"fyz"));
hst.add(new Student(18,"xhr"));
hst.add(new Student(19,"fyz"));
System.out.println(hst.size()); //3
System.out.println(hst);
//[Student{id=19, name='fyz'}, Student{id=18, name='xhr'}, Student{id=19, name='fyz'}]
//自定义的类中重写hashCode()和equals方法后满足唯一,无序特点
HashSet<Studenthash> hsth = new HashSet<>();
hsth.add(new Studenthash(19,"fyz"));
hsth.add(new Studenthash(18,"xhr"));
hsth.add(new Studenthash(19,"fyz"));
System.out.println(hsth.size()); //2
System.out.println(hsth);
//[Student{id=18, name='xhr'}, Student{id=19, name='fyz'}]
}
}
class Student{
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
class Studenthash{
int id;
String name;
public Studenthash(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Studenthash that = (Studenthash) o;
return id == that.id &&
name.equals(that.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
LinkedHashSet
唯一,有序(按照输入顺序)。实质上是在HashSet的基础上增加了一个总的链表,将放入的元素串在一起。
-
具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。 此实现与 HashSet 的不同之处在于它维护一个双向链表,贯穿其所有条目。 这个链表定义了迭代顺序,即元素插入到 set 中的顺序(插入顺序)。 请注意,如果将元素重新插入到集合中,则插入顺序不会受到影响。(如果在
s.contains(e)
返回true
后立即调用s.add(e)
,则元素e
会被重新插入到 sets
中) -
此实现可以让客户免遭未指定的、由
HashSet
提供的通常杂乱无章的排序工作,而又不致引起与TreeSet
关联的成本增加。使用它可以生成一个与原来顺序相同的 set 副本,并且与原 set 的实现无关:void foo(Set s) { Set copy = new LinkedHashSet(s); ... }
- 如果模块通过输入得到一个 set,复制这个 set,然后返回由此副本决定了顺序的结果,这种情况下这项技术特别有用。(客户通常期望内容返回的顺序与它们出现的顺序相同。)
-
此类提供所有可选的
Set
操作,并且允许 null 元素。与HashSet
一样,它可以为基本操作(add
、contains
和remove
)提供稳定的性能,假定哈希函数将元素正确地分布到存储段中。由于增加了维护链接列表的开支,其性能很可能会比HashSet
稍逊一筹,不过,这一点例外:LinkedHashSet
迭代所需时间与 set 的大小 成正比,而与容量无关。HashSet
迭代很可能支出较大,因为它所需迭代时间与其容量 成正比。 -
链接的哈希 set 有两个影响其性能的参数:初始容量 和加载因子(initial capacity and load factor)。它们与
HashSet
中的定义极其相同。注意,为初始容量选择非常高的值对此类的影响比对HashSet
要小,因为此类的迭代时间不受容量的影响。
构造方法
- LinkedHashSet()
- 构造一个带默认初始容量 (16) 和加载因子 (0.75) 的新空链接哈希 set。
- LinkedHashSet(Collection<? extends E> c)
- 构造一个与指定 collection 中的元素相同的新链接哈希 set。
- LinkedHashSet(int initialCapacity)
- 构造一个带指定初始容量和默认加载因子 (0.75) 的新空链接哈希 set。
- LinkedHashSet(int initialCapacity, float loadFactor)
- 构造一个带有指定初始容量和加载因子的新空链接哈希 set。
方法
- 从类 java.util.HashSet 继承的方法
add, clear, clone, contains, isEmpty, iterator, remove, size
- 从类 java.util.AbstractSet 继承的方法
equals, hashCode, removeAll
- 从类 java.util.AbstractCollection 继承的方法
addAll, containsAll, retainAll, toArray, toArray, toString
- 从类 java.lang.Object 继承的方法
finalize, getClass, notify, notifyAll, wait, wait, wait
- 从接口 java.util.Set 继承的方法
add, addAll, clear, contains, containsAll, equals, hashCode, isEmpty, iterator, remove, removeAll, retainAll, size, toArray, toArray
public class LinkedHashT {
public static void main(String[] args) {
//创建一个LinkedHashSet集合:
LinkedHashSet<Integer> lhs = new LinkedHashSet<>();
System.out.println(lhs.add(9));//true
lhs.add(5);
lhs.add(8);
System.out.println(lhs.add(9));//false 这个9没有放入到集合中
lhs.add(6);
System.out.println(lhs.size());//4 唯一,有序
System.out.println(lhs); //[9, 5, 8, 6]
}
}