Java容器类框架分析(5)HashSet源码分析

概述

在分析HashSet源码前,先看看HashSet的继承关系api

HashSet继承关系
HashSet继承关系

从上图能够看出,HashSet继承自AbstractSet,实现了Set接口,接着看一下源码中的注释安全

  • This class implements the Set interface, backed by a hash table
    (actually a HashMapinstance). It makes no guarantees as to the
    iteration order of the set; in particular, it does not guarantee that the
    order will remain constant over time. This class permits the null element.
  • HashSet实现了Set接口,内部有一个哈希表支撑(实际上就是一个HashMap实例),它不保证迭代的顺序;尤为是,随着时间的变化,它不能保证set的迭代顺序保持不变。容许插入空值。

到此发现,HashSet实际上能够拆分红Hash跟Set,Hash指的是HashMap,Set则是指实现了Set接口,这样看来,HashSet的实现其实就比较简单了,下面开始分析源码。bash

正文

成员变量

//序列化ID
 static final long serialVersionUID = -5024744406713321676L;
//内置的HashMap
 private transient HashMap<E,Object> map;

 // 就是一个傀儡,填充HashMap的Value而已,没有实际意义
 private static final Object PRESENT = new Object();复制代码

构造方法

空的构造方法

初始化一个空的HashMapui

public HashSet() {
        map = new HashMap<>();
    }复制代码

带有容量的构造方法

HashMap给定一个容量this

public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }复制代码

带有容量跟负载因子的构造方法

public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }复制代码

带有容量跟负载因子,以及Value类型区分

dummy做为Value是基本类型跟引用类型,注意此处初始化的是一个LinkedHashMapspa

HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }复制代码

经过一个集合初始化

public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }复制代码

调用addAll方法线程

public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        //循环遍历
        for (E e : c)
        //若是set中没有此元素,添加成功
            if (add(e))
                modified = true;
        return modified;
    }复制代码

增长元素

添加一个元素,若是Map中存在,返回false,不然返回truecode

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }复制代码

看一下Map的put方法cdn

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
        int i = indexFor(hash, table.length);
        for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
        //这里比较了hash值跟equals方法
            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;
    }复制代码

因此Set元素必须复写hashcode跟equals方法,否则会致使元素错乱blog

删除元素

public boolean remove(Object o) {
  //直接调用map的方法
        return map.remove(o)==PRESENT;
    }复制代码

clear

public void clear() {
 //调用map的Clear方法
        map.clear();
    }复制代码

contains方法

public boolean contains(Object o) {
   调用map的contains方法
        return map.containsKey(o);
    }复制代码

isEmpty

public boolean isEmpty() {
  //调用map的isEmpty方法
        return map.isEmpty();
    }复制代码

迭代

public Iterator<E> iterator() {
 //由于不须要value,因此只是调用了keySet的iterator
        return map.keySet().iterator();
    }复制代码

分析了一下,其实最终的底层实现都是在调用HashMap的方法,因此了解了HashMap的源码以后,HashSet其实就会比较简单了

总结

  • HashSet是非线程安全的,容许插入空元素
  • HashSet不容许重复元素
  • HashSet的Key须要复写hashcode跟equals方法
相关文章
相关标签/搜索