HashMap可以用键值对来存储对象,所谓键值对,就是可以通过对象来寻找对象,是对数组索引的推广。
底层原理
存储结构
HashMap底层的实现采用了哈希表,基本结构是“数组+链表”。HashMap里面有一个叫table的Entry数组,Entry是一个用作链表节点的类,也就是说,数组的每个元素都对应着一个链表,结构如图所示:
存储过程
当调用HashMap的put方法时
- 首先,会计算key对象的哈希码,也就是调用它的hashcode()方法,一般是根据地址求出的。
- 然后,通过特定的算法计算出哈希码对应在 [0,数组长度-1) 区间内的哈希值,例如图中是让哈希码模16,求出对应的哈希值是15;
- 最后在table数组对应位置的链表中添加value对象(在添加的过程中会比较,将相同key值的对象覆盖掉)
注:两种最极端的计算哈希值算法:1.hashCode / hashCode 退化成一个链表 2.hashCode / 1退化成一个数组
注:为了提高效率,会将模运算换成位运算,当length是2的幂次时,hashCode & (length - 1)等效于hashCode % length
注:JDK8中,当链表长度大于8时,会将链表转化成一颗红黑树,提高查找效率
查找键值对过程
查找的过程类似于存储过程,只不过把存改为找到后返回。
- 根据key值求出hashCode
- 根据hashCode求出哈希值
- 在table数组对应位置的链表中找到key值相同(equals)的节点,返回value对象
注:equals为true,则hashCode必相同
扩容
当数组使用了四分之三的长度时,会进行扩容,扩容为原来长度的两倍,实质上是新定义一个两倍长度的数组,再进行数组拷贝。
简单实现
只是对HashMap的简单实现,许多情况并未考虑,仅仅是为了方便理解HashMap的两个核心方法:get()和put();
节点类
/**
* 用于HashMap
* TODO
* @version 1.0
* @author 王星宇
*/
class node<K,V>{
int hash;
K key;
V value;
node next;
}
变量及构造方法
node[] table; //位桶数组 buckey array
int size;
/**
* 构造方法 ,默认16(2的整数次幂)个
*/
myHashMap(){
table = new node[16] ; // 2 的整数次幂
}
计算哈希值
/**
* 计算v的哈希值
* @param key key的hashCode
* @param length 数组长度
* @return v的哈希值
*/
public int myHash(int key,int length) {
return key & (length - 1);
}
重写toString()方法
/**
* 重写toString方法
*/
@Override
public String toString() {
StringBuffer SB = new StringBuffer("[");
for(int i = 0;i < table.length;i++) {
node temp = table[i];
while(temp != null) {
SB.append(temp.key + "=" + temp.value + ",");
temp = temp.next;
}
}
SB.setCharAt(SB.length() - 1, ']');
return SB.toString();
}
*put()方法
/**
* 存放键值对,未考虑数组扩容
* @param key
* @param value
*/
public void put(K key,V value) {
node newNode = new node();
newNode.hash = myHash(key.hashCode(),table.length);
newNode.key = key;
newNode.value = value;
newNode.next = null;
node temp = table[newNode.hash];
node last = null;//链表最后一个节点
if(temp == null) {
table[newNode.hash] = newNode;
size++;
}else {
while(temp != null) {
if(temp.key.equals(key)) {
temp.value = value;
break;
}else {
last = temp;
temp = temp.next;
}
}
if(temp == null) {
last.next = newNode;
size++;
}
}
}
get()方法
public V get(K key) {
int hash = myHash(key.hashCode(),table.length);
V value = null;
if(table[hash] != null) {
node temp = table[hash];
while(temp != null) {
if(temp.key.equals(key)){
value = (V)temp.value;
break;
}
temp = temp.next;
}
}
return value;
}
全部代码
package myCollection;
/**
* 测试HashMap
* TODO
* @version 1.0
* @author 王星宇
*/
public class myHashMap <K,V>{
node[] table; //位桶数组 buckey array
int size;
/**
* 构造方法 ,默认16(2的整数次幂)个
*/
myHashMap(){
table = new node[16] ; // 2 的整数次幂
}
/**
* 存放键值对,未考虑数组扩容
* @param key
* @param value
*/
public void put(K key,V value) {
node newNode = new node();
newNode.hash = myHash(key.hashCode(),table.length);
newNode.key = key;
newNode.value = value;
newNode.next = null;
node temp = table[newNode.hash];
node last = null;//链表最后一个节点
if(temp == null) {
table[newNode.hash] = newNode;
size++;
}else {
while(temp != null) {
if(temp.key.equals(key)) {
temp.value = value;
break;
}else {
last = temp;
temp = temp.next;
}
}
if(temp == null) {
last.next = newNode;
size++;
}
}
}
/**
* 计算v的哈希值
* @param key key的hashCode
* @param length 数组长度
* @return v的哈希值
*/
public int myHash(int key,int length) {
return key & (length - 1);
}
/**
* 重写toString方法
*/
@Override
public String toString() {
StringBuffer SB = new StringBuffer("[");
for(int i = 0;i < table.length;i++) {
node temp = table[i];
while(temp != null) {
SB.append(temp.key + "=" + temp.value + ",");
temp = temp.next;
}
}
SB.setCharAt(SB.length() - 1, ']');
return SB.toString();
}
public V get(K key) {
int hash = myHash(key.hashCode(),table.length);
V value = null;
if(table[hash] != null) {
node temp = table[hash];
while(temp != null) {
if(temp.key.equals(key)){
value = (V)temp.value;
break;
}
temp = temp.next;
}
}
return value;
}
}
/**
* 用于HashMap
* TODO
* @version 1.0
* @author 王星宇
*/
class node<K,V>{
int hash;
K key;
V value;
node next;
}
来源:CSDN
作者:聪明的王二狗
链接:https://blog.csdn.net/w_xy999/article/details/104174391