HashSet 中有一个map属性,数据的存储放在map中(底层实际就是hashMap)
数组加链表(某条链表数>8,且数组的容量>64会转化为红黑树)的实现方式,
有去重功能,存储元素无序,可加入null。
PRESENT是占位作用(映射中的对象关联的伪值),即存放value
put(K key, V value)其中的value在HashSet中代表的就是这个PRESENT
HashSet有几个构造方法,如下:
执行默认构造,创建对象时
HashSet hashSet = new HashSet();
分析:
/** * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has * default initial capacity (16) and load factor (0.75). */ public HashSet() { map = new HashMap<>(); //会创建一个空的HashMap对象 }
//初始化加载因子,默认值=0.75,即DEFAULT_LOAD_FACTOR = 0.75 public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }
//对象创建完毕,调用添加方法,
hashSet.add(1);
分析:实际调用的就是map的put(K key, V value)方法,PRESENT无实际意义,用于占位
/** * Adds the specified element to this set if it is not already present. * More formally, adds the specified element <tt>e</tt> to this set if * this set contains no element <tt>e2</tt> such that * <tt>(e==null ? e2==null : e.equals(e2))</tt>. * If this set already contains the element, the call leaves the set * unchanged and returns <tt>false</tt>. * * @param e element to be added to this set * @return <tt>true</tt> if this set did not already contain the specified * element */ public boolean add(E e) { return map.put(e, PRESENT)==null; //e为要添加的元素 }
//分析 map.put(e, PRESENT)方法
/** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with <tt>key</tt>, or * <tt>null</tt> if there was no mapping for <tt>key</tt>. * (A <tt>null</tt> return can also indicate that the map * previously associated <tt>null</tt> with <tt>key</tt>.) */ public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
//1.首先会将传入的元素(e),进行hash运算,即hash(key)方法
- 如果传入元素为null,即hash运算为0,否则取该添加元素的hashCode值和hashCode值无符号右移16位进行异或运算,目的是计算该添加元素存放在数组表中的具体位置
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2.计算完成后,执行putVal(hash(key), key, value, false, true)方法
在创建hashMap时,该对象有以下属性
/** * Implements Map.put and related methods. * * @param hash hash for key * @param key the key * @param value the value to put * @param onlyIfAbsent if true, don't change existing value * @param evict if false, the table is in creation mode. * @return previous value, or null if none */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) //①第一次添加元素时,table = null n = (tab = resize()).length; //②进入resize()方法 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
分析一下resize()扩容方法:
- 第一次添加,table为null,进行扩容,执行以下语句
- // newCap = 16
- newCap = DEFAULT_INITIAL_CAPACITY;
- // newThr = 16 * 0.75 = 12
- newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
- threshold = newThr; // 加载因子赋值为12
- Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //构造一个容量为16的结点数组
- table = newTab; // 此时数组表为一个容量为16的结点数组 扩容完毕,返回
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; // table第一次添加为null
int oldCap = (oldTab == null) ? 0 : oldTab.length; // oldCap = 0
int oldThr = threshold; // threshold 刚创建hashMap时默认值为0,oldThr = 0
int newCap, newThr = 0;
if (oldCap > 0) { //条件不成立
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY; //newCap = 1<<4 = 16
// newThr = 16 * 0.75 = 12
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr; // 加载因子赋值为12
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
//扩容完成,继续执行②步骤后面的操作
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) //①第一次添加元素时,table = null n = (tab = resize()).length; //②执行完成,扩容为16的数组表 n = 16 //计算i索引(数组表下标索引),确认存放位置, //将p指向数组对应下标的表头元素,此时为null(第一次添加) if ((p = tab[i = (n - 1) & hash]) == null) // 创建新节点保存该添加元素key,放在对应数组表位置 tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; // ③ 修改数加1 if (++size > threshold) //④ ++size = 1 < threshold(12) 不会扩容 if条件不成立 resize(); // void afterNodeInsertion(boolean evict) { } 空实现,提供机制让其他hashMap子类重写 afterNodeInsertion(evict); return null; }
//至此,添加完成 返回null
public boolean add(E e) { // 添加成功会返回null 表明添加成功,如果添加元素重复会返回重复的元素,表明添加失败 return map.put(e, PRESENT)==null; }
//此时,hashSet构成如下所示,在数组索引为1的位置存放了刚新增的1元素
//继续添加元素,add(2),前面逻辑相同,主要以下方法有区别
/** * Implements Map.put and related methods. * * @param hash hash for key * @param key the key * @param value the value to put * @param onlyIfAbsent if true, don't change existing value * @param evict if false, the table is in creation mode. * @return previous value, or null if none */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // 此时,table不为空,如上图所示,if条件不成立 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // 添加的第二个元素计算出来的hash存放的位置和添加的第一个元素的位置不同 // 因此这里if判断依旧成立,重复之前的添加步骤 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
//添加的元素计算的hash位置不重复时,添加逻辑同上,不再赘述,主要看添加相同元素时逻辑
//此时,在添加了1,2元素以后,table中的元素存储情况如图:
//现在,又添加重复元素,add(2)
/**
* Implements Map.put and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// ①此时table不为空,有两个元素被添加进去(1,2) if条件不成立
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// ②此时计算出的hash和之前添加元素(2)的hash相同
// 因此p不为null,指向的刚好是之前添加的元素(2),if条件不成立
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else { //③ 进入此分支,说明新添加的元素计算出的位置在之前已经有元素加入
Node<K,V> e; K k;
// ④ 此时p指向的是数组表对应位置的第一个元素,如上图
// 此时p.hash == hash p.key== key 这里p.key== key
//是因为我加入的是Integer(-128-127) 所有共用同一个对象
// 特别说明,如果加入的元素和之前已经加入的元素计算的hash值相同,
//即使两者地址不同,即不满足p.key== key
// 也会取判断key.equals(k)是否相同,即如果两者的equals相同,也当做相同元素
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// ⑤ 此时将e指向数组表对应位置的第一个元素
e = p;
else if (p instanceof TreeNode) // 这里判断是否是树结点
//如果已经树化,则按照树化的方式添加
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else { // 计算出来的位置相同,但判断不是相同对象,也不是树结点,会进入该分支
for (int binCount = 0; ; ++binCount) {
// 将e指向p的下一个结点(p.next),判断p的下一个结点是否为null
// 如果为空,则将p下结点指向新创建的结点
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//这里是树化的条件,通过分析可以知道binCount >=7才会考虑是否树化
//而binCount = 7时,该链表已经有9个元素(包含newNode)
//因此,当添加的元素已经有8个,继续添加的时候满足该if条件
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//这个方法还有一个判断条件,MIN_TREEIFY_CAPACITY = 64
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
// 因此,还要数组大小<64的时候不会树化,而是先扩容,,
//只有数组大小>64,某条链表的元素达到8个,继续添加的时候才会树化
treeifyBin(tab, hash);
break;
}
//这里和链表的每一个元素再次比较,判断是否为同一个对象,
//如果是放弃添加,中断循环逻辑
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//指向后移
p = e;
}
}
// ⑥ 此时e != null,条件成立,说明加入的是同一个元素
if (e != null) { // existing mapping for key
V oldValue = e.value; // 记录第一次添加的重复元素的伪值,即PRESENT
// onlyIfAbsent 如果为true,不改变存在的value,即e.value
if (!onlyIfAbsent || oldValue == null) // 如果为null ,返回伪值PRESENT
e.value = value;
afterNodeAccess(e);
return oldValue; // 返回伪值
}
}
++modCount;
if (++size > threshold) // 添加的元素>阈值12时,执行扩容
resize();
afterNodeInsertion(evict);
return null;
}
//至此,添加重复元素的逻辑处理结束,这里再分析一下再数组表中已经存在元素的时候的扩容机制resize(),分析见蓝色注释
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
// 继续添加元素时,oldTab !=null,oldCap = 16
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold; // oldThr = 12
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// newCap = oldCap << 1 = 16 * 2 = 32 满足if条件
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// newThr = 12<<1 = 12 * 2 = 24
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr; // 阈值更新为24
@SuppressWarnings({"rawtypes","unchecked"})
// 创建新的结点数组,大小为newCap = 32
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab; // 指向新结点数组。扩容完成
// 重新计算,将旧元素放置到新结点数组对应的位置
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
到此,添加逻辑完成,hashSet的添加原理,以及扩容机制大概如上所述,树化的实现机制以后再补足。