Java HashSet 如何检查重复?

在 Java 中,HashSet 是一个常用的数据结构,用于存储不重复的元素。因为它使用了哈希表作为底层实现,所以具有高效的插入和查找性能。在这篇博客中,我们将详细剖析 HashSet 如何检查重复元素,并结合源码进行深入解析。

基本原理

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他加入的对象的 hashcode 值作比较。如果没有相符的 hashcodeHashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals() 方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让加入操作成功。

HashSet 的源码解析

在 JDK 1.8 中,HashSet 的 add() 方法只是简单地调用了 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 是一个静态的常量对象,用来作为 HashMap 的 value。

HashMap 的 putVal 方法解析

进一步来看 HashMap 的 putVal 方法,它是真正负责插入元素的地方:

java

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

该方法主要执行以下步骤:

  1. 计算哈希值并确定数组索引:通过 hash 值计算出元素在数组中的位置。
  2. 检查是否存在冲突:如果当前位置没有元素,直接插入。如果有冲突(即位置上已有元素),则通过链表或红黑树处理冲突。
  3. 检查重复元素:使用 equals 方法来比较键是否相等。如果相等,则认为是重复元素。
举例说明

假设我们有如下代码:

java

Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("apple");

在上述代码中,HashSet 在添加第二个 "apple" 时,会先计算其 hashcode,发现位置上已有元素(第一个 "apple"),然后调用 equals 方法判断两者是否相同,进而得出添加失败的结论。

性能对比

HashSet 的查找和插入操作在大多数情况下的时间复杂度为 O(1),这是因为哈希表具有快速的随机访问能力。然而在最坏情况下(哈希冲突严重,所有元素都在一个桶内),时间复杂度会退化为 O(n)。

我们可以通过以下代码来测试 HashSet 的性能:

java

public class HashSetPerformanceTest {
    public static void main(String[] args) {
        Set<Integer> hashSet = new HashSet<>();
        long startTime = System.nanoTime();
        for (int i = 0; i < 1000000; i++) {
            hashSet.add(i);
        }
        long endTime = System.nanoTime();
        System.out.println("Time taken to insert 1 million elements: " + (endTime - startTime) + " ns");
    }
}
总结

通过对 HashSet 和 HashMap 源码的剖析,我们可以清晰地了解到 HashSet 如何检查重复元素。它通过计算 hashcode 并调用 equals 方法来确保集合中没有重复元素。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值