编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 9.1数据集合设计思想解读
  • 9.2ArrayList设计思想
  • 9.3LinkedList设计思想
  • 9.4HashMap源码设计思想
  • 9.5TreeMap设计思想
  • 9.6HashSet设计思想
  • 9.7LinkedHashMap设计思想
  • 9.8CopyOnWriteArrayList设计
  • 9.9ConcurrentHashMap设计

9.6HashSet设计思想

目录介绍

  • 01.HashSet特点
  • 02.HashSet如何去重
  • 03.HashSet源码分析
  • 04.HashSet案例

01.HashSet特点

  • HashSet特点说明
    • HashSet 实现了 Set 接口,不允许插入重复的元素,允许包含 null 元素,且不保证元素迭代顺序,特别是不保证该顺序恒久不变
    • HashSet 的代码十分简单,去掉注释后的代码不到两百行。HashSet 底层是通过 HashMap 来实现的。
  • 案例测试
    • HashSet是根据hashCode来决定存储位置的,是通过HashMap实现的,所以对象必须实现hashCode()方法,存储的数据无序不能重复,可以存储null,但是只能存一个。
    public class DataType {
        public static void main(String[] args){
            Set<String> set = new HashSet<>();
            set.add("1");
            set.add("2");
            set.add(null);
            set.add("1");
            for(String s : set){
                System.out.println(s);
            }
        }
    }
    运行结果
    null
    1
    2

02.HashSet如何去重

  • 在向 HashSet 添加元素时,HashSet 会将该操作转换为向 HashMap 添加键值对,如果 HashMap 中包含 key 值与待插入元素相等的键值对(hashCode() 方法返回值相等,通过 equals() 方法比较也返回 true),则待添加的键值对的 value 会覆盖原有数据,但 key 不会有所改变,因此如果向 HashSet 添加一个已存在的元素时,元素不会被存入 HashMap 中,从而实现了 HashSet 元素不重复的特征。

03.HashSet源码分析

  • 源码分析如下所示
    public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable{
    
        //序列化ID
        static final long serialVersionUID = -5024744406713321676L;
    
        //HashSet 底层用 HashMap 来存放数据
        //Key值由外部传入,Value则由 HashSet 内部来维护
        private transient HashMap<E,Object> map;
    
        //HashMap 中所有键值对都共享同一个值
        //即所有存入 HashMap 的键值对都是使用这个对象作为值
        private static final Object PRESENT = new Object();
    
        //无参构造函数,HashMap 使用默认的初始化大小和装载因子
        public HashSet() {
            map = new HashMap<>();
        }
    
        //使用默认的装载因子,并以此来计算 HashMap 的初始化大小
        //+1 是为了弥补精度损失
        public HashSet(Collection<? extends E> c) {
            map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
            addAll(c);
        }
    
        //为 HashMap 自定义初始化大小和装载因子
        public HashSet(int initialCapacity, float loadFactor) {
            map = new HashMap<>(initialCapacity, loadFactor);
        }
    
        //为 HashMap 自定义初始化大小
        public HashSet(int initialCapacity) {
            map = new HashMap<>(initialCapacity);
        }
    
        //此构造函数为包访问权限,只用于对 LinkedHashSet 的支持
        HashSet(int initialCapacity, float loadFactor, boolean dummy) {
            map = new LinkedHashMap<>(initialCapacity, loadFactor);
        }
    
        //将对 HashSet 的迭代转换为对 HashMap 的 Key 值的迭代
        public Iterator<E> iterator() {
            return map.keySet().iterator();
        }
    
        //获取集合中的元素数量
        public int size() {
            return map.size();
        }
    
        //判断集合是否为空
        public boolean isEmpty() {
            return map.isEmpty();
        }
    
        //判断集合是否包含指定元素
        public boolean contains(Object o) {
            return map.containsKey(o);
        }
    
        //如果 HashSet 中不包含元素 e,则添加该元素,并返回 true
        //如果 HashSet 中包含元素 e,则不会影响 HashSet ,并返回 false
        //该方法将向 HashSet 添加元素 e 的操作转换为向 HashMap 添加键值对
        //如果 HashMap 中包含 key 值与 e 相等的结点(hashCode() 方法返回值相等,通过 equals() 方法比较也返回 true)
        //则新添加的结点的 value 会覆盖原有数据,但 key 不会有所改变
        //因此如果向 HashSet 添加一个已存在的元素时,元素不会被存入 HashMap 中
        //从而实现了 HashSet 元素不重复的特征
        public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }
    
        //移除集合中的元素 o
        //如果集合不包含元素 o,则返回 false
        public boolean remove(Object o) {
            return map.remove(o)==PRESENT;
        }
    
        //清空集合中的元素
        public void clear() {
            map.clear();
        }
    
        @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);
            }
        }
    
        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);
        }
    
        /**
         * Reconstitute the <tt>HashSet</tt> instance from a stream (that is,
         * deserialize it).
         */
        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);
            }
        }
    
        //为了并行遍历数据源中的元素而设计的迭代器
        public Spliterator<E> spliterator() {
            return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);
        }
        
    }

04.HashSet案例

  • 产生10个1-20之间的随机数要求随机数不能重复案例
    /**
     * 产生10个1-20之间的随机数,要求不能重复
     * 分析:
     *         1: 创建一个HashSet集合对象 , 作用: 存储产生的随机数
     *         2: 生成随机数 , 把随机数添加到集合中
     *         3: 使用循环,当集合的长度大于等于10退出循环 , 小于10就一直循环
     */
    // 创建一个HashSet集合对象 , 作用: 存储产生的随机数
    HashSet<Integer> hs = new HashSet<Integer>() ;
    while(hs.size() < 10) {
        // 使用Random类
        Random random = new Random() ;
        int num = random.nextInt(20) + 1 ;
        // 把num添加到集合中
        hs.add(num) ;
    }
    // 遍历
    for(Integer i : hs) {
        System.out.println(i);
    }
贡献者: yangchong211
上一篇
9.5TreeMap设计思想
下一篇
9.7LinkedHashMap设计思想