图1 新建-数据存储html
散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。java
①以键值对的形式进行存储;算法
②不容许存在相同的key值,保证惟一映射,再次存入相同key数据,至关于更新数据;数组
③无序存储、无序输出【原理致使,详见三、底层实现部分】;数据结构
④能够存储为null的键和值;ide
样例1:函数
package com.cnblogs.mufasa.demo1; import java.util.HashMap; public class Client { public static void main(String[] args) { HashMap<Integer,Integer> hm=new HashMap<>(50); hm.put(1,1); hm.put(1,2);//至关于把数据更新 hm.put(null,3); hm.put(2,null); hm.forEach((k,v)->{ System.out.println("key:"+k+",value:"+v); }); } }
样例1输出:性能
key:null,value:3 key:1,value:2 key:2,value:null
3.1基本实现流程测试
①HashMap本质上是在内存中开辟一个固定大小的数组空间,②而后根据key计算的hashcode来定位将value存储在数组空间中的哪里【浪费空间不少,完美的状况是每一个key对应的数组空间地址都不相同而且都恰好把空间填满!】,③可是在经过key计算hash值的时候总会出现所得结果相同的状况【除非开辟的原始空间特别大、hash算法特别好】,这个就是hash冲突。优化
经过上面三个基本步骤能够知道HashMap中两个关键的技术点:①hash算法;②hash冲突;
注意:①hashcode值相同可是有可能不是同一个对象,有多是hash冲突;②同一个对象的hashcode值必定相同;
如今命名为Node,之前命名为Entry
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } public final int hashCode() { return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); } public final String toString() { return getKey() + "=" + getValue(); } }
hashcode实现:
package com.cnblogs.mufasa.demo2; public class hmcode extends Object{ @Override public int hashCode() { return super.hashCode(); }//public native int hashCode();??? public static int hashcode2(String str){ int code=0,len=str.length(); char[] strs=str.toCharArray(); for(int i=0;i<len;i++){ code = 31 * code + (Integer.valueOf(strs[i]) & 0xff); } return code; } } class Client{ public static void main(String[] args) { System.out.println((new hmcode()).hashCode()); System.out.println(hmcode.hashcode2("123")); System.out.println("123".hashCode()); } }
输出【原生的hashCode调用的是C++的方法,已经被编译成了DLL文件了】:
1854778591 48690 48690
其中hashcode越分散,在hashmap应用中性能越好!
hashcode优化路线:①普通的映射函数-h(x);②二次映射-a*h(x)+a^2*h(x);/③多函数组合法-a*h1(x)+b*h2(x);【再次验证正确能够这样解决稀疏问题】
当两个key值不一样的数据返回的hash值相同的时候,能够采用拉链法来将新数据链接到以前数据的最后【链表型数据】,而且当这个hashMap的容量超过限定的容量DEFAULT_LOAD_FACTOR时,就须要对容量进行扩充(通常状况下是进行2倍扩充),而且还要将原始数据转移到新的hashMap中【这个过程至关于查询、存储数据,有些耗时】,原始的数据变成了垃圾空间。
应该注意到链表的插入是以头插法方式进行的,例如上面的 <K3,V3> 不是插在 <K2,V2> 后面,而是插入在链表头部。下面的先put K2,V2后put K3,V3。
查找须要分红两步进行:
hashMap扩容:
①HashMap是先遍历旧table,再遍历旧table中每一个元素的单向链表,取得Entry之后,从新计算hash值,而后存放到新table的对应位置。
②LinkedHashMap是遍历的双向链表,取得每个Entry,而后从新计算hash值,而后存放到新table的对应位置。
从遍历的效率来讲,遍历双向链表的效率要高于遍历table,由于遍历双向链表是N次(N为元素个数);而遍历table是N+table的空余个数(N为元素个数)。
hMap函数代码:
package com.cnblogs.mufasa.demo0; import java.util.HashMap; class hMap<K,V> { static final int MAXIMUM_CAPACITY = 1 << 30;//hm最大开辟空间 static final float DEFAULT_LOAD_FACTOR = 0.75f;//最大容量,超过这个容量就须要进行内存的扩充 private int initialCapacity=13; private float loadFactor= 0.75f; private int num=0; transient Node<K,V>[] table;//不可序列化 /* 第一步:构建数据存储结构,特定长度数组、每一个里面是一个Node链表型数据<K,V> 三个构造函数【方法重载】 */ public hMap(){ table=new Node[initialCapacity]; } public hMap(int initialCapacity){ if(initialCapacity>MAXIMUM_CAPACITY){ this.initialCapacity=MAXIMUM_CAPACITY; }else { this.initialCapacity=initialCapacity; } table=new Node[initialCapacity]; } public hMap(int initialCapacity,float loadFactor){ this(initialCapacity); if(loadFactor<=0||loadFactor>DEFAULT_LOAD_FACTOR){ this.loadFactor=DEFAULT_LOAD_FACTOR; }else { this.loadFactor=loadFactor; } table=new Node[initialCapacity]; } /* 第二步:经过key计算hashcode */ private int hashcode(Object key){ int h; return (key==null?0:((h = key.hashCode()) ^ (h >>> 16))); } /* 第三步:数据存、读、删、扩容 */ public void put(K k, V v){//数据存储 if(num>(int) initialCapacity*loadFactor){//须要进行容量扩充了 int newCap=initialCapacity<<1;//默认不超过最大容量,这里须要注意最大容量问题 hMap<K,V> preTable=new hMap<>(newCap); System.out.println("当前size="+num+",数据内存进行扩容"+",之前大小为:"+initialCapacity+",如今大小为:"+newCap); //从新进行旧数据到新数据的转移 //①遍历;②计算存储 for(int i=0;i<initialCapacity;i++){//对原始数据进行遍历整合到新的数据中 if(table[i]!=null){ Node pre=table[i]; while (pre!=null){ preTable.put((K)pre.getK(),(V)pre.getV()); pre=pre.getNext(); } } } table=preTable.getTable(); num=preTable.getNum(); initialCapacity=newCap; preTable=null;//成为垃圾 // System.gc();//手动GC } int hash=hashcode(k); hash=hash&(initialCapacity-1);//计算出应该存储的位置 if(table[hash]==null){ table[hash]=new Node<>(k,v); num++;//计数+1 }else {//在那个位置存在一个数据,可能为【hash冲突】,也多是数据更新 Node pre=table[hash]; while (pre!=null){ if(pre.getK()==k){ pre.setV(v); break; } pre=pre.getNext(); } if(pre==null){ pre=new Node(k,v); } } } public V getValue(K k){//数据读取 int hash=hashcode(k); hash=hash&(initialCapacity-1);//计算出应该存储的位置 Node pre=table[hash]; V v = null; while (pre!=null){ if(pre.getK().equals(k)){ v= (V) pre.getV(); break; } pre=pre.getNext(); } return v; } public void remove(K k){//数据删除 int hash=hashcode(k); hash=hash&(initialCapacity-1);//计算出应该存储的位置 Node pre=table[hash]; if(pre==null){//空数据 return; }else {//存在数据 --num;//计数自减 while (pre!=null){ if(pre.getK().equals(k)){//找到数据位置 pre=pre.getNext(); break; } } } } public int size(){//获取数据大小 return num; } public void removeAll(){//清楚全部数据 num=0; table=new Node[initialCapacity]; } private Node<K,V>[] getTable(){ return this.table; } private int getNum(){ return this.num; } }
自编写的hashMap测试:
package com.cnblogs.mufasa.demo0; public class Client { public static void main(String[] args) { //测试本身写的hashMap数据结构 hMap<Integer,Integer> hm=new hMap<>(4,0.5f); hm.put(null,1); hm.put(null,2); hm.put(1,10); hm.put(2,20); hm.put(3,30); for(int i=4;i<=50;i++){ hm.put(i,i*10); } System.out.println(hm.size()); } }
输出【经验证正确】:
当前size=3,数据内存进行扩容,之前大小为:4,如今大小为:8 当前size=5,数据内存进行扩容,之前大小为:8,如今大小为:16 当前size=9,数据内存进行扩容,之前大小为:16,如今大小为:32 当前size=17,数据内存进行扩容,之前大小为:32,如今大小为:64 当前size=33,数据内存进行扩容,之前大小为:64,如今大小为:128 51
其中hashcode使用的是Object类中的方法!!!
参考连接
https://www.cnblogs.com/java-jun-world2099/p/9258605.html
http://www.javashuo.com/article/p-vukynzog-cb.html
http://www.javashuo.com/article/p-dhpzjlen-v.html
https://blog.csdn.net/liji_xc/article/details/79698223