JAVA HashSet详解

目录

HashSet概述:

 方法:

HashSet的数据结构

HashSet中添加元素的过程

为什么会出现equals()为false但hashCode()为ture的情况

哈希碰撞:

桶链: 


HashSet概述:

  1. 无序性:HashSet中的元素是无序的,不会按照插入的顺序进行存储和访问。

  2. 不可重复性:HashSet中的元素是唯一的,不会存在重复的元素。这是通过哈希表的机制来实现的。

  3. 允许存储null值:HashSet允许存储一个null元素。

  4. 内部实现机制:HashSet基于哈希表实现,使用了哈希函数来计算元素的存储位置,所以HashSet集合中元素的无序性,不等同于随机性。这里的无序性与元素的添加位置有关。具体来说:我们在添加每一个元素到数组中时,具体的存储位置是由元素的hashCode()调用后返回的hash值决定的。导致在数组中每个元素不是依次紧密存放的,表现出一定的无序性。

  5. HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法得到的哈希值相等,并且两个对象的 equals()方法返回值为true。

  6. 对于存放在Set容器中的对象,对应的类一定要重写hashCode()和equals(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

  7. HashSet集合中元素的无序性,不等同于随机性。这里的无序性与元素的添加位置有关。具体来说:我们在添加每一个元素到数组中时,具体的存储位置是由元素的hashCode()调用后返回的hash值决定的。导致在数组中每个元素不是依次紧密存放的,表现出一定的无序性。

 方法:

方法 描述
boolean add​(E e) 如果指定的元素尚不存在,则将其添加到此集合中。
void clear() 从该集中删除所有元素。
boolean contains​(Object o) 如果此 set 包含指定的元素,则返回 true。
boolean isEmpty() 如果此集合不包含任何元素,则返回 true。
Iterator<E> iterator() 返回此 set 中元素的迭代器。
boolean remove​(Object o) 如果存在,则从该集合中移除指定的元素。
int size() 返回此集合中的元素数(基数)。

HashSet的数据结构

HashSet的数据结构是基于哈希表(Hash Table)实现的。它是Java集合框架中Set接口的一个实现类。

在HashSet内部,使用一个数组(也称为桶)来存储元素。HashSet中的每个桶是一个链表或红黑树结构,称为桶链。如果一个桶中的元素数量超过了某个阈值(默认为8),且当前Java版本支持红黑树,则该桶链会被转换成红黑树。这个设计是为了在哈希冲突较多时,提高查询、添加和删除操作的效率。

具体来说,HashSet的数据结构可以用如下伪代码表示:

public class HashSet extends AbstractSet implements Set, Cloneable, Serializable {
    // 底层数据结构是一个哈希表(数组+链表/红黑树)
    private transient HashMap map;

    // 添加元素到HashSet
    public boolean add(E element) {
        return map.put(element, PRESENT) == null;
    }

    // 删除元素
    public boolean remove(Object element) {
        return map.remove(element) == PRESENT;
    }

    // 判断是否包含元素
    public boolean contains(Object element) {
        return map.containsKey(element);
    }
    
    // 其他方法略
}

可以看到,HashSet内部持有一个HashMap实例,通过HashMap的键来存储元素。HashSet的值则统一被设置为一个特殊的Object对象(通常为预定义的PRESENT对象,用来节省空间)。

HashSet利用元素的hashCode()方法和equals()方法来确定元素在哈希表中的存储位置和去重。当添加元素时,HashSet使用元素的哈希码计算出桶索引,然后在该桶中查找是否已经存在相等的元素。如果存在,则不会重复添加。

HashSet中添加元素的过程

  1. 创建一个HashSet对象:

    Set hashSet = new HashSet<>();
  2. 调用HashSet的add()方法来添加元素:

    hashSet.add("apple");
    hashSet.add("banana");
    hashSet.add("orange");
  3. 在添加元素时,HashSet首先会调用被添加元素的hashCode()方法来获取其哈希码。假设"apple"的哈希码为2678758,"banana"的哈希码为3016053,"orange"的哈希码为3508310。

  4. 根据哈希码计算该元素的桶索引:
    在HashSet内部,使用哈希码与桶的数量进行位运算得到桶索引。桶的数量通常是2的幂,比如默认情况下为16。假设使用与运算,得到的桶索引如下:

    • "apple":2678758 & 15 = 10
    • "banana":3016053 & 15 = 5
    • "orange":3508310 & 15 = 6
  5. 在桶索引为10的位置上,没有其他元素,因此将"apple"直接存储在这个位置上。

  6. 在桶索引为5的位置上,也没有其他元素,因此将"banana"直接存储在这个位置上。

  7. 假设在桶索引为6的位置上,已经有一个元素"apple"发生了哈希碰撞。此时,HashSet会使用equals()方法进行进一步比较。

  8. HashSet会调用给定元素("orange")的equals()方法与桶索引为6位置上的已有元素("apple")进行比较。如果equals()方法返回true,则认为元素"orange"已经存在,不会重复添加。如果equals()方法返回false,则认为元素"orange"不同于桶中已有元素,会将"orange"添加到桶链中。

  9. 在这个例子中,假设"orange"不同于"apple",因此"orange"会被添加到桶索引为6的位置上的桶链中。

需要注意的是,当元素数量增加到一定程度时,HashSet可能会进行内部的扩容操作,以保持桶的数量和元素的分布均匀性,从而维护性能。

为什么会出现equals()为false但hashCode()为ture的情况

出现equals()返回false但是hashCode()返回true的情况是由于哈希碰撞(hash collision)造成的。

在HashSet中,当添加元素时,首先会通过元素的hashCode()方法计算出哈希码。然后,HashSet会根据哈希码计算出存储位置,即桶索引。如果在同一个桶索引上存在多个元素,就会发生哈希碰撞。

当发生哈希碰撞时,HashSet会使用equals()方法进行进一步的比较。equals()方法是用来判断两个对象是否相等的方法。默认情况下,equals()方法是使用对象的引用比较,即判断两个对象的引用是否指向同一个内存地址。如果equals()方法返回false,说明两个对象不相等。

然而,两个对象的hashCode()方法返回相同的哈希码,意味着它们被散列到了同一个桶中。此时,HashSet会使用equals()方法来确定它们是否实际上是相等的元素。因此,如果equals()方法返回false,表示存在两个不同的对象,它们的哈希码发生了碰撞。

综上所述,equals()返回false但hashCode()返回true的情况很可能是由于哈希碰撞导致的。这种情况下,HashSet会将它们存储在同一个桶中,并使用equals()方法进行额外的比较以确保元素的唯一性。

哈希碰撞:

当使用HashSet或HashMap等基于哈希表的数据结构时,元素的哈希码(hash code)被用来计算元素在数据结构中的存储位置,即桶索引。每个桶索引对应一个存储位置,元素被存储在该位置上。

在理想情况下,每个元素的哈希码都是唯一的,可以直接映射到不同的桶索引上,从而实现均匀分布。然而,在实际情况下,哈希函数是有限的,无法保证每个元素的哈希码都是唯一的。因此,在一些情况下会发生哈希碰撞,即多个元素的哈希码映射到了相同的桶索引上。

当多个元素的哈希码映射到同一个桶索引上时,这些元素会形成一个链表或红黑树结构,称为桶链(bucket chain)。桶链中的元素共享相同的桶索引,但哈希码不同,因此它们在链表或红黑树中以顺序的方式存储。这样,当需要添加、删除或查找元素时,系统会在相应的桶链上进行操作。

可以将桶链类比为一个桶,其中具有相同桶索引的元素被放置在同一个桶中。如果多个元素的哈希码不同,但它们被映射到同一个桶索引上,就会出现哈希碰撞。这是因为哈希表中的存储位置只有有限的数量,每个桶的容量是有限的,无法容纳无限数量的元素。因此,元素需要共享同一个存储位置,形成桶链。

在哈希碰撞发生时,HashSet或HashMap等数据结构会使用equals()方法进行进一步的比较,以区分相同桶索引上的不同元素。这是为了保证元素的唯一性,避免存储重复的元素。只有当equals()方法返回true时,元素才被认为是相等的,HashSet或HashMap才会执行相应的操作。

桶链: 

桶链是指在哈希表(比如HashSet或HashMap)中,当多个元素的哈希码映射到同一个桶索引上时所形成的链表(或红黑树)结构。

在哈希表中,每个元素都根据其哈希码确定了存储位置,即桶索引。当多个元素的哈希码映射到同一个桶索引上时,这些元素会形成一个链表或红黑树结构,称为桶链。桶链中的元素共享相同的桶索引,但哈希码不同,因此它们在链表或红黑树中以顺序的方式存储。

具体来说,当发生哈希碰撞时,新加入的元素会添加到相同桶索引上的桶链中。如果桶链为空,则新元素直接链接为该桶链的第一个元素;如果桶链不为空,则新元素会被追加到桶链的末尾。这样,通过桶链,哈希表可以存储和访问具有相同桶索引的不同元素。

在Java中,当元素数量较少时,哈希表会使用链表来存储桶链。当元素数量较多时,为了提高访问效率,哈希表会将桶链转换为红黑树。这样,查找、插入和删除操作的时间复杂度可以从O(n)降低为O(log n)。

总而言之,桶链是指哈希表中,多个元素的哈希码映射到同一个桶索引上时所形成的链表或红黑树结构。它可以解决哈希碰撞的问题,确保元素的存储和访问能够保持高效。

你可能感兴趣的:(JAVA,java,开发语言)