一、什么是HashMap?
HashMap是java中一种很常用的数据结构(用来存放数据),HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中(数组中的每一个元素都是一个Entry键值对),这个数组就是HashMap的主干,如图:
对于HashMap最常用的操作就是get(根据key值获取value),put(将键值对存入HashMap)。
二、原理浅析
1.常用方法
一般在创建HashMap的时候会一起将泛型定义,定义泛型之后只能存取对应的数据类型的key-value(否则编译器会报错)
HashMap<String,Object> hashMap = new HashMap<String,Object>(); hashMap.put("数学", 99); hashMap.put("语文", 90); hashMap.put("英语", 92);
这里我们模拟将三科的成绩存入新建的HashMap,接下来就可以调用get获取对应的key的value值
2.原理浅析
那么在调用put方法的时候发生了什么?
当运行hashMap.put("数学", 99),会插入一个key为数学value为99的键值对,这时候会利用一个哈希函数来确定键值对的插入位置(index )。
index =hash(“数学”);
假设index =2,结果就是:
Get方法的原理
使用Get方法根据Key来查找Value的时候,发生了什么呢?
首先会把输入的Key做一次Hash映射,得到对应的index:
index = Hash(“数学”)
由于刚才所说的Hash冲突,同一个位置有可能匹配到多个Entry,这时候就需要顺着对应链表的头节点,一个一个向下来查找。假设我们要查找的Key是“数学”,这时候会拿着这个key去和每个键值对的key比较,如果相等,则返回对应key的value值
由于HashMap的长度是有限的(默认为16),当插入的键值对越来越多的时候,不同的key值通过hash函数运算出的结果可能出现相同的值,这时候就会产生链表
当有另外的键值对的hashcode值与原本hashMap中键值对的hashcode值重复时,就会产生如图的链表,新的entry键值对会放置在链表的头节点上。在日常开发中为了保证hashMap的快速存取,应当避免产生链表,当产生链表时,调用get方法效率会变低。
那么怎么避免产生链表呢?
hash函数的算法就是:(Length是HashMap的长度)利用每个键值对的key的HashCode与hashMap的长度进行位运算(&符号有兴趣可以百度一下原理)
index = HashCode(Key) & (Length - 1)
从公式上可以看出影响index的因素有两个,一个是key的HashCode,另一个是hashMap的长度。
(1)重写key类型的HashCode方法
class Student { String name; int age; //true表示男,false表示女 boolean sex; @Override public int hashCode() { int result = 17; result = 37*result+name.hashCode(); result = 37*result+age; result = 37*result+(sex ? 0 : 1); return result; } @Override public boolean equals(Object obj) { return obj instanceof Student && this.name.equals(((Student)obj).name) && this.age == ((Student)obj).age && this.sex == ((Student)obj).sex; } }如果key的类型是String的话(String是继承Object类的final方法不能被重写),就不能重写了。
(2)改变hashMap的长度
1>初始化hashMap对象时可以设置长度
HashMap<String,Object> hashMap = new HashMap<String,Object>(1024);
长度应当为2的整数次幂(使hash方法结果分布均匀)
2>没有设置初始长度,会自动扩容
当hashMap中的元素数量达到长度的0.75倍时会自动扩容到原长度的2倍,然后重建hashMap,由于长度的变化,会重新运算原有键值对的index,会影响部分性能,如果要存放的元素数量较大时建议初始化时设置长度。