今天就简单了解一下HashMap的实现:
首先先了解一下hashMap:它的底层结构是哈希表,采用了顺序表+链表 ;同一个链表上的存储地址都是相同的是发生冲突的元素;
物理模型
每一个HashMap都包含了一个主数组,数组的每一个节点又包含了链表,每一个链表都是一个Entry对象 其中包含了,hash:使用key计算的哈希码,key:唯一的entry标识,value:对应的值,next:指向下一个节点的地址
同一个链表节点的哈希码不同,但是存储的地址是相同的;
相对于单链表和双链表,HashMap中的链表与之不同的是,HashMap中添加对象是添加在数组中的首个位置的,不是再后面添加了。
HashMap的优点:
- 添加快,查询快,原因:他们都是通过计算得到存储位置的,并不是通过比较得到的,
存储是无序的,
key值是唯一的
HashMap默认空间是16 ,装填因子为75%,当HashMap存储到了160.75=12的时候,HashMap会扩容 扩容原来大小的2倍 即:162=32
分析HashMap put动作:put分为3部分,
1、计算哈希码值
根据key.hashCode();得到哈希码
2、计算存储位置
int index=hashCode%table.length;//根据哈希值来取余 得到存储的位置
3、存储数据到指定的位置
3.1、首先判断该存储位置是否有值 如果没有值的话 就new一个netry对象
table[index]=new Entry(key,value,null,hashCode);
同时size++;
3.2、如果有值,那么就需要查询相同的key是否存在,如果存在就覆盖之前相同key的值
创建entry并且指向链表的第一个对象
Entry entry = table[index];
循环entry对象,当entry对象的key等于要添加对象的key 同时 entry的hash值等于计算出来的hash值 表示该key存在
entry.value=value;
将新的value赋值给老的value
3.3、如果没有相同的key,添加一个新的节点 到链表的第一个位置
Entry firstEntry = table[index];
table[index] = new Entry(key,value,firstEntry,hashCode);
size++;
上代码:
Map接口
/**
* 简单实现HashMap的核心方法
*/
public interface Map {
public void put(Object key,Object value);
public Object get(Object key);
public int size();
public boolean isEmpty();
//定义内部接口
interface Entry {
public Object getKey();
public Object getValue();
}
}
HashMap实现类
public class HashMap implements Map{
static final int DEFAULT_INITIAL_CAPACITY = 16;//hashMap默认长度 超过75% 即16*0.75=12就扩容 扩容大小为2倍
static final float DEFAULT_LOAD_FACTOR = 0.75f;//扩容因子
transient int size;//链表中Entry对象的个数
transient Entry[] table = null; //定义的主数组
/**
* int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
重点:如果指定的初始长度不是2的幂,则会转换成2的幂
*/
public HashMap(){
table=new Entry[DEFAULT_INITIAL_CAPACITY];//默认16
}
/**
* 核心
* @param key
* @param value
*/
@Override
public void put(Object key, Object value) {
//1、计算哈希码
int hashCode=key.hashCode();//根据key来计算哈希值
//2、计算存储位置
int index=hashCode%table.length;//根据哈希值来取余 得到存储的位置 源码:h & (length-1); 效果一样,但是效率高
//3、存储数据到指定的位置
if(table[index]==null){//说明该存储位置还未存在数据
table[index]=new Entry(key,value,null,hashCode);
size++;
}else{//该数据已经存在数据 查询是否有相同的key值存在
Entry entry = table[index];//指向链表的第一个元素
while (entry!=null){
//开始比较
if(entry.hash== hashCode && entry.getKey().equals(key)){ //当entry对象的key等于要添加对象的key 同时 entry的hash值等于计算出来的hash值 表示该key存在
//key值存在开始覆盖
entry.value=value;
return;
}
//指向下一个节点
entry = entry.next;
}
//如果没有相同的key,添加一个新的节点 到链表的第一个位置
Entry firstEntry = table[index];
table[index] = new Entry(key,value,firstEntry,hashCode);
size++;
}
}
@Override
public Object get(Object key) {
//计算哈希值
int hashCode=key.hashCode();
//计算存储位置
int index=hashCode%table.length;
Entry e=null;
if(table[index]!=null){//该位置存储的有元素
Entry entry=table[index];//指向链表的第一个元素
while (entry!=null){
//比较
if(entry.hash== hashCode && entry.getKey().equals(key)){//找到了
e = entry;
break;
}
//指向下一个节点
entry = entry.next;
}
}
return e==null?null:e.getValue();
}
public String toString(){
StringBuilder builder=new StringBuilder("{");
for (int i=0;i<table.length;i++){
//循环主数组
if(table[i] != null ) {
Entry entry = table[i];//指向链表第一个元素
while (entry!= null) {
builder.append(entry.key + "=" + entry.value + ",");
//指向下一个节点
entry = entry.next;
}
}
}
if(size != 0){
builder.deleteCharAt(builder.length()-1);
}
builder.append("}");
return builder.toString();
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size==0;
}
//定义encty对象 每一个主数组中的链表元素都是一个encrt对象
class Entry implements Map.Entry{
final Object key; //Entry对象的key
Object value;//Entry对象的value
Entry next; //指向的下一个Entry对象
int hash;//计算的哈希码值
public Entry(Object key,Object value,Entry next,int hash){
this.key=key;
this.value=value;
this.hash=hash;
this.next=next;
}
@Override
public Object getKey() {
return key;
}
@Override
public Object getValue() {
return value;
}
@Override
public String toString() {
return "Entry{" +
"key=" + key +
", value=" + value +
", next=" + next +
", hash=" + hash +
'}';
}
}
}
测试类
public class TestHashMap {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put(23,"Italian");
map.put(47,"England");
map.put(23,"China");
map.put(36,"Japan");
map.put(48,"America");
map.put(86,"the United States");
map.put(67,"France");
System.out.println(map.size());
System.out.println(map.get(23));//Entry ---China
System.out.println(map.toString());
}
}