认真研究Java集合之HashSet 的实现原理

简介: 认真研究Java集合之HashSet 的实现原理

HashSet 是 Set 接口的典型实现,由哈希表(实际上是一个HashMap 实例)支持,大多数时候使用 Set 集合时都使用这个实现类。HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能。


【1】HashSet基础概念

HashSet 具有以下特点:


不能保证元素的排列顺序

HashSet 不是线程安全的

集合元素可以是 null

当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后通过 hashCode 值计算得到hash值决定该对象在 HashSet 中的存储位置。


HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。


HashSet类继承图

HashSet方法概览


hashCode()与equals()的相关规定

如果两个对象相等,则hashcode一定也是相同的。

两个对象相等,对两个对象分别调用equals方法都返回true。

即使两个对象有相同的hashcode值,它们也不一定是相等的。

因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖。hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指 向相同的数据)。


对象的相等与指向他们的引用相等,两者有什么不同?


对象的相等 比的是内存中存放的内容是否相等而 引用相等 比较的是他们指向的 内存地址是否相等。


【2】HashSet的实现

对于HashSet 而言,它是基于HashMap 实现的,HashSet 底层使用HashMap 来保存所有元素,因此HashSet 的实现比较简单,相关HashSet 的操作,基本上都是直接调用底层HashMap 的相关方法来完成。


① 核心属性和构造方法

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;
// 底层使用HashMap 来保存HashSet 中所有元素。
    private transient HashMap<E,Object> map;
// 定义一个虚拟的Object 对象作为HashMap 的value,将此对象定义为static final。
    private static final Object PRESENT = new Object();
     /*
     默认的无参构造器,构造一个空的HashSet。
     实际底层会初始化一个空的HashMap,并使用默认初始容量为16 和加载因子0.75。
     */
    public HashSet() {
        map = new HashMap<>();
    }
  /*
     构造一个包含指定collection 中的元素的新set。
     实际底层使用默认的加载因子0.75 和足以包含指定
     collection 中所有元素的初始容量来创建一个HashMap。
     @param c 其中的元素将存放在此set 中的collection。
     */
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }
    /*
     以指定的initialCapacity 和loadFactor 构造一个空的HashSet。
     实际底层以相应的参数构造一个空的HashMap。
     */
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }
     /*
     以指定的initialCapacity 构造一个空的HashSet。
     实际底层以相应的参数及加载因子loadFactor 为0.75 构造一个空的HashMap。
     */
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }
     /*
     以指定的initialCapacity 和loadFactor 构造一个新的空链接哈希集合。
     此构造函数为包访问权限,不对外公开,实际只是是对LinkedHashSet 的支持。
     实际底层会以指定的参数构造一个空LinkedHashMap 实例来实现。
     */
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
     /*
     返回对此set 中元素进行迭代的迭代器。返回元素的顺序并不是特定的。
     底层实际调用底层HashMap 的keySet 来返回所有的key。
     可见HashSet 中的元素,只是存放在了底层HashMap 的key 上,
     value 使用一个static final 的Object 对象标识。
     @return 对此set 中元素进行迭代的Iterator
     */
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }
     /*
     返回此set 中的元素的数量(set 的容量)。
     底层实际调用HashMap 的size()方法返回Entry 的数量,就得到该Set 中元素的个数。
     */
    public int size() {
        return map.size();
    }
     /*
     如果此set 不包含任何元素,则返回true。
     底层实际调用HashMap 的isEmpty()判断该HashSet 是否为空。
     */
    public boolean isEmpty() {
        return map.isEmpty();
    }
     /*
     如果此set 包含指定元素,则返回true。
     更确切地讲,当且仅当此set 包含一个满足(o==null ? e==null : o.equals(e))的
     e 元素时,返回true。
     底层实际调用HashMap 的containsKey 判断是否包含指定key。
     */
    public boolean contains(Object o) {
        return map.containsKey(o);
    }

② 添加和移除方法


底层实际将将该元素作为key 放入HashMap。由于HashMap 的put()方法添加key-value 对时,当新放入HashMap 的Entry 中key与集合中原有Entry 的key 相同(hashCode()返回值相等,通过equals 比较也返回true)则新添加的Entry 的value 会将覆盖原来Entry 的value,但key 不会有任何改变。


因此如果向HashSet 中添加一个已经存在的元素时,新添加的集合元素将不会被放入HashMap 中,原来的元素也不会有任何改变,这也就满足了Set 中元素不重复的特性。

// 如果e的hash结果相等或者equals方法相等,表示已经存在
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
//底层实际调用HashMap 的remove 方法删除指定Entry
public boolean remove(Object o) {
     return map.remove(o)==PRESENT;
 }
/*
从此set 中移除所有元素。此调用返回后,该set 将为空。
底层实际调用HashMap 的clear 方法清空Entry 中所有元素。
*/
public void clear() {
   map.clear();
}

③ 克隆方法

返回此HashSet 实例的浅表副本:并没有复制这些元素本身。底层实际调用HashMap 的clone()方法,获取HashMap 的浅表副本,并设置到HashSet 中。

@SuppressWarnings("unchecked")
public Object clone() {
    try {
        HashSet<E> newSet = (HashSet<E>) super.clone();
        newSet.map = (HashMap<E, Object>) map.clone();
        return newSet;
    } catch (CloneNotSupportedException e) {
        throw new InternalError(e);
    }
}


④ 序列化和反序列化

两个概念


序列化:将数据结构或对象转换成二进制字节流的过程


反序列化:将在序列化过程中所生成的二进制字节流的过程转换成数据结构或者对象的过程


这里使用了ObjectOutputStream 来实现 序列化,


首先是map.capacity,

然后是map.loadFactor,

接着是map.size,

最后是set中的每个元素(map.keySet)

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException {
    // Write out any hidden serialization magic
    s.defaultWriteObject();
    // Write out HashMap capacity and load factor
    s.writeInt(map.capacity());
    s.writeFloat(map.loadFactor());
    // Write out size
    s.writeInt(map.size());
    // Write out all elements in the proper order.
    for (E e : map.keySet())
        s.writeObject(e);
}

反序列化

反序列化的本质就是从ObjectInputStream 读取数据。

  • 读取capacity
  • 读取loadFactor
  • 读取size
  • 计算capacity
  • 实例化map
  • 读取每一个key并放入map
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in any hidden serialization magic
    s.defaultReadObject();
    // Read capacity and verify non-negative.
    int capacity = s.readInt();
    if (capacity < 0) {
        throw new InvalidObjectException("Illegal capacity: " +
                                         capacity);
    }
    // Read load factor and verify positive and non NaN.
    float loadFactor = s.readFloat();
    if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
        throw new InvalidObjectException("Illegal load factor: " +
                                         loadFactor);
    }
    // Read size and verify non-negative.
    int size = s.readInt();
    if (size < 0) {
        throw new InvalidObjectException("Illegal size: " +
                                         size);
    }
    // Set the capacity according to the size and load factor ensuring that
    // the HashMap is at least 25% full but clamping to maximum capacity.
    capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
            HashMap.MAXIMUM_CAPACITY);
    // Create backing HashMap
    map = (((HashSet<?>)this) instanceof LinkedHashSet ?
           new LinkedHashMap<E,Object>(capacity, loadFactor) :
           new HashMap<E,Object>(capacity, loadFactor));
    // Read in all elements in the proper order.
    for (int i=0; i<size; i++) {
        @SuppressWarnings("unchecked")
            E e = (E) s.readObject();
        map.put(e, PRESENT);
    }
}

对于HashSet 中保存的对象,请注意正确重写其equals 和hashCode 方法,以保证放入的对象的唯一性。

目录
相关文章
|
18天前
|
Java 大数据 API
Java Stream API:现代集合处理与函数式编程
Java Stream API:现代集合处理与函数式编程
177 100
|
18天前
|
Java API 数据处理
Java Stream API:现代集合处理新方式
Java Stream API:现代集合处理新方式
192 101
|
23天前
|
存储 Java Go
对比Java学习Go——函数、集合和OOP
Go语言的函数支持声明与调用,具备多返回值、命名返回值等特性,结合`func`关键字与类型后置语法,使函数定义简洁直观。函数可作为一等公民传递、赋值或作为参数,支持匿名函数与闭包。Go通过组合与接口实现面向对象编程,结构体定义数据,方法定义行为,接口实现多态,体现了Go语言的简洁与高效设计。
|
1月前
|
算法 Java
50道java集合面试题
50道 java 集合面试题
|
2月前
|
存储 NoSQL Java
Java Stream API:集合操作与并行处理
Stream API 是 Java 8 提供的集合处理工具,通过声明式编程简化数据操作。它支持链式调用、延迟执行和并行处理,能够高效实现过滤、转换、聚合等操作,提升代码可读性和性能。
|
Java 算法
HashSet源码解析(基于Java8)
List保证元素的添加顺序,元素可重复 Set不保证元素的添加顺序,元素不可重复 public class Test { public static void main(String[] arg...
1179 0
|
Java
Java HashSet LinkedHashSet TreeSet类源码解析
Set集合中不含有重复的元素,插入重复的元素会失败。常用的有HashSet LinkedHashSet TreeSet。HashSet是无序的集合,LinkedHashSet中的排序和插入成功的顺序一致重复插入,TreeSet中元素是有序排列的,排序的依据是自身的comparator如果为null则根据key从小到大排序。
2251 0
|
Java 索引 算法
Java HashSet源码解析
本解析源码来自JDK1.7,HashSet是基于HashMap实现的,方法实现大都直接调用HashMap的方法 另一篇HashMap的源码解析文章 概要 实现了Set接口,实际是靠HashMap实现的 不保证遍历时的顺...
966 0
|
Java API
Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例
概要 这一章,我们对HashSet进行学习。我们先对HashSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashSet。内容包括:第1部分 HashSet介绍第2部分 HashSet数据结构第3部分 HashSet源码解析(基于JDK1.6.0_45)第4部分 HashSet遍历方式第5部分 HashSet示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3311252.html   第1部分 HashSet介绍 HashSet 简介 HashSet 是一个没有重复元素的集合。
1137 0
|
22天前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案

热门文章

最新文章