一、HashSet底层实现的数据结构为什么?简单介绍
HashSet的底层实现是HashMap,而HashMap的底层实现为散链表(数组+链表)
因此在学习HashSet的源码是我们必须线先掌握对HashMap的底层实现。(HashMap的底层实现网址:https://mp.csdn.net/postedit/79756598)
List集合和Set集合分别为有序可重复、无序不可重复,那么我们的set集合为什么不可以存放重复元素?我们将在文章最后给出简答。
二、对HashSet的主要变量源码分析
//序列化版本号
static final long serialVersionUID = -5024744406713321676L;
//HashSet底层存储数据的容器;通过HashMap实现
private transient HashMap<E,Object> map;
//定义Object对象用于一会在add值时候作为Value值存入map中
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
三、HashSet之构造函数
//无参数构造,对map变量赋值
public HashSet() {
map = new HashMap<>();
}
// initialCapacity初始化数组长度大小;loadFactor加载因子
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
// initialCapacity初始化数组长度大小;loadFactor加载因子取HashMap底层的构造函数的默认值0.75
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
// 有参数构造,通过给定的集合创建map的大小,如果传入(集合大小/0.75f强制装换为int类型+1)和 16取最大值
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
三、对HashSet中主要方法讲解
1、向集合中添加元素
public boolean add(E e) {
// 调用map的put方法往容器里面添加数据;Map中存放key===>value,value就用定义的Object对象作为value值存入
return map.put(e, PRESENT)==null;
}
2、删除集合中的元素
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
3、清空HashSet集合中的元素
public void clear() {
map.clear();
}
4、返回HashSet集合容器的大小
public int size() {
//调用HashMap的size()方法
return map.size();
}
面试题:HashSet中为什么不能存放重复的数据?
因为在HashSet中数据的底层存储为HashMap实现的;在HashMap添加元素方法add中;当往HashMap中添加元素时除了存在增长策略存在,还对数据的存储通过了计算:
1、首先通过Hash算法确定数据的存储在数组中的位置
2、对通过Hash算法计算的到的数组位置进行是否存在数据判断;不存在就将数据放入该数组位置
3、如果该数组位置存在数据,那么就遍历该位置上的链式结构,如果存在相同的key值就将原来的value值覆盖掉
4、如果不存在就在链式结构的最后追加该数据信息
上述问题中我们讲到了Hash算法,那么它是怎么进行计算的尼?
//jdk1.7的hash(Object o)算法
final int hash(Object k) {
//初始值为0
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
c }
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
//右移动 后 再 异或操作
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
//在jdk1.7中确定数据存放在数组中的位置索引,其中h为上面计算的hash函数的返回值
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : “length must be a non-zero power of 2”;
return h & (length-1);
}
//jdk1.8的hash(Object o)算法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
通过保存对象进行初步计算如果 Object==null,则返回0,否则得到调用对象的hashCode()^(对象的hashCode>>>16)
在jdk1.7中也有对传入对象为null的判断,如果为null,那么得到的hash值为0,一定是放在数组的起始位置。