先举个例子,假设,内存只允许申请一段长度为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;
}