`

HashSet--阅读源码从jdk开始

阅读更多

基于HashMap实现

 

HashSet内部实现是基于HashMap的,这是阅读和理解HashSet源码的关键,只要理解了HashMap的实现原理,再来看HashSet的源码就简单了。对于HashMap源码的阅读可以看之前的一篇文章

 

首先看两个重要的成员变量:

private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();//HashMap的value存放的都是这个对象

 

HashSet内部的主要成员变量是一个HashMap,使用这个HashMapkey进行数据存放(利用HashMapkey的不重复,保证HashSet的不可重复),HashMapvalueHashSet没啥意义,所以统一存放的同一个对象PRESENT的引用。

 

构造方法

 

HashSet的构造方法的目的只有一个:实例化自己的HashMap成员变量(实际上就是调用HashMap的各个构造方法进行实例化)。

 

默认构造方法:

public HashSet() {//默认构造方法,实际上调用的HashMap的默认构造方法进行实例化
        map = new HashMap<>();
}

 

 

指定容量的构造方法:

public HashSet(int initialCapacity) {//调用HashMap的指定容量构造方法
        map = new HashMap<>(initialCapacity);
}

 

 

指定容量和增长因子构造方法:

public HashSet(int initialCapacity, float loadFactor) {//调用HashMap的指定容量和增长因子构造方法
        map = new HashMap<>(initialCapacity, loadFactor);
    }

 

 

参数为集合的构造方法

public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));//根据集合的大小,调用HashMap的指定容量和增长因子构造方法
        addAll(c); 实际上循环调用的是HashSet的add方法
    }

 

 

包保护的构造方法

HashSet(int initialCapacity, float loadFactor, boolean dummy) {//该构造方法普通用户无法使用
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

 

这个构造方法只跟HashSet同一个包下的类才可以见,即:java.util包中类才有权使用。目前发现只有在LinkedHashSet的构造方法中调用过该方法,LinkedHashSet是HashSet的子类

 

其他主要方法:

 

HashSet的逻辑处理方法,都是直接调用HashMap的相关方法进行处理。比如add方法调用的HashMapput方法,remove方法调用的HashMapremove 等等:

 

public boolean add(E e) {
    return map.put(e, PRESENT)==null; //hashMap中的每个value都是存放的同一个PRESENT对象的引用
}
 
public boolean remove(Object o) {
        return map.remove(o)==PRESENT; //调用的HashMap的remove方法
    }
 

 

其他方法不再一一列举,都是直接调用HashMap的相对应方法。

 

接下来看看两个特殊的方法。

 

writeObject readObject方法

 

HashSet实现了Serializable序列化接口,我们都知道实现了该接口其实就是可序列化的。重写writeObject readObject方法可以实现自定义序列化。

    

/**
     * 重写的序列化方法
     * @param s
     * @throws java.io.IOException
     */
    private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException {
        // 调用默认序列化方法
        s.defaultWriteObject();
 
        // 序列化HashMap的容量和增长因子
        s.writeInt(map.capacity());
        s.writeFloat(map.loadFactor());
 
        // 序列化HashMap的大小
        s.writeInt(map.size());
 
        // 序列化HashSet的每一个节点
        for (E e : map.keySet())
            s.writeObject(e);
    }
}
 
/**
     * 重写反序列化方法
     * @param s
     * @throws java.io.IOException
     * @throws ClassNotFoundException
     */
    private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
        // 调用默认的序列化方法
        s.defaultReadObject();
 
        // 反序列化容量
        int capacity = s.readInt();
        if (capacity < 0) {
            throw new InvalidObjectException("Illegal capacity: " +
                    capacity);
        }
 
        // 反序列化增长因子.
        float loadFactor = s.readFloat();
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
            throw new InvalidObjectException("Illegal load factor: " +
                    loadFactor);
        }
 
        // 反序列化大小.
        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);
 
        // 根据以上参数,构造新的HashMap
        map = (((HashSet<?>)this) instanceof LinkedHashSet ?
                new LinkedHashMap<E,Object>(capacity, loadFactor) :
                new HashMap<E,Object>(capacity, loadFactor));
 
        // 反序列化每个节点,并放入HashMap
        for (int i=0; i<size; i++) {
            @SuppressWarnings("unchecked")
            E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
}

 

 

我们再看下HashSetHashMap成员变量定义:private transient HashMap<E,Object> map; transient修饰是不能被序列化的,这里所谓的不能被序列化 是指不能被默认的序列化方法序列化。但是HashSet采用重写writeObject readObject方法,自己对transient成员变量进行序列化和反序列化。

为什么HashSet要自定义序列化规则呢?个人理解:可以从源码上来看,序列化和反序列化里不同的地方,就是HashMap的容量capacity在反序列化时进行了重写计算。由于HashMap的容量是自动增长的,为了避免反序列化后重新生成HashMap空间浪费,需要重新进行容量计算。

 

其实ArrayListHashMapHashSet等他们的主要成员变量都是transien修饰的,都重写了writeObject readObject方法,重写的方式都类似(重新计算容量)。这些类都有一个共同的特征,就是容量会自动增长。

 

当然这只是表面上的理解,其实默认的序列化会遍历整个对象的拓扑关系,会消耗更多的空间和时间,自己实现的序列化方法则会简单得多。

 

关于java的序列化和反序列化,且听下回分解。

0
1
分享到:
评论

相关推荐

    源码解析jdk7.0集合:HashSet的底层实现原理.pdf

    源码解析jdk7.0集合:HashSet的底层实现原理.pdf

    javajdk源码学习-JavaSourceLearn:JDK源码学习

    jdk源码学习 JavaSourceLearn 版本号 版本 corretto-1.8.0_275 方式 逐步阅读源码添加注释、notes文件夹添加笔记 计划学习任务计划 标题为包名,后面序号为优先级1-4,优先级递减 java.lang Object 1 String 1 ...

    javajdk1.8源码-Java-source-reading:jdk1.8源代码分析

    java jdk1.8 源码 Java-source-reading 缓慢更新一些个人学习java相关源码过程中的笔记,在这里你将不可避免地看到以下情况: 个别不懂/没想好的地方留空待补全 限于个人水平出现的解读错误 ...HashSet LinkedHashMap

    【JDK1.8源码剖析】Collection接口

    文章目录Collection源码剖析(一)简介(二)源码分析 Collection源码剖析 (一)简介 Collection接口是集合层次结构中的根接口。 (1)下面是常用集合类关系图 Collection  |___List 有序,可重复  |___...

    JDK-:JDK原始码学习笔记

    JDK源码学习 JDK版本基于1.7 集合框架的学习 ArrayList原始码学习 HashMap原始码学习 LinkedList原始码学习 HashSet原始码学习 LinkedHashMap原始学习 LinkedHashSet原始码学习

    java8源码-JavaRobot:Java学习笔记,JavaLearningNote

    说明:如无特别说明,所有代码都基于JDK8 JavaSE(Java基础) Java Core 关键字 synchronized关键字 Java String Java Arrays Java Collections Java 泛型 Java NIO Buffer Channel Selector Java 8 Features 源码解读...

    疯狂的java讲义源码-Java_study:ideagithub测试

    源码 这是Java学习中的一些代码以及重要的知识点 1 2018-10-16 熟悉idea中github使用 2 2018-10-17 添加.ignore插件,忽略不必要的文件 3 2018-10-17 添加覆盖隐藏、抽象类等学习注释 4 2018-10-22 添加类初始化 5 ...

    Java JDK实例宝典

    全部代码出自电子工业出版社夏先波的《Java JDK实例宝典》一书,本书以J2SE 5.0为开发环境,选取Java应用的典型实例,循序渐进地介绍了Java语言的各种开发方法和技巧,实例代码注释详细规范,思路清晰。 第1章 ...

    java基础案例与开发详解案例源码全

    11.2.1 实现类HashSet267 11.2.2 实现类LinkHashSet270 11.2.3 实现类TreeSet272 11.3 List接口实现类277 11.3.1 实现类ArrayList277 11.3.2 实现类LinkedList279 11.3.3 实现类Vector281 11.4 Map接口283 11.4.1 ...

    java内核源码-JavaCompass:「Java指南针」为你学习Java指明方向。内容涵盖互联网Java工程师所需要掌握的核心知识,涉及J

    java内核源码 Java指南针 基础核心 基础知识 反射 泛型 动态代理 JDK8新特性 集合容器 多线程与并发 Spring SpringMVC SpringBoot Mybatis 数据结构与算法 入门基础 基础数据结构 数组&链表 数组&链表进阶 栈 队列 ...

    java范例开发大全(pdf&源码)

    实例204 利用HashSet删除学生 358 实例205 不重复的随机数序列 360 实例206 运用映射的相关类(Map) 363 实例207 运用集的相关类(Set) 365 12.2 List 368 实例208 增加所需的元素 368 实例209 Iterator迭代器的...

    疯狂JAVA讲义

    1.8 何时开始使用IDE工具 21 学生提问:老师,我想学习Java编程,到底是学习Eclipse好呢,还是学习JBuilder好呢? 21 1.9 本章小结 22 本章练习 22 第2章 理解面向对象 23 2.1 面向对象 24 2.1.1 结构化程序...

    Java范例开发大全(全书源程序)

    实例204 利用HashSet删除学生 358 实例205 不重复的随机数序列 360 实例206 运用映射的相关类(Map) 363 实例207 运用集的相关类(Set) 365 12.2 List 368 实例208 增加所需的元素 368 实例209 Iterator...

Global site tag (gtag.js) - Google Analytics