先举个例子,假设,内存只允许申请一段长度为100个整形的连续空间,但我们有10000个整形数据需要存储,当然,我们可以用链表将这10000个数据零散的存储,但这样查找和删除无疑都会很慢,用数组空间又不足,为了实现空间和时间的均衡,我们可以把数组和链表结合利用。基本解决思路是先拿出100个数据存进数组,每个数组元素又可以用做链表的头或尾,这样一定程度上加快了查找速度,又解决了空间问题。但首先,哪个数字放在数组中哪个位置,这就需要我们建立一种映射关系,10000个数字抢100个位子必然会产生冲突,这需要我们设定一种解决冲突的方案。这就是,hash表的基本思想,也是实现hash表的一种最基本的方法。hash表可以简单理解为是一种数据结构,这种结构意义在于实现存储查找时间和空间的均衡。
JDK中提供的hashTable就是hash表的实现,HashaSet,HashMap(实际上是hashtable的升级版,但结构用法相似)等都是基于hash表实现了set或是map接口。
个人理解,尽管hashTable,hashTable,HashMap都用到了map作为存储结构,看似挺复杂,但说到底也是按照数组和链表的原理来分析。影响HashTable使用性能的两个因素:初始容量和加载因子。容量是hash表中桶的数量,即数组元素个数。假设,一开始只有5000个数据要加入表,但后来可能会渐渐增加至10000个,为了减少开始时的空间消耗,我们可以在创建表时先申请一段较小的空间,即设定一个初始容量,之后数据渐渐增多,如果容量及数组长度保持不变就需要不断增加链表长度,这就意味着查找效率的降低,所以必须增大表的容量,以适当的空间代价换取较高的时间效率,这是rehash()过程,但什么时候rehash()才能实现时间和空间的尽量均衡呢?这就用到加载因子这个概念了。默认的是0.75,加载因子太高会增加查询的时间。
hash算法是hash表的核心,也是最复杂的部分,需要根据具体情况设定,这里先不说,以后会在单独写一篇。
最后上一段自己的代码,没什么技术含量,写这个其实就是在给自己理理思路。
public class TestHash { int initialSize=10; //申请固定大小的学生结点类数组 protected StudentNode student_Array[]=new StudentNode[initialSize]; /** * 传入学生结点对象,将其学号转换成int值进行hash计算,返回code值作为相应数组下标; * 若返回的数组下标对应元素为空,直接将该结点放在该位子;若返回下标位子不为空,将该节点前置在该位置结点,形成链表 * @param studentNode 学生结点 */ public void add(StudentNode studentNode){ int num=studentNode.number; int code=getHashCode(num); if(student_Array[code]==null){ student_Array[code]=studentNode; }else{ StudentNode temp=student_Array[code]; while(temp.next!=null){ temp=temp.next; } temp.next=studentNode; } } public StudentNode get(int key){ StudentNode student=null; //将该关键字转换成哈希吗作为数组下标 int hashCode=getHashCode(key); StudentNode tempt=student_Array[hashCode]; while(tempt.number!=key){ tempt=tempt.next; } student=tempt; return student; } public void delete(int key){ int hashCode=getHashCode(key); int count=0; //找到对应结点并结算其在链表中的位子 StudentNode tempt=student_Array[hashCode]; while(tempt.number!=key ){ tempt=tempt.next; count++; } //如果是链表中第一个结点 if(count==0){ student_Array[hashCode]=student_Array[hashCode].next; }else{//如果不是第一个节点 //找到所求结点的前一个节点 StudentNode p=student_Array[hashCode]; while(count>1 ){ p=p.next; } //结点后继直接接在结点前驱上 p.next=tempt.next; } } /** * 传入关键字,进行哈希计算,返回对应code值,作为数组下标 * @param key 关键字(学号) * @return code 对应编码(数组下标) */ public int getHashCode(int key){ int code = 0; code=key%5; return code; }
public class StudentManager { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub StudentManager stuManager=new StudentManager(); TestHash hashTest=new TestHash(); stuManager.addStudent(hashTest); stuManager.travelHash(hashTest); stuManager.deleteStudent(hashTest, 105); stuManager.travelHash(hashTest); } public void addStudent(TestHash hashTest){ StudentNode studentNode; //向hash表中添加20个学生,以学号为关键字 for(int i=1;i<21;i++){ studentNode=new StudentNode(); studentNode.number=100+i; hashTest.add(studentNode); if(studentNode.number==105){ studentNode.name="李四"; } if(studentNode.number==110){ studentNode.name="张三"; } if(studentNode.number==115){ studentNode.name="王五"; } } } public void travelHash(TestHash hashTest){ //遍历哈希表 for(int i=0;i<10;i++){ if(hashTest.student_Array[i]!=null){ StudentNode temp; temp=hashTest.student_Array[i]; while(temp!=null){ temp=temp.next; } } } } public StudentNode findStudent(TestHash hashTest,int key){ StudentNode student=hashTest.get(key); return student; } public void deleteStudent(TestHash hashTest,int key){ hashTest.delete(key); } } class StudentNode { String name;//姓名 int number;//学号 StudentNode next; }