HashMap是一种非常常见的数据结构,是一种键,值对<k,v>形式的存储结构,对于HashMap应该注意一下四点
- HashMap不管是键还是值都是允许为null的
- HashMap是无序的(put进去的顺序和get出来的顺序是不同的)
- 是否允许重复数据?key重复会被覆盖value可以重复
- HashMap非线程安全
首先先来看看当我们穿件一个HashMap时发生了什么?

在HashMap的构造函数中

这个常量表示的负载因子

默认的初始化容量-必须是2的次幂
在重要的是209行table

注释说了:table根据需要调整大小,当时必须是2的次幂
在看下init是个空方法,这个方法是HashMap的子类实例化是使用的

好了综上所诉建一个hashMap实例,实际就是初始化了一个数组table,长度为16,内部装Entry对象
接下来让我们看下Entry类中都有什么

下面我们来看下put方法是怎样添加的,我们现已null为key和String为value

当key等于null时进入putforNullKey()


394行当我们第一次添加元素时,table[0]中并没有保存任何值 table[0]等于null,所以不会进入for循环,直接进入addEntry()

此时hash=0,key=null, value="1",752行将null赋值给e,所以e=null
753行:也就是说新生成一个Entry对象赋值给table[bucketIndex]也就是table[0]如下图

以上所述能看出,当key时null时Entry时保存在table[1]上的,所以key是可以等于null的
再看下put时key不是null,首先添加一个元素map.put("abc", "97");

369行跳过,到327行key时String类型那么key.hashCode()一定是String的hashcode方法,计算值是96354(hashCode()详情将)在使用下面的hash()(>>>右移高位补0,^异或

计算出int hash=93442,再来看indexFor(),很简单使用hash与length-1取模
计算的出i=5,现在我们确定要在table[5]上保存元素

现在是头一次保存元素,table[5]还是null,所以直接实行385 addEntry(hash, key, value, i);
此时 hash=93442,key="abc", value="97", i=5,进入addEntry()

752行将null赋值给e,753创建一个新的Entry对象赋值个table[5],如下图

好,现在我们754是扩容问题,一会再说,现在我们再来添加一个元素map.put("Zd@","98")
过程一样,你会发现他的hash也为93422,i也是5,看374行此时的table[5]已经不是null了,进入for循环,if(1&&0),故退出循环,执行了 addEntry()方法形成下图

综上所述HashMap实例化和存值过程就是生成一个Entry的数组,每个数组元素是一个单链表,在put时发现要put的元素的key在单链表中已经有了则将原有数据覆盖,并返回原来的value376到380行,如下图

其中379行是从新组个单链表结构的语句,这就是我们为什么说元素不能重复的原因了!
下面让我们来看看java怎样对map进行扩容

经过上面的操作 我们知道size已经等于3了,threshold=(int)16*0.75,阈值 threshold使我们初始化时定义的值 等于12 在来看看resize()(调整)

进入transfer(newTable)

这是在单线程的正常情况下,当HashMap<K,V>的容量不够之后的扩容操作,将旧表中的数据赋给新表中的数据.正常情况下,就是下图图片显示的那样.新表的数据就会很正常,并且还需要说的一点就是,进行扩容操作之后,在旧表中key值相同的数据块在新表中数据块的连接方式会逆向.就拿key = “abc”和key = Zd@的两个数据块来说,在旧表中是key = “abc” 的数据块指向key = Zd@的数据块的,但是在新表中,key = Zd@的数据块则是指向了key = “abc”的数据块
还有就是我put的三个值
package thinking.in.java.lesson17;
import java.util.HashMap;
public class Test_People_Hashcode {
public static void main(String[] args) {
HashMap<String, String> map=new HashMap<String, String>();
map.put("2", "1");
map.put("abc", "2");
map.put("Zd@", "3");
System.out.println(93442&15);//hash值与15取模,确定数组索引
System.out.println(50&15);
}
} 在旧的HashMap中是在同一链表中的,但在扩容后key="abc"和key="Zd@"还会在同一链表里,单顺序变了,置于newtable[93442&31]=newtable[2],至于key="2" 则会放在 newtable[50&31]=newtable[18]的位置(图中key=3,key=7,key=5依次对应key="abc",key="Zd@",key="2")图片来自网路

然后将newTable[]的引用赋值给table[],并将threshold重新赋值,
标注一个点我们知道HashMap是线程不安全的,简单来说就是因为扩容是设计到了引用的赋值(指针的移动),在两个线程同时对HashMap扩容是会造成Entry中的next保存的是自己本身的引用,这样造成do while 的死循环,也就是e=next ;永远是自己赋值自己
下面我们再来看hashmap的get()过程
有了上面的添加的过程,在看获取的过程就简单多了,299行获取key=null就别说了,直接将table[0]中的value返回就是了,对于其他key的情况同样是先比较hash值在比较单链表中key的equals()发现条件(1&&1)时返回对应value。
这里需要注意一点,当key使用我们自己编写的类时,要想正确的与HashMap一起使用,一定要重写hashcode()和equals()如果只是重写的类的equals方法就会出现put进去一个值但get()时发现没这个key,原因就在于hashmap,get()时为了提高效率先判断hash值的 如果hash值都不同下面它都不看了,你没重写hashCode()那么hash值的计算还是使用Object的hashcode(),这样两个对象的hash很有可能不同啊,就造成这个原因
例如下面的代码(代码来源 Thinking in java)
package thinking.in.java.lesson17;
//土拨鼠类
public class Groundhog extends Object {
protected int munber;//编号
public Groundhog( int n){
this.munber=n;
}
public String toString(){
return "Groundhog #"+munber;
}
} package thinking.in.java.lesson17;
import java.util.Random;
import javax.swing.text.StyledEditorKit.BoldAction;
//预报类
public class Prediction {
protected static Random r=new Random(47);
private boolean show=r.nextDouble()>0.5;
public String toString(){
if(show){
return "还有6周的冬天";
}else{
return "春天来了";
}
}
} package thinking.in.java.lesson17;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class Springdetector {
public static <T extends Groundhog> void detector(Class<T> type) throws Exception{
Constructor<T> gsh=type.getConstructor(int.class);
HashMap<Groundhog, Prediction> map=new HashMap<Groundhog, Prediction>();
for(int i=0;i<5;i++){
map.put(gsh.newInstance(i), new Prediction());
System.out.println(gsh.newInstance(i).hashCode());
}
System.out.println("map:"+map);
Groundhog gh=gsh.newInstance(3);
System.out.println(gh.hashCode());
System.out.println("gh:"+gh);
if(map.containsKey(gh)){
System.out.println(map.get(gh));
}else{
System.out.println("没有这个key");
}
}
public static void main(String[] args) throws Exception {
detector(Groundhog.class);
}
} 输出结果:
714682869
1743911840
322722178
1028355155
1104499981
map:{Groundhog #4=还有6周的冬天, Groundhog #1=还有6周的冬天, Groundhog #3=春天来了, Groundhog #2=春天来了, Groundhog #0=还有6周的冬天}
2136955031
gh:Groundhog #3
没有这个key