Hashset
原理概述:
源码:
public void test1(){
HashSet hashSet=new HashSet();
hashSet.add(1);
hashSet.add("da");//3197
hashSet.add(3197);
}
HashSet hashSet=new HashSet();
原理为利用hashmap
LoadFactor:加载因子
加载因子是表示Hsah表(table=node[])中元素的填满的程度.
若:加载因子越大,填满的元素越多,好处是,空间利用率高了,
但冲突的机会加大了.反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了.
冲突的机会越大,则查找的成本越高.反之,查找的成本越小.因而,查找时间就越小.
因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷. 这种平衡与折衷本质上是数据结构中有名的"时-空"矛盾的平衡与折衷.
设置加载因子默认值
hashSet.add(1);
此时e(添加的值)为key,present为value存入hashmap
PRENSENT为
进入put
进入hash(key)
进入hashcode
当key为int时转为Integer,hashcode直接存为该值1
当key为string类型时:
&
既是位运算符又是逻辑运算符,&的两侧可以是int,也可以是boolean表达式,当&两侧是int时,要先把运算符两侧的数转化为二进制数再进行运算,而短路与(&&)的两侧要求必须是布尔表达式。举例如下:
(1)如果字符是&,则两个整数进行二进制位的与运算;
(2)如果字符是 |,则两个整数进行二进制位的或运算;
(3)如果字符是^,则两个整数进行二进制位异或运算;
(4)如果是其他字符,则固定输出信息:ERROR
12&5 的值是多少?答:12转成二进制数是1100(前四位省略了),5转成二进制数是0101,则运算后的结果为0100即4 这是两侧为数值时;输出格式:
类似3(0011) & 4(0100) = 0
其中,运算符号&的前后都有一个空格,等号的前后也都有一个空格。
上面表示3和4做二进制的与运算,结果是0。
输入样例:
3的二进制是0011,4的二进制是0100,二者与运算的结果是0。
进入putval(重点)
源码(当为第一次赋值时)
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)//给tab与n赋值
//当table(当前的hashmap存量)为空(即为第一次存时),进行扩容
n = (tab = resize()).length;
//为当前table开辟空间,下面详解
if ((p = tab[i = (n - 1) & hash]) == null)
/* (n-1)&hash 得到给此值在该table中得位置
(若n此时tab得容量为16(10000)则n-1为1111,
当hash为3(0011),则i=1111&0011=0011=3
若hash为17,则i=01111&10001=1
若hash为18,则i=01111&10011=2,以此类推
以这种方式确定该数值应该放在的位置,使该tab上能找到“合适”的索引位置装该值
*/
//根据key转化得到的hash值赋值给索引值i,并且查看该索引位置是不是为空
tab[i] = newNode(hash, key, value, null);
//为空,则直接为创建一个node赋值添加
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(12))//(size为所有添加进这个hash对象中元素的总数)
//如果size+1后,大与预备增容大小,则resize开始增容
resize();
afterNodeInsertion(evict);
//留个子类去实现使用,现在为空
return null;
}//至此添加第一个元素成功
resize(赋容量(增容))
先拿出oldtable的容量
当oldcap大于0时(既不是第一次赋值时),
先判断有没有超过MAXIMUM_CAPACITY,若有如上
若没有,
将newcap=oldcap*2,并且得小于MAXIMUM_CAPACITY,oldcap得大于初始扩容的大小16
并将newThr=2*oldThr,
DEFAULT_INITIAL_CAPACITY;//默认的初始化大小为16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
//开始扩容的时候为0.75*16
如果小于或等于0,
将默认初始化值(16)直接赋给newcap
将下次开始的准备扩容的长度newthr赋值为(int)(16*0.75)
解释:hashmap的扩容不会等到容量用完才扩。这样会产生一定层度的堵塞,所以会使容量未用完时的一个Thr值就开始扩容
最后:
threshold = newThr;//赋值给threshold
并返回newtable,完成赋容量
当赋值出现哈希冲突时:
hashSet.add("da");//3197
hashSet.add(3197);
进入putval的else
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//判断是否为相同的值的添加,hash值相同并且key相同(完全相同),且不为null
e = p;//当相同,用原来的赋给e
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) // 当链表数再增加,会大于8时
treeifyBin(tab, hash);//转换为红黑树
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;//在链表中找到完全相同的值也会break
p = e;
}
}
if (e != null) { // 当e被赋值后
V oldValue = e.value;//取出e的value
if (!onlyIfAbsent || oldValue == null)//onlyIfAbsent为一开始默认赋值默认为false
//就是默认新值去覆盖旧值,或者原来的的value为null或者不存在,也会新值去覆盖旧值
e.value = value;
afterNodeAccess(e);
return oldValue;//相当于赋值了,一个旧值
}
}