Hash表是一种特殊的数据结构。在Hash表中,记录在表中的位置和其关键字之间存在着一种确定的关系,这样我们就可以预先知道关键字在表中的位置,从而直接通过下标找到记录。使时间复杂度直接降为O(1).Hash表采用一个映射函数f(key) –> adress将关键字映射到该记录在表中的存储位置.

###Hash函数的设计

  • 直接定址法 取关键字或者关键字的某个线性函数为Hash地址,即address(key)=a*key+b;如知道学生的学号从2000开始,最大为4000,则可以将address(key)=key-2000作为Hash地址。
  • 平方取中法 对关键字进行平方运算,然后取结果的中间几位作为Hash地址。假如有以下关键字序列{421,423,436},平方之后的结果为{177241,178929,190096},那么可以取{72,89,00}作为Hash地址。
  • 折叠法 将关键字拆分成几部分,然后将这几部分组合在一起,以特定的方式进行转化形成Hash地址。假如知道图书的ISBN号为8903-241-23,可以将address(key)=89+03+24+12+3作为Hash地址。
  • 除留取余法 如果知道Hash表的最大长度为m,可以取不大于m的最大质数p,然后对关键字进行取余运算,address(key)=key%p。在这里p的选取非常关键,p选择的好的话,能够最大程度地减少冲突,p一般取不大于m的最大质数。

###Hash处理冲突方法

  • 开放定址法 为产生冲突的关键字地址 H(key) 求得一个地址序列: H0, H1, H2, …, Hs 1≤s≤m-1,Hi = (H(key) +di ) MOD m,其中: i=1, 2, …, s,H(key)为哈希函数;m为哈希表长;
  • 再哈希法 构造若干个哈希函数,当发生冲突时,根据另一个哈希函数计算下一个哈希地址,直到冲突不再发生。即:Hi=Rhi(key) i=1,2,……k,其中:Rhi——不同的哈希函数,特点:计算时间增加
  • 链地址法 采用数组和链表相结合的办法,将Hash地址相同的记录存储在一张线性表中,而每张表的表头的序号即为计算得到的Hash地址。如上述例子中,采用链地址法形成的Hash表存储表示为: 链地址法

代码实现

package com.sllt.qiao;
public class MyMap<K, V> {
    private int size;// 当前容量
    private static int INIT_CAPACITY = 16;// 默认容量
    private Entry<K, V>[] container;// 实际存储数据的数组对象
    private static float LOAD_FACTOR = 0.75f;// 装载因子
    private int max;// 能存的最大的数=capacity*factor

    // 自己设置容量和装载因子的构造器
    public MyMap(int init_Capaticy, float load_factor) {
        if (init_Capaticy < 0)
            throw new IllegalArgumentException("Illegal initial capacity: "
                    + init_Capaticy);
        if (load_factor <= 0 || Float.isNaN(load_factor))
            throw new IllegalArgumentException("Illegal load factor: "
                    + load_factor);
        this.LOAD_FACTOR = load_factor;
        max = (int) (init_Capaticy * load_factor);
        container = new Entry[init_Capaticy];
    }

    // 使用默认参数的构造器
    public MyMap() {
        this(INIT_CAPACITY, LOAD_FACTOR);
    }

    /**
     * 存
     * 
     * @param k
     * @param v
     * @return
     */
    public boolean put(K k, V v) {
        // 1.计算K的hash值
        // 因为自己很难写出对不同的类型都适用的Hash算法,故调用JDK给出的hashCode()方法来计算hash值
        int hash = k.hashCode();
        //将所有信息封装为一个Entry
        Entry<K,V> temp=new Entry(k,v,hash);
            if(setEntry(temp, container)){
                // 大小加一
                size++;
                return true;
            }
            return false;
    }


    /**
     * 扩容的方法
     * 
     * @param newSize
     *            新的容器大小
     */
    private void reSize(int newSize) {
        // 1.声明新数组
        Entry<K, V>[] newTable = new Entry[newSize];
        max = (int) (newSize * LOAD_FACTOR);
        // 2.复制已有元素,即遍历所有元素,每个元素再存一遍
        for (int j = 0; j < container.length; j++) {
            Entry<K, V> entry = container[j];
            //因为每个数组元素其实为链表,所以…………
            while (null != entry) {
                setEntry(entry, newTable);
                entry = entry.next;
            }
        }
        // 3.改变指向
        container = newTable;
        
    }
    
    /**
     *将指定的结点temp添加到指定的hash表table当中
     * 添加时判断该结点是否已经存在
     * 如果已经存在,返回false
     * 添加成功返回true
     * @param temp
     * @param table
     * @return
     */
    private boolean setEntry(Entry<K,V> temp,Entry[] table){
        // 根据hash值找到下标
        int index = indexFor(temp.hash, table.length);
        //根据下标找到对应元素
        Entry<K, V> entry = table[index];
        // 3.若存在
        if (null != entry) {
            // 3.1遍历整个链表,判断是否相等
            while (null != entry) {
                //判断相等的条件时应该注意,除了比较地址相同外,引用传递的相等用equals()方法比较
                //相等则不存,返回false
                if ((temp.key == entry.key||temp.key.equals(entry.key)) && temp.hash == entry.hash&&(temp.value==entry.value||temp.value.equals(entry.value))) {
                    return false;
                }
                //不相等则比较下一个元素
                else if (temp.key != entry.key && temp.value != entry.value) {
                        //到达队尾,中断循环
                        if(null==entry.next){
                            break;
                        }
                        // 没有到达队尾,继续遍历下一个元素
                        entry = entry.next;
                }
            }
            // 3.2当遍历到了队尾,如果都没有相同的元素,则将该元素挂在队尾
            addEntry2Last(entry,temp);
                
        }
        // 4.若不存在,直接设置初始化元素
        setFirstEntry(temp,index,table);
        return true;
    }
    
    private void addEntry2Last(Entry<K, V> entry, Entry<K, V> temp) {
        if (size > max) {
            reSize(container.length * 4);
        }
        entry.next=temp;
        
    }

    /**
     * 将指定结点temp,添加到指定的hash表table的指定下标index中
     * @param temp
     * @param index
     * @param table
     */
    private void setFirstEntry(Entry<K, V> temp, int index, Entry[] table) {
        // 1.判断当前容量是否超标,如果超标,调用扩容方法
        if (size > max) {
            reSize(table.length * 4);
        }
        // 2.不超标,或者扩容以后,设置元素
        table[index] = temp;
        //!!!!!!!!!!!!!!!
        //因为每次设置后都是新的链表,需要将其后接的结点都去掉
         //NND,少这一行代码卡了哥哥7个小时(代码重构)
        temp.next=null;
    }

    /**
     * 取
     * 
     * @param k
     * @return
     */
    public V get(K k) {
        Entry<K, V> entry = null;
        // 1.计算K的hash值
        int hash = k.hashCode();
        // 2.根据hash值找到下标
        int index = indexFor(hash, container.length);
        // 3。根据index找到链表
        entry = container[index];
        // 3。若链表为空,返回null
        if (null == entry) {
            return null;
        }
        // 4。若不为空,遍历链表,比较k是否相等,如果k相等,则返回该value
        while (null != entry) {
            if (k == entry.key||entry.key.equals(k)) {
                return entry.value;
            }
            entry = entry.next;
        }
        // 如果遍历完了不相等,则返回空
        return null;

    }

    /**
     * 根据hash码,容器数组的长度,计算该哈希码在容器数组中的下标值
     * 
     * @param hashcode
     * @param containerLength
     * @return
     */
    public int indexFor(int hashcode, int containerLength) {
        return hashcode & (containerLength - 1);

    }

    /**
     * 用来实际保存数据的内部类,因为采用挂链法解决冲突,此内部类设计为链表形式
     * 
     * @param <K>key
     * @param <V>
     *            value
     */
    class Entry<K, V> {
        Entry<K, V> next;// 下一个结点
        K key;// key
        V value;// value
        int hash;// 这个key对应的hash码,作为一个成员变量,当下次需要用的时候可以不用重新计算

        // 构造方法
        Entry(K k, V v, int hash) {
            this.key = k;
            this.value = v;
            this.hash = hash;

        }

        //相应的getter()方法

    }
}