在Java集合框架中,HashSet
是一个典型的实现无序性和不可重复性的数据结构。为了更好地理解这两个概念的含义,我们需要深入了解HashSet
的底层实现机制。
无序性
无序性并不等于随机性。无序性指的是存储的数据在底层数组中并非按照插入顺序进行存储,而是根据数据的哈希值决定其存储位置。
示例代码:
java
import java.util.HashSet;
import java.util.Iterator;
public class HashSetExample {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("one");
set.add("two");
set.add("three");
set.add("four");
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
执行上述代码,输出结果可能是:
txt
one
two
three
four
也可能是其他顺序。可以看到,元素输出的顺序并不是按照插入的顺序,这是因为HashSet
底层使用了哈希表来存储数据,具体存储位置由哈希值决定。
不可重复性
不可重复性是指在HashSet
中添加的元素不能重复。判断重复的依据是equals()
方法返回true
,以及hashCode()
方法值相同。因此,为了保证HashSet
能正确判断元素的唯一性,必须同时重写equals()
方法和hashCode()
方法。
示例代码:
java
import java.util.HashSet;
import java.util.Objects;
public class HashSetExample {
public static void main(String[] args) {
HashSet<Person> set = new HashSet<>();
set.add(new Person("John", 25));
set.add(new Person("John", 25)); // 这将不会被添加,因为equals和hashCode相同
for (Person person : set) {
System.out.println(person);
}
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
在上述代码中,我们重写了Person
类的equals()
和hashCode()
方法。由于两个Person
对象的name
和age
属性相同,因此它们被认为是相同的对象,不会被重复添加到HashSet
中。
深入源码解析
为了更深入地理解无序性和不可重复性,我们来看一下HashSet
的源码实现。
HashSet
实际上是基于HashMap
实现的。每次我们向HashSet
中添加元素,内部都会调用HashMap
的put
方法。
HashSet源码片段:
java
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
}
在HashSet
中,每个元素作为HashMap
的键,而一个固定的对象PRESENT
作为值。因此,HashSet
的add
方法实际上调用了HashMap
的put
方法。
HashMap的put
方法:
java
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
在这个方法中,首先计算键的哈希值,并通过哈希值找到对应的桶位置。如果桶中已经存在相同哈希值且equals
方法返回true
的键,则更新值,否则创建新的Entry
并插入到桶中。
通过这个源码解析,我们可以更好地理解HashSet
如何实现无序性和不可重复性。
实际应用场景
- 去重: 当我们需要一个不包含重复元素的集合时,
HashSet
是一个理想的选择。例如,统计一篇文章中的唯一单词。 - 快速查找:
HashSet
提供了常数时间复杂度的查找操作,因此在需要频繁查找操作的场景中,HashSet
性能非常高效。
示例代码:
java
import java.util.HashSet;
public class UniqueWords {
public static void main(String[] args) {
String text = "This is a test. This test is only a test.";
String[] words = text.split("\\W+");
HashSet<String> uniqueWords = new HashSet<>();
for (String word : words) {
uniqueWords.add(word.toLowerCase());
}
System.out.println("Unique words: " + uniqueWords);
}
}
在这个示例中,我们通过HashSet
来统计一篇文章中的唯一单词,大大简化了代码复杂度,并且保证了高效的性能。