前注:本文不是讲解Java类库的Hashtable实现原理,而是根据计算机哈希表原理自己实现的一个Hashtable。
HashTable内部是用数组存放一个(Key-Value pair)键值对的引用,其原理是根据Key的hashCode来计算出数组下标。因为存放位置是直接计算出来,不需要遍历数据结构,这使得hash table具有快速增删查改的优势。下面介绍HashTable的实现步骤:
取得Key的hashCode。
通过Eclipse等工具可以轻松的复写Object的hashCode方法,生成的hashCode是不会有重复值的。
将hashCode与地址映射(压缩地址):
但是,Hashtable内部是用一个数组来存储Key-Value对的,数组容量有限,本文中设置数组容量为10。那么现在的问题就是如何将hashCode和数组地址相对应?一个简单的方法就是对hashCode取模运算(hashCode%10)。这样就可以将hashCode与数组地址做映射(多对一)。同时注意,hashCode可能为负数,而数组的下标不能为负数,所以在映射的时候需要处理一下。
1 int hashCode = Math.abs(key.hashCode())% this.maxSize;
解决hashCode重复问题:
上文已经说过,数组容量为10,当我们在将hashCode映射为数组下标的时候,肯定会遇到有重复的情况。比如hashCode为21和31,分别对他们取模运算,结果都是1,也就是说这两个hashCode对应数组的下标都是1.那么第一个元素添加上去后,再添加第二个元素,则新元素会覆盖之前的元素。这时,如果我们想后面添加的重复地址元素也能添加上去,只能将其放在其它位置。这时,我们可以将新元素放在改地址的下一位,如果下一位已经有元素,那么就继续往后找,直到找到空位为止(其实这个过程有些边界条件需要考虑,比如找到数组末尾后应跳到数组开头继续找。以及数组已经满了,找遍数组都找不到合适的位置,就应该提示该数组已满,不能插入)。
现在既然解决了上面的问题,也就是说每个元素都能在数组中找到合适的位置(除非数组已满)。那么可以根据这个算法实现数组的增删查改。下面是Hashtable的实现代码:
1 packageorg.lyk.impl;2
3 public class HashTable
4 {5 /**
6 * Key-Value pair 存放键值对7 *@authorliuyuank8 *9 */
10 private classKeyValue11 {12 K key;13 V value;14
15 privateKeyValue(K key, V value)16 {17 this.key =key;18 this.value =value;19 }20
21 publicK getKey()22 {23 returnkey;24 }25
26 public voidsetKey(K key)27 {28 this.key =key;29 }30
31 publicV getValue()32 {33 returnvalue;34 }35
36 public voidsetValue(V value)37 {38 this.value =value;39 }40
41 }42
43 privateObject[] table;44 private int maxSize = 10;45 private int currentAmmount = 0;46
47 publicHashTable()48 {49 this.table = new Object[this.maxSize];50 }51
52 public HashTable(int maxSize) throwsException53 {54 if (0 == maxSize || maxSize < 0 || maxSize > 100)55 {56 throw new Exception("table容量非法!");57 }58
59 this.maxSize =maxSize;60 this.table = newInfo[maxSize];61 }62
63 /**
64 * 增加一个键值对65 *@paramkey66 *@paramvalue67 */
68 public voidadd(K key, V value)69 {70 //将hashCode映射到数组下标
71 int hashCode = Math.abs(key.hashCode())% this.maxSize;72
73 //将元素插入到数组中,如果该位置已经被占用,则循环查找下一个位置,直到找到合适的位置,或发现数组已满,退出循环
74 while (this.table[hashCode] != null
75 && (this.currentAmmount < this.maxSize))76 {77 hashCode++;78 hashCode = hashCode % this.maxSize;79 }80
81 if (this.currentAmmount == this.maxSize)82 {83 //数组已满
84 System.out.println("Hash table 已满");85 } else
86 {87 //找到合适位置
88 this.table[hashCode] = newKeyValue(key, value);89 this.currentAmmount++;90 }91 }92
93 /**
94 * 与add方法同样的算法,根据key值找到数组中元素,然后将改元素设置为null95 *@paramkey96 *@return
97 */
98 public booleanremove(K key)99 {100 int hashCode = Math.abs(key.hashCode()) % this.maxSize;101 int count = 0;102 while (this.table[hashCode] != null && count < this.maxSize)103 {104 if (((KeyValue) this.table[hashCode]).getKey().equals(key))105 {106 this.table[hashCode] = null;107 return true;108 }109 count++;110 hashCode++;111 hashCode = hashCode%this.maxSize;112 }113
114 return false;115 }116
117 publicV get(K key)118 {119 int hashCode = Math.abs(key.hashCode()) % this.maxSize;120 int count = 0;121 while (this.table[hashCode] != null && count < this.maxSize)122 {123 if (key.equals(((KeyValue)this.table[hashCode]).getKey()))124 return ((KeyValue) this.table[hashCode]).getValue();125
126 hashCode++;127 count++;128 hashCode = hashCode%this.maxSize;129 }130 return null;131 }132
133 public booleancontains(K key)134 {135 if (this.get(key) != null)136 {137 return true;138 } else
139 {140 return false;141 }142 }143
144 public voidreplace(K key, V value)145 {146 KeyValue kv = this.find(key);147 if(kv != null)148 {149 kv.setValue(value);150 }151 }152
153 privateKeyValue find(K key)154 {155 int hashCode = Math.abs(key.hashCode()) % this.maxSize;156 int count = 0;157 while (this.table[hashCode] != null && count < this.maxSize)158 {159 if (key.equals(((KeyValue)this.table[hashCode]).getKey()))160 return ((KeyValue) this.table[hashCode]);161
162 hashCode++;163 count++;164 hashCode = hashCode%this.maxSize;165 }166 return null;167 }168 }
HashTable实现
packageorg.lyk.impl;importjava.math.BigInteger;public classInfo
{privateString name;privateString address;privateInteger age;publicInfo(String name, String address, Integer age)
{super();this.name =name;this.address =address;this.age =age;
}
@Overridepublic inthashCode()
{final int prime = 31;int result = 1;
result= prime * result + ((address == null) ? 0: address.hashCode());
result= prime * result + ((age == null) ? 0: age.hashCode());
result= prime * result + ((name == null) ? 0: name.hashCode());returnresult;
}//@Override//public int hashCode()//{//final int prime = 27;//int result = 1;//result = prime*result + (this.name == null ? 0:this.name.hashCode());//result = prime*result + (this.address == null ? 0:this.address.hashCode());//result = prime*result + (this.age == null ? 0 : this.age.hashCode());//return result;//}
@Overridepublic booleanequals(Object obj)
{if (this ==obj)return true;if (obj == null)return false;if (getClass() !=obj.getClass())return false;
Info other=(Info) obj;if (address == null)
{if (other.address != null)return false;
}else if (!address.equals(other.address))return false;if (age == null)
{if (other.age != null)return false;
}else if (!age.equals(other.age))return false;if (name == null)
{if (other.name != null)return false;
}else if (!name.equals(other.name))return false;return true;
}publicString getName()
{returnname;
}public voidsetName(String name)
{this.name =name;
}publicString getAddress()
{returnaddress;
}public voidsetAddress(String address)
{this.address =address;
}publicInteger getAge()
{returnage;
}public voidsetAge(Integer age)
{this.age =age;
}
@OverridepublicString toString()
{return "Info [name=" + name + ", address=" + address + ", age=" +age+ "]";
}
}
Value实现
测试代码:String为Key Info为Value
packageorg.lyk.main;importorg.lyk.impl.BiTree;importorg.lyk.impl.HashTable;importorg.lyk.impl.Info;public classMain
{public static voidmain(String[] args)
{
HashTable ht = new HashTable<>();for(int i =0; i <15;i++)
{
Info info= new Info("sheldon" + i, "address" +i , i);//System.out.println("hashCode in main:" + info.getName().hashCode());
ht.add(info.getName(), info);
}
String key= "sheldon3";
System.out.println(ht.contains(key));
ht.replace(key,new Info("谢耳朵","美国洛杉矶", 999));
System.out.println(ht.contains(key));
System.out.println(ht.get(key));
System.out.println("///~ main done");
}
}