JAVA 实现 - 哈希表

哈希表

v1

package com.datastructure.hashtable;

public class HashTable {

    //节点类
    static class Entry {
        int hash;
        Object key;
        Object value;
        Entry next;

        public Entry(int hash, Object key, Object value) {
            this.hash = hash;
            this.key = key;
            this.value = value;
        }
    }

    Entry[] table = new Entry[16]; //数组存放链表第一个元素
    int size = 0;

    //根据 hash 获取 value
    Object get(int hash, Object key, Object value) {
        int inx = hash & (table.length - 1);
        if (table[inx] == null) {
            return null;
        }
        //遍历链表
        Entry p = table[inx];
        while (p != null) {
            if (p.key.equals(key)) {
                return p.value;
            }
            p = p.next;
        }
        return null;
    }


    /*
     1. 向hash中存入key value, 如果key 重复,则更新为value
        hash 决定存在数组中的哪个位置:hash % 数组长度 得到的值即为落入数组的位置

     2. 求模运算没有位运算效率高,可以替换为位运算
        - 前提: 数组长度是2的n次方
        - hash % 数组  等价于 hash & *(数组长度 - 1)
     */
    void put(int hash, Object key, Object value) {
        int idx = hash & (table.length - 1);
        Entry p = table[idx];
        if (p == null) {         //1. 在数组中找到空位直接放入
            Entry entry = new Entry(hash, key, value);
            table[idx] = entry;
            size++;
        } else {
            while (true) { //2. 数组中没有找到空位,遍历链表,如果key 相等更新,如果没找到相等的key 就新增
                if (p.key.equals(key)) {
                    //更新
                    p.value = value;
                    return;
                }

                if (p.next == null) {
                    break;
                }
                p = p.next;
            }
            p.next = new Entry(hash, key, value);
            size++;
        }
    }

    //移除元素
    Object remove(int hash, Object key) {
        return null;
    }


    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "bca";

        System.out.println("str1 => String.hashCode:" + str1.hashCode());
        System.out.println("str2 => String.hashCode:" + str2.hashCode());
        int hash = 0;
        for (int i = 0; i < str1.length(); i++) {
            char c = str1.charAt(i);
            System.out.println((int) c);
            hash = (hash << 5) - hash + c;  //loop1: 0+a, loop2: (0+a)*10+b, lopp3: ((0+a)*10+b) * 10 + c <=> a*100 + b*10 +c
        }
        System.out.println(hash);
    }
}

测试类:

package com.datastructure.hashtable;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class HashTableTest {


    @Test
    public void test1() {
        HashTable table = new HashTable();
        table.put(1, "zhangsan", "23"); // 1 % 16 = 1
        table.put(17, "lisi", "28");  // 17 % 16 = 1
        table.put(2, "wangwu", "29"); // 2 % 16 = 2
//        assertEquals()
        assertEquals(3, table.size);
        assertEquals("23", table.table[1].value);
        assertEquals("28", table.table[1].next.value);
        assertEquals("29", table.table[2].value);


        //更新
        table.put(1, "zhangsan", "25");
        table.put(17, "lisi", "27");
        assertEquals("25", table.table[1].value);
        assertEquals("27", table.table[1].next.value);
    }
}

求莫与按位与等价:

什么是哈希算法

hash 算法是一种将任意长度的数据通过一个算法,变成固定长度数据的过程,这个固定长度的数据就是hash值。hash算法可以将任意大小的数据压缩到固定大小的值。常见的hash算法有MD5、SHA1、SHA256、SHA512、CRC32等。其中,MD5和SHA系列算法是最常用的hash算法。这些算法在计算hash值时,都考虑了原始数据的每一个字节,一旦改动原始数据的任何一个字节,所得到的hash值都会有明显的不同。因此,hash算法被广泛应用于数据完整性校验和加密等方面。

hash 算法是将任意对象,分配一个编号的过程,其中编号是一个有限范围内的数据(如int范围内)

哈希算法 String.hashCode

public static void main(String[] args) {
    String  str1 = "abc";
    String str2 = "bca";

    int hash = 0;
    for (int i = 0; i < str2.length(); i++) {
        char c = str1.charAt(i);
        System.out.println((int)c);

        hash += c;
    }
    System.out.println(hash);
}

以上hash值是将每个字符的值相加得到一个hashcode,但问题是"abc" 与 "cba" 的hashcode 就会一致。如何解决?
可以给前两位字符都乘以一个权重,如下
a * 100 + b * 10 + c
b * 100 + c * 10 + a

实现如下:

public static void main(String[] args) {
    String str1 = "abc";
    String str2 = "bca";

    System.out.println("str1 => String.hashCode:" + str1.hashCode());
    System.out.println("str2 => String.hashCode:" + str2.hashCode());
    int hash = 0;
    for (int i = 0; i < str1.length(); i++) {
        char c = str1.charAt(i);
        System.out.println((int) c); 
        hash = hash * 10 + c;  //loop1: 0+a, loop2: (0+a)*10+b, lopp3: ((0+a)*10+b) * 10 + c <=> a*100 + b*10 +c
    }
    System.out.println(hash);
}

还可以进一步优化, 将 hash * 10 改为 乘以一个质数 如31 ,但是为什么是质数那?

public static void main(String[] args) {
    String str1 = "abc";
    String str2 = "bca";

    System.out.println("str1 => String.hashCode:" + str1.hashCode());
    System.out.println("str2 => String.hashCode:" + str2.hashCode());
    int hash = 0;
    for (int i = 0; i < str1.length(); i++) {
        char c = str1.charAt(i);
        System.out.println((int) c);
        hash = hash * 31 + c;  //loop1: 0+a, loop2: (0+a)*10+b, lopp3: ((0+a)*10+b) * 10 + c <=> a*100 + b*10 +c
    }
    System.out.println(hash);
}

输出:

str1 => String.hashCode:96354
str2 => String.hashCode:97344
97
98
99
96354

此时输出的hashcode 就是 String 类的实现,还可以进一步优化: 将乘法运算转换为 左移位运算,因为位运算的效率比乘法高,32为 2 的 5次方,因此可以左移5位

public static void main(String[] args) {
    String str1 = "abc";
    String str2 = "bca";

    System.out.println("str1 => String.hashCode:" + str1.hashCode());
    System.out.println("str2 => String.hashCode:" + str2.hashCode());
    int hash = 0;
    for (int i = 0; i < str1.length(); i++) {
        char c = str1.charAt(i);
        System.out.println((int) c);
        hash = (hash << 5) - hash + c;  //loop1: 0+a, loop2: (0+a)*10+b, lopp3: ((0+a)*10+b) * 10 + c <=> a*100 + b*10 +c
    }
    System.out.println(hash);
}
posted @ 2024-07-28 14:41  chuangzhou  阅读(18)  评论(0编辑  收藏  举报