【Java】浅析哈希表

1、哈希表简介:
通常用于海量数据处理:1 查重 ;2 求 TOP k (时间复杂度,空间复杂度O(1))
优点:增、删、查找O(1)
缺点:占内存空间较大
应用:HashMap,HashTable,LinkedHashSet…等容器,底层都用了哈希表。因为哈希表的增删查的特性,使这些容器的效率增加。
2、哈希值:
说到哈希值那就要提到哈希函数。因为哈希值并不是固定不变的,而使根据应用的不同的哈希函数而改变,比如HashMap,HashTable底层哈希值的计算用的就不是一个方法。
HashTable是key.hashCode取余;而HashMap都是先对hashCode进行再哈希,然后再与(桶数 - 1)进行取余运算。
一些常见的哈希函数:平方取中、随机数、折叠、数字分析、直接定址、平方取中、随机数、折叠、除留余数;
这里简单的介绍两个常用的哈希函数:
除留余数法 (data%bucket_number=bucket_index)
直接定址法( H(key)=a*key+b (a,b为常数))
3、哈希冲突:
虽然哈希值有很多计算的方法,但是当存储的数字足够多的时候,难免会产生不同的对象拥有相同哈希值的情况。我们把这种情况就叫做哈希冲突。那么解决哈希冲突的方法有很多,这里主要介绍两种:
1、线性探测法(向后找,看有没有空的地方,若有则插入)
插入(线性探测法) 参数:laod factor装载因子(已占用桶的个数/桶的总数)
2、链地址法、拉链法(组织在链表的数据结构上)
4、实现简易的哈希表:
1、插入操作:
首先判断是否需要扩容,及是否大于装载因子;
然后用除留余数发计算出哈希值后判断党建准备插入位置的状态,使used,using,unused,如果此位置正在使用,且当前的元素和待插入元素一样,就返回,避免插入相同值。如过不一样但哈希值一样,那就将当前的idx加一再进行除留取余。
代码实现:

class LinerHashMap<T extends Comparable<T>>{
    // 散列表数组
    private Entry<T>[] hashTable;
    // 被占用的桶的个数
    private int usedBucketNum;
    // 哈希表的装载因子
    private double loadFactor;
    // 定义素数表
    private static int[] primTable;
    // 记录当前使用的素数的下标
    private int primIndex;
    // 类的静态初始化块
    static{
        primTable = new int[]{3, 7, 23, 47, 97, 127};
    }

    /**
     * 构造函数,初始化
     */
    public LinerHashMap(){
        this.primIndex = 0;
        this.hashTable = new Entry[primTable[this.primIndex]];
        this.usedBucketNum = 0;
        this.loadFactor = 0.75;
    }
    public void put(T key){
     //计算哈希表是否需要扩容
    double ret = this.usedBucketNum*1.0 / this.hashTable.length;//装载因子=已使用桶数/桶长
    if(ret > this.loadFactor){
        resize(); // 哈希表的扩容
    }

    // 先计算key应该放的桶的下标
    int index = key.hashCode() % this.hashTable.length;//除留取余法
    int idx = index;
    do{
        // 表示是从未使用过的桶
        if(this.hashTable[idx] == null){
            this.hashTable[idx] = new Entry<>(key, State.USING);
            this.usedBucketNum++;
            return;
        }

        // 表示使用过的桶
        if(this.hashTable[idx].getState() == State.USED){
            this.hashTable[idx].setData(key);
            this.hashTable[idx].setState(State.USING);
            this.usedBucketNum++;
            return;
        } else {
            // 正在使用中的桶,不插入重复元素
            if(this.hashTable[index].getData().compareTo(key) == 0){//不能重复插入元素
                return;
            }
            idx = (idx+1)%this.hashTable.length;
        }
    } while(idx != index);

}

2、删除操作:
当当前元素的哈希值和compareTo方法比较后皆符合使,设当前位元素为空,状态设为used。当遇到空桶时候说明此哈希链中没有此元素,直接返回。
代码实现;

public void remove(T key){
    // 先计算key应该放的桶的下标
    int index = key.hashCode() % this.hashTable.length;

    // 从当前位置开始找元素
    int idx = index;
    do{
        // 如果遍历桶的过程中,发现了从未使用过的桶,直接返回
        if(this.hashTable[idx] == null){
            return;
        }
        if(this.hashTable[idx].getState() == State.USING
                && this.hashTable[idx].getData().compareTo(key) == 0){
            this.hashTable[idx].setData(null);
            this.hashTable[idx].setState(State.USED);
            this.usedBucketNum--;
            return;
        }
        idx = (idx+1)%this.hashTable.length;
    } while(idx != index);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值