散列表

两种用法: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            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

    }

}


你可能感兴趣的:(数据结构)