hash与HashMap详解

本文详细介绍了哈希算法的基本概念,包括其特点、用途以及碰撞性的解决方法。深入探讨了Java中HashMap的工作原理,包括其设计、源码分析及线程安全性问题。同时,文章还提供了手写模仿HashMap的示例代码,帮助读者更好地理解其内部机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

hash的介绍

Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

优秀的哈希算法特点

效率高
不可逆性
敏感性
低碰撞性
速度、空间、碰撞性进行取舍,不同的hash算法侧重点不同

java中的hash

  1. hashCode:代表一个对象的签名,两个对象相等则hashCode一定要相等,但是,两个hashCode相等,对应的两个对象则不一定相等(碰撞),在数学中叫“必要条件”。
  2. HashMap:侧重速度。
  3. Object.hashCode:直接获取内存地址
  4. Integer.hashCode:直接返回的intValue
  5. String.hashCode:根据字符串内容生成hashCode,字符串内容一样则hashCode也相同,String对象会保存第一次计算出的哈希值,之后该对象用到的哈希值都是这个保存好的哈希值,不会再次计算。

其他场景中的Hash算法

  1. MD4,MD5
  2. SHA(Secure Hash Algorithm)[SHA-1, SHA-224, SHA-256, SHA-384, SHA-512]

哈希算法的用途

  1. 哈希查找,哈希表
  2. 秒传(百度网盘传电影,如果电影的哈希值在数据库中已经存在了,就直接引用)
  3. HashMap
  4. 加解密,MD5,SHA
  5. Git文件提交
  6. 区块链

碰撞性及解决方法

  1. MD5碰撞案例
  2. SHA1碰撞案例
  3. 再散列函数法:算出一个对象的哈希值,发现已经被其他对象算出来,这时候就再次散列一下,直到算出来的值以前没有存在过,空间不足时用链地址法。
  4. 链地址法
哈希值相同加入链表
e
b
f
a
c
d

手写模仿HashMap主要功能

public class MyHashMap<K, V> {
    
    private Entry<K, V>[] table;
    //容量
    private static final Integer CAPCITY = 1 << 4; //16
    //存入个数
    private int size;
    
    public void put(K k, V v) {
    	//HashMap是懒加载,table在第一次存入数据时才初始化。
        if (null == table) {
            inflate();
        }
        //计算key的哈希值
        int hashCode = hash(k);
        //计算下标
        int index = indexFor(hashCode);
        for (Entry<K, V> entry = table[index]; entry != null; entry = entry.next) {
            if (entry.key.equals(k)) {
                entry.value = v;
                return;
            }
        }
        addEntry(k, v, index);
    }

    private void addEntry(K k, V v, int index) {
    	//如果是table[index]已经存在(产生碰撞),在原来的table[index]头上添加新的对象,新对象的next参数指向原对象,这里用的是链地址法。
        Entry<K, V> newEntry = new Entry<>(k, v, table[index]);
        table[index] = newEntry;
        size++;
    }

    private int indexFor(int hashCode) {
        return hashCode % table.length;
    }

    private int hash(K k) {
        return k.hashCode();
    }

    private void inflate() {
        table = new Entry[CAPCITY];
    }

    public V get(K k) {
        int hashCode = hash(k);
        int index = indexFor(hashCode);

        for (Entry<K, V> entry = table[index]; entry != null; entry = entry.next) {
            if (entry.key.equals(k)) {
                return entry.value;
            }
        }
        return null;
    }

    class Entry<K, V> {
        public K key;
        public V value;
        //产生碰撞时用的链地址法往链表下面加对象,指向链表的下一个对象,或者往链表上面加。
        public Entry<K, V> next;

        public Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public Entry(K key, V value, Entry<K, V> next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }
    }

    public static void main(String[] args) {
        MyHashMap<String, String> myHashMap = new MyHashMap<>();
        myHashMap.put("1", "1v");
        myHashMap.put("2", "2v");
        myHashMap.put("3", "3v");
        System.out.println(myHashMap.get("3"));
    }
}

研究HashMap源码 1.7

1.7源码解读:https://blog.youkuaiyun.com/carson_ho/article/details/79373026

HashMap是线程不安全的

扩容resize的时候会出现死循环 http://www.importnew.com/22011.html
fail-fast快速失败法,比如在遍历时元素删除,会抛出ConcurrentHashMap

		HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("1", "1");
        hashMap.put("2", "2");

        //modCount=3
        for (String key : hashMap.keySet()) {
            if (key.equals("2")) {
                hashMap.remove(key);
            }
        }
        System.out.println(hashMap);

解决方法1:在迭代器中删除

		HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("1", "1");
        hashMap.put("2", "2");

        //modCount=3
        /*for (String key : hashMap.keySet()) {
            if (key.equals("2")) {
                hashMap.remove(key);
            }
        }*/
        
        Iterator<String> iterator = hashMap.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            if (key.equals("1")) {
                iterator.remove();
            }
        }
        
        System.out.println(hashMap);

解决方法2:使用ConcurrentHashMap

 		ConcurrentHashMap<String, String> hashMap = new ConcurrentHashMap<>();
        hashMap.put("1", "1");
        hashMap.put("2", "2");

        //modCount=3
        for (String key : hashMap.keySet()) {
            if (key.equals("2")) {
                hashMap.remove(key);
            }
        }

        /*Iterator<String> iterator = hashMap.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            if (key.equals("1")) {
                iterator.remove();
            }
        }*/

        System.out.println(hashMap);

为什么要用2的次方数作为数组大小?

1、新老索引可以尽可能保持一致,大大减少了之前已经散列良好的老数组的数据位置重新调换。
2、低位全是1,有更好的散列性。

为什么重写equals的时候要重写hashCode方法?

public class Equals {

    public static void main(String[] args) {
        Person person1 = new Person(1, "gakki");
        Person person2 = new Person(1, "gakki");
        System.out.println(person1.equals(person2));
        HashMap<Person, String> hashMap = new HashMap<>();
        hashMap.put(person1, "可爱");
        System.out.println(hashMap.get(person1));//可爱
        System.out.println(hashMap.get(person2));//null
        //HashMap中下标是通过key的哈希计算出来的,person1和person2的哈希值不一样,所以算出的下标不一样,为了使下标一样,必须重写hashCode方法
    }

    //目的是id一样,person就一样
    static class Person{
        private int id;
        private String name;

        public Person(int id, String name) {
            this.id = id;
            this.name = name;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Person person = (Person) o;
            return id == person.id;
        }
    
        //应该重写hashCode
        /*@Override
        public int hashCode() {
            return Objects.hash(id);
        }*/
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值