jdk7.0源码解析HashSet底层实现原理
一、HashSet的概述
HashSet是Set接口的实现类,用于存储多个元素,存储特点是无序、无下标,元素不可以重复,
同时允许null作为元素;而且线程不安全,因为内部方法为非同步方法。
二、HashSet的存储结构
对于HashSet而言,底层是基于HashMap实现的;HashSet数据的存储是利用HashMap存储对
应的数据,相关的操作也是调用HashMap的方法来实现,所以HashSet的底层实现相对比较简单。
三、HashSet内部实现原理机制(源码解析)
-
HashSet的基本元素
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中的元素HashSet对象, 底层默认创建一个HashMap,容量为足以存储Collection中的元素, 同时采用默认的加载因子 0.75 */ public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } /* 利用指定的容量和加载因子创建一个新的HashSet对象, 传递的参数作为HashMap的参数:代表容量和加载因子 */ public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } /* 利用指定的参数创建一个新的HashSet对象, 实际底层是以此参数创建一个HashMap对象,同时采用默认的 加载因子 */ public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); }
-
HashSet中常见方法的实现:
/* 往 HashSt中添加一个元素:添加成功-true;不成功-false 实际底层调用 HashMap中的put方法,将要添加的元素作为键, 用字段中定义的虚拟Object对象作为值,进行存储,当存储的 key在HashMap中已经存在(哈希值相同,同时调用equals方法 返回值为true)时,key不改变,但是会用新值覆盖旧值,但是 每一次添加时,value都是用的同一个虚拟 Object对象。 所以:向HashSet中添加元素时,如果此元素已经存在(判断 依据:哈希码值相同,同时equals方法的返回值为true),集合 中的元素不会发生改变,这也满足了Set的不重复性。 但是需要注意的就是,判别元素在HashSet中是否存在,在put 方法中调用了 hashCode方法 和 equals方法进行判断,所以 为了保证HashSet的元素不重复性,需要做到以下两点: (1) 覆盖 hashCode方法 原则:内容相同的对象返回相同的哈希码值,为了提高效率 内容不同的对象,尽可能的返回不同的哈希码值 (2) 覆盖equals方法 原则:内容相同的对象,结果返回true */ public boolean add(E e) { return map.put(e, PRESENT)==null; } /* 返回此Set中的元素个数: 实际底层调用了Map中的size方法返回Entry的数量,返回值 代表Set集合中元素的个数 */ public int size() { return map.size(); } /* 判断HashMap中是否为空: 实际底层调用HashMap的isEmpty方法判断是否为空 */ public boolean isEmpty() { return map.isEmpty(); } /* 判断HashSet中是否包含某一个元素: 底层实际调用HashMap的containsKey方法,判断 HashMap中是否包含此键 */ public boolean contains(Object o) { return map.containsKey(o); } /* 从HashSet中删除元素: 实际底层调用的是 HashMap的 remove方法 */ public boolean remove(Object o) { return map.remove(o)==PRESENT; } /* 将HashMap中的元素进行清除: 实际底层调用 Map中的remove方法 */ public void clear() { map.clear(); }
应用层解析:根据put方法的分析,当HashMap中存储键值对时,仅仅考虑key,完全不考虑value,只是根据key来计算并决定每一个键值对(Entry)存储的位置。如果保证键的不重复性,需要让内容相同的键有一个相同的存储位置,这样会让for循环的过程中if条件成立(此过程中需要调用equals方法),则才能让重复的键对应的值,新值替换旧值;但是如果每存储一个键值对时,都获取相同的存储下标,这样数组的同一个下标对应的链表就会很长,并且每一次存储都需要调用equals方法具体比较键的内容是否相同,则会降低存储的效率,所以为了提高效率,尽可能满足内容不同的键给定一个不同的存储下标,这样可以尽量让HashMap中的元素分布均匀即每一个位置上尽可能一个元素。
所以如果自定类型的元素往HashSet中存储(作为HashMap的键)时,需要覆盖hashCode方法和equals方法:
(1) 覆盖hashCode方法的原则:
a. 必须保证内容相同的元素返回相同的哈希码值
b. 为了提高效率,尽可能做到内容不同的元素返回不同的哈希码值
(2) equals方法:内容相同的对象返回true。
四、相关总结
HashSet底层本质为HashMap,,所以先理解HashMap的底层实现原理,再来学习HashSet的
底层实现原理。