HashSet源码初探

附图:

timg.jpg

前言:

在日常项目中,一般我们需要一个元素唯一的集合多用HashSet实现, HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素。但是他可以保证元素的唯一性。

其实对于HashSet,他的源码很简单,其本质就是对hashMap做了一个封装,基本上都是直接调用底层HashMap的相关方法来完成。另外他最大的特点就是Ele唯一,那么他是怎么实现的呢?我们知道,HashMap中key值是不能“重复”的(这个是否重复是通过hashcode和equals比较出来的,这是一个值得探讨的问题),HashSet正是借鉴了HashMap的key的这样一个特性,以此产生了这样一个不能包含重复数据的集合。

一 :结构

public class HashSet<E> 
        extends AbstractSet<E> 
        implements Set<E>, Cloneable, Serializable {

二 :为啥要用HahSet

假如我们现在想要在一大堆数据中查找X数据。LinkedList的数据结构就不说了,查找效率低的可怕。ArrayList哪,如果我们不知道X的位置序号,还是一样要全部遍历一次直到查到结果,效率一样可怕。HashSet天生就是为了提高查找效率的。

另外,散列码是由对象导出的一个整数值。在Object中有一个hashCode方法来得到散列码。基本上,每一个对象都有一个默认的散列码,其值就是对象的内存地址。

三:特性

(1):是一个没有重复元素的集合
(2):底层是由hashMap支持
(3):它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变
(4):允许使用 null 元素
(5):非线程安全

四:重要知识点

(1):重要属性
(2):构造
(3):重要方法
(4):迭代方式
(5):元素唯一性的保证机制
(6):线程安全问题
(7):与TreeSet以及其他集合比较

四:源码解析

4.1:重要属性
    //序列号
    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的源码中有一个重要属性map,这个map就是承载数据的容器,它实现了接口Serializable又以transient修饰map属性,其实质是用了另一种序列化方式,PRESENT是用来填充map的value的默认对象,而真正的值是在map的Key中存储,这也是HashSet为什么能保证元素的唯一性。

4.2:构造
    /**
     * 默认的无参构造器,构造一个空的HashSet。
     * 
     * 实际底层会初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。 
     */
    public HashSet() {
        map = new HashMap<>();
    }


    /**
     * 构造一个包含指定collection中的元素的新set。 
     * 实际底层使用默认的加载因子0.75和足以包含指定 
     * collection中所有元素的初始容量来创建一个HashMap。 
     * 其中的元素将存放在此set中的collection。
     */
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size() / .75f) + 1, 16));
        addAll(c);
    }


    /** 
    * 以指定的initialCapacity构造一个空的HashSet。 
    * 
    * 实际底层以相应的参数及加载因子loadFactor为0.75构造一个空的HashMap。 
    * @param initialCapacity 初始容量。 
    */ 
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }
4.3添加元素
    /**
     * 可以看出,它调用的是map的添加方法,而把元素存储到了key中,value则是用PRESENT填充
     */
    public boolean add(E e) {
        return map.put(e, PRESENT) == null;
    }

    //map添加方法的实现
    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        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;
    }
4.4删除元素
    public boolean remove(Object o) {
        return map.remove(o) == PRESENT;
    }
4.5 迭代器
    /**
     * 迭代器
     *      由于其不保证元素的存入去除顺序,固没有get(int index)获取方法,
     *      
     * 他的迭代器获取是取出map的key集合的迭代器(key才是真正的元素)
     */
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

五:线程安全问题

通过看HashSet的源码我们发现其底层都是调用map的方法来实现的,而且都非同步方法,所以其非线程安全。

如果多个线程同时访问一个哈希 set,而其中至少一个线程修改了该 set,那么它必须 保持外部同步。这通常是通过对自然封装该 set 的对象执行同步操作来完成的

测试代码:

/**
 * ClassName: TestHashSet
 * @author lvfang
 * @Desc: TODO
 * @date 2017-9-22
 */
public class TestHashSet implements Runnable {
    
    public Set<Integer> set = null;
    
    public TestHashSet(Set<Integer> set){
        this.set = set;
    }
    
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) set.add(i);
        System.out.println(set.size());
    }

    public static void main(String[] args) {
        Set<Integer> set = new HashSet<>();
        
        //单个线程操作(始终保持只有50个元素)
        new Thread(new TestHashSet(set)).start();       
        
        //多个线程操作
        //分别启动5个线程,每个线程都忘set中添加0-50的元素,我们知道set是保持元素唯一的,所以最终应该只有50个元素
        for(int i=0;i<5;i++){
            new Thread(new TestHashSet(set)).start();
        }       
    }   
}

解决方案 1 :在操作时方法加同步
解决方案 2 :Set s = Collections.synchronizedSet(new HashSet(...));

六:总结

(1):HashSet:底层数据结构是哈希表,线程是非同步的,无须的
(2):TreeSet:可以对Set集合中的元素进行排序(自然排序,由小到大) 底层的数据结构是二叉树,线程不同步
(3):LinkedHashSet(链表结构和has结构相结合)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,690评论 0 11
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,169评论 11 349
  • 实际上,HashSet 和 HashMap 之间有很多相似之处,对于 HashSet 而言,系统采用 Hash 算...
    曹振华阅读 2,508评论 1 37
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • mariadb是一个开源数的据库管理软件,是MySQL的一个分支,是MySQL创始人在出售MySQL之后开发的一个...
    Yi_Feng阅读 926评论 0 3