数据结构——哈希表(Java)

 数组优点在于查找和访问容易,通过下标就可以直接访问,链表的优点在于增删元素容易,只要改变指针指向就能达到增删的效果,而哈希表就集合了数组和链表的优点,在数组中存入链表,这样既能方便访问,又能方便增添和删除元素。

1.创建一个链表类,作为内部类,实现键值对的增删逻辑,这里使用了泛型,优点在于可以存储任意类型的键和值,不需要在获取值时进行强制类型转换,编译器会检查类型一致性,避免类型错误

package ZFile.HashMapV1;

//定义一个泛型链表类
class LinkList<K, V> {
    private class Node {
        K key;
        V value;
        Node next;

//定义内部节点,包括键,值,指向下一个结点的指针
        Node(K key, V value) {
            this.key = key;
            this.value = value;
            this.next = null;
        }
    }

    private Node head;

    public LinkList() {
        head = null;
    }

//链表添加键值对逻辑
    public void add(K key, V value) {
        Node newNode = new Node(key, value);
        if (head == null) {
            head = newNode;//头结点为空,新节点作为头结点
        } else {
            Node temp = head;//临时节点
            Node prev = null;//存临时节点的前一个结点
            while (temp!= null) {
                // 检查 temp.key 和 key 是否相等 同为 null 或同为某值
                if (temp.key == null && key == null) {
                        temp.value = value;
                        return;
                } else if (temp.key.equals(key)) {
                    temp.value = value;
                    return;
                }
                prev = temp;
                temp = temp.next;
            }
            // 如果不存在相同 key 的节点,添加到末尾
            prev.next = newNode;
        }
    }

    public V getval(K key) {
        Node temp = head;
        while (temp!= null) {
            // 检查 temp.key 为 null
            if (temp.key == null) {
                if (key == null) {
                    return temp.value;
                }
            } else if (temp.key.equals(key)) {
                return temp.value;
            }
            temp = temp.next;
        }
        return null;
    }
}

2.定义一个哈希表,设定一个数组初始长度为16(参考内置哈希表数组长度) 

// 自定义哈希表
public class MHashMap<K, V> {
    // 保存链表的数组
    public LinkList<K, V>[] linkArr;
    public static int len = 16;

    // 初始数组 自定义初始长度
    public MHashMap(int len) {
        linkArr = new LinkList[len];
        // 初始化
        for (int i = 0; i < len; i++) {
            linkArr[i] = new LinkList<>();
        }
    }
    // 默认长度
    public MHashMap() {
        this(len);
    }
   

 3.实现键值对的输入put,获取get,存入方法运用到自定义哈希函数

 // put
    public void put(K key, V value) {
        // 计算保存位置
        int index = hash(key);
        linkArr[index].add(key, value);
    }


    // get
    public V get(K key) {
        int index = hash(key);
        return linkArr[index].getval(key);
    }
    // 哈希函数(散列函数)
    public int hash(K key) {
        if (key == null) {
            return 0;   //key == null 直接返回 0(固定存储在 linkArr[0])
        }
        return Math.abs(key.hashCode())%len;
    }
}

 4.测试输出

package ZFile.HashMapV1;

public class Hash {
    public static void main(String[] args) {
        MHashMap<String, Integer> hm = new MHashMap<>();
        hm.put("b", 10);
        hm.put("a", 10);
        hm.put("a", 20);
        hm.put("hello", 29);
        hm.put("world", 99);
        hm.put(null, 30);

        System.out.println(hm.get("a"));
        System.out.println(hm.get("b"));
        System.out.println(hm.get("hello"));
        System.out.println(hm.get("world"));
        System.out.println(hm.get(null));
    }
}

6.结果展示

7.不足与优化方向:

a.使用%len取模方法确保索引在 [0, len-1] 范围内,但是对于较大的哈希值可能会丢弃高位,产生哈希冲突,可以参考内置哈希函数采用高位扰动,h >>> 16:将 h 无符号右移 16 位(高位补 0),相当于取 h 的高 16 位。 h ^ (h >>> 16):将 高 16 位和低 16 位进行异或(XOR),使得高位信息能影响低位。

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

b.数组没有进行动态扩容,在 HashMap 中,扩容发生在

if (++size > threshold) {
    resize(); // 触发扩容
}
//size当前键值对数量。

//threshold扩容阈值 = capacity * loadFactor(如默认 16 * 0.75 = 12)

c.当哈希冲突严重(很多个值对应同一个key,使得链表过长,查找时间复杂度退化为 O(n))没有做处理,处理方法如使用红黑树, 红黑树是一种 自平衡的二叉搜索树(BST),满足以下规则:
节点是红色或黑色。根节点是黑色。所有叶子节点(NIL)是黑色。红色节点的子节点必须是黑色(不能有连续红色节点)。从任一节点到其叶子节点的路径包含相同数量的黑色节点(黑高平衡)

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值