两种用法:1.key与value值一样,Set集合 ; 2.key与value所对应的内容不一样,Map集合
特点:1.访问速度快; 2.需要额外的空间; 3.无序; 4.可能产生碰撞
使用场景:1.缓存; 2.快速查找(在集合中找出是否存在指定的元素);
性能分析:如果没有碰撞,对元素访问的时间复杂度是O(1),但是实际上不可能没有碰撞,所以我们不得不对碰撞进行一定的处理,我们常用链表的方式解决,由于可能会产生碰撞,而碰撞之后需要重新遍历链表,所以时间复杂度将变为O(L),其中L为链表长度
扩容因子/载荷因子:意思达到这个值时,性能就不好了。是一个小数,java语言默认是0.75.在使用散列表的过程中,不会等到把所有地址都用完了才去扩容,而是会在占用地址达到散列表长度乘以扩容因子的这个值时区扩容,一般的扩容会在原有长度基础上乘以2作为新的长度
代码:
package a;
/**
* 元素类、用于存储key和value
* @author lxm
*
*/
public class Entry {
int key;
int value;
Entry next;
public Entry() {
// TODO Auto-generated constructor stub
}
public Entry(int key,int value,Entry next){
super();
this.key=key;
this.value=value;
this.next=next;
}
}
package a;
import java.awt.RenderingHints.Key;
import javax.xml.namespace.QName;
/**
* 散列表的实现代码
* @author lxm
*
*/
public class HashTable {
/**
* 默认散列表的初始化长度
* 设置小一点儿,这样我们可以清楚地看到扩容
* 在实际使用中其实可以在初始化的时候传参,要知道,扩容也是很损耗性能
*/
private static final int DEFAULT_INTIAL_CAPACITY = 4;
/**
* 扩容因子
*/
private static final float LOAD_FACTOR = 0.75f;
/**
* 散列表数组
*/
private Entry[] table = new Entry[DEFAULT_INTIAL_CAPACITY];
private int size=0;//散列表元素的个数
private int use=0;//散列表使用地址的个数
/**
* 根据key,通过哈希函数获取位于散列表数组中的哪个位置
* @param key
* @return
*/
private int hash(int key){
return key%table.length;
}
/**
* 扩容
*/
private void resize(){
int newLength=table.length*2;
Entry[] oldTable = table;
table = new Entry[newLength];
use=0;
for(int i=0;i<oldTable.length;i++){
if(oldTable[i]!=null && oldTable[i].next!=null){
Entry e =oldTable[i];
while(e.next!=null){
Entry next = e.next;
//重新计算哈希值,放入新地址中
int index = hash(next.key);
if(table[index]==null){
use++;
table[index]=new Entry(-1,-1,null);
}
Entry temp = table[index].next;
Entry newEntry = new Entry(next.key,next.value,temp);
table[index].next=newEntry;
e=next;
}
}
}
}
/**
* 添加/修改
* @param key
* @param value
*/
public void put(int key,int value){
int index=hash(key);
if(table[index]==null){
table[index]=new Entry(-1,-1, null);
}
Entry e =table[index];
if(e.next==null){
//不存在值,向链表添加,有可能扩容,要用table属性
table[index].next=new Entry(key, value, null);
size++;
use++;
//不存在值,说明是个未用过的地址,需要判断是都需要扩容
if(use>=table.length*LOAD_FACTOR){
resize();
}
} else{
//本身存在值,修改已有值
for(e=e.next;e!=null;e=e.next){
int k=e.key;
if(k==key){
e.value=value;
return;
}
}
//不存在相同的值,直接向链表中添加元素
Entry tempEntry =table[index].next;
Entry newEntry = new Entry(key,value,tempEntry);
table[index].next=newEntry;
size++;
}
}
/**
* 删除
* @param key
*/
public void remove(int key){
int index = hash(key);
Entry entry = table[index];
Entry preEntry =table[index];
if(entry!=null && entry.next!=null){
for(entry=entry.next;entry!=null;preEntry=entry,entry=entry.next){
int k=entry.key;
if(k==key){
preEntry.next=entry.next;
size--;
return;
}
}
}
}
/**
* 获取
* @param key
* @return
*/
public int get(int key){
int index = hash(key);
Entry e = table[index];
if(e!=null && e.next!=null){
for(e=e.next;e!=null;e=e.next){
int k=e.key;
if(k==key){
return e.value;
}
}
}
//若没有找到,则返回-1
return -1;
}
/**
* 获取散列表中元素的个数
* @return
*/
public int size(){
return size;
}
/**
* 获取散列表的长度,看是否扩容了
* @return
*/
public int getLength(){
return table.length;
}
}
package a;
public class HashTableTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
HashTable hashTable = new HashTable();
hashTable.put(1, 10);
hashTable.put(2, 20);
hashTable.put(5, 50);//和key为1的元素落到同一个散列表地址上了,实际上长度是2
System.out.println(hashTable.getLength());//散列表长度为4
hashTable.put(3, 30);//总长度为4,加上该元素后长度就大于3了,所以扩容了
System.out.println(hashTable.getLength());//散列表长度为8
//在扩容后4个元素又分别落到不同的地址上
hashTable.put(6, 60);//使用了5地址
hashTable.put(7, 70);//使用了6地址,是8的0.75倍,又需要扩容
System.out.println(hashTable.getLength());//散列表长度为16
}
}