散列表的实现是一种在时间和空间上做出权衡的方式。对于数组存储,如果们没有内存限制,则可以直接将数据作为键作为数组的索引,那么所有的查询操作都只需要一次访问内存即可完成。但是当键很多的时候,需要的内存会非常大,所以这种方式不可能实现。
另一方面,如果没有时间限制,我们可以对数组进行遍历查找,来查询数组里面的数据。这样会耗费比较多的时间。
散列表是一种折中的方法,在这两个极端之间找到一个权衡。
散列表的两个步骤:
第一:利用对象的hashCode()函数,求出对象的hash值,利用hash函数(hash函数有很多,这里使用除留余数法),将对象的hash值映射到内存中去(即求出在数组中 的索引)。
第二:在映射的过程中,当两个不同的对象可能会映射到同一个内存单元中时,就发生了碰撞冲突,第二部就是要解决碰撞冲突问题。这里是利用链表,每个数组单元 中,存放一个链表。将映射到同一内存单元的多个元素,存放到链表中。
package hashtable;
import java.util.LinkedList;
import java.util.List;
/*
* hashtable的数据结构
* 使用
* hash函数:除留余数法
* 碰撞解决:分离链接法
*/
public class Table<anyType> {
private static final int DEFUALT_TABLE_SIZE=101;
private List<anyType>[] theList;
private int currentSize;
public Table(){
this(DEFUALT_TABLE_SIZE);
}
public Table(int tableSize){
theList = new LinkedList[tableSize];
for(int i=0;i<tableSize;i++)
theList[i] = new LinkedList<anyType>();
}
public void insert(anyType xType){
List<anyType>whichList = theList[myHash(xType)];
System.out.println("数组的索引:"+myHash(xType)+"\t对象的哈希值:"+xType.hashCode());
if(!whichList.contains(xType)){
whichList.add(xType); //whichList只是一个指向theList数组中指定位置的指针,故操作的实际上还是theList数组对象
currentSize++;
}
}
public void remove(anyType xType){
List<anyType>whichList=theList[myHash(xType)];
if(whichList.contains(xType)){
whichList.remove(xType);
currentSize--;
}
}
public boolean contain(anyType xType){
List<anyType>whichList=theList[myHash(xType)];
return whichList.contains(xType);
}
public void makeEmpty(){
currentSize=0;
for(int i=0;i<theList.length;i++)
theList[i].clear();
}
private int myHash(anyType xType){
int hashVal=0;
hashVal=xType.hashCode()%theList.length;
if(hashVal<0)
hashVal+=theList.length;
return hashVal;
}
public static void main(String[] args) {
Table<people> hashTable = new Table<people>(11);
for(int i=0;i<11;i++)
hashTable.insert(new people("xiaoming"+i, 20+i));
hashTable.remove(new people("xiaoming0", 20));
System.out.println("该对象已经被删除,散列中是否还包含该对象:"+hashTable.contain(new people("xiaoming0", 20)));
}
}
/*
* 自定义对象,为了能够进行散列,需要实现hashCode和equal方法
*/
class people{
private String name;
private int age;
public people(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
people other = (people) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
测试中添加11个对象,将这11个对象散列到初始大小为11的散列表中,运行结果可以看到,大部分情况下,元素是均匀分布在数组中的。
删除掉对象new people("xiaoming0", 20)之后,散列表中就不再包含该对象。对象相等这里是根据重载的equal方法来判断的,而不是对象的地址相等。
运行结果:
数组的索引:5 对象的哈希值:-364430039
数组的索引:4 对象的哈希值:-364430007
数组的索引:3 对象的哈希值:-364429975
数组的索引:2 对象的哈希值:-364429943
数组的索引:1 对象的哈希值:-364429911
数组的索引:0 对象的哈希值:-364429879
数组的索引:10 对象的哈希值:-364429847
数组的索引:9 对象的哈希值:-364429815
数组的索引:8 对象的哈希值:-364429783
数组的索引:7 对象的哈希值:-364429751
数组的索引:8 对象的哈希值:1587523638
该对象已经被删除,散列中是否还包含该对象:false
散列表的构造主要就是这两步:
第一:根据hashCode与合适的散列函数算法,将存储对象均匀散列到存储数组中去。哈希算法还有很多种,直接定址法,除留余数法等等
第二:解决碰撞冲突问题。解决方法有,分离链接法,开放定址法等等