Hash原理以及在java集合框架中的应用

2015年最后一天,写篇文章记录一下我对hash算法的理解以及其在java集合框架中的应用以及其他地方的应用的大概介绍,算是一个比较系统的总结吧。文章参考了网上一些大神的文章(在文章末尾我会把参考文章写上),但是网上的文章很多都没有一个应用场景,读起来虽然知道了原理但是还是用不出来,我会结合平时做项目中遇到的一些问题来说明一些集合框架的具体使用。
--致敬2015

Hash介绍

基本概念

Hash其实就是散列,就是把任意长度的输入,通过散列算法变成固定长度的输出,由于是不定到定长,所以这种变换是一种压缩映射,输出值的值域远远小于输入值的值域,所以不同的输入可能会有相同的输出,这就是Hash碰撞。

解决冲突

  1. 开放地址法:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。这种方法有一个通用的再散列函数形式:Hi=(H(key)+di)%m i=1,2,…,n,其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。
  2. 再hash法:当发生冲突时再次散列,如果依然冲突的话,可以变为多重散列或者结合其他解决冲突的方法使用。
  3. 链地址法:在发生冲突的地方,以链表的形式存储(HashMap就是这么做的)
  4. 建立公共溢出区:建立一个公共溢出区,将冲突的值放在溢出区。

什么是哈希表?

哈希表也叫散列表,是根据关键字而直接进行访问内存存储位置的数据结构。它通过把关键字通过散列函数映射到哈希表中的一个位置来访问记录,以加快查找的速度。存放记录的数组叫做散列表。哈希表就是一种依托于数组的数据结构,只不过增加了一些规则来在数组上存储元素和访问元素。

hash在java中的应用

java对象的equals和hashCode方法

java中Object类默认的equals方法和==一样,是比较两个对象的地址是否相等的,hashCode方法是返回对象的存储地址的(hashCode是一个native方法哦,是通过JNI用其他语言实现的)。

但是一般情况下,我们并不需要去比较两个对象的地址(需要的时候我们完全可以用==),所以我们可以选择重写equals方法来比较对象的内容,在我们的业务逻辑里面,对于一个人物实体类,只要身份证属性相同,就可以认为这两个人相同,所以我们选择覆盖equals方法来比较身份证属性。

重写equals方法后,一定要记得重写hashCode方法,因为如果该对象如果出现在了使用了Hash表结构的java集合框架中的话,会首先比较该对象的hashCode方法,如果相同,再比较equals方法,如果相同,则覆盖之前的对象,不同,则用链表的方式存储这两个对象。试想一下,对于一个身份证信息相同的对象,如果只重写了equals方法,那么这两个在业务逻辑上应该被判断为相同的对象就会被重复存储。

java中对于equals和hashCode有两个约定:

  1. 当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true
  2. 当obj1.hashCode() == obj2.hashCode()为false时,obj1.equals(obj2)必须为false

也就是说,hashcode相等,equals可能不相等,但是equals相等代表这两个对象是是一个对象,所以hashCode必须相等。

HashMap

HashMap使用数组加链表的方式实现的,当存在hashCode相同但是equals返回false的两个对象时,会使用链地址法来解决冲突。

HashMap中我们最常用的就是put(K, V)和get(K)。我们都知道,HashMap的K值是唯一的,那如何保证唯一性呢?我们首先想到的是用equals比较,没错,这样可以实现,但随着内部元素的增多,put和get的效率将越来越低,这里的时间复杂度是O(n),假如有1000个元素,put时需要比较1000次。

实际上,HashMap很少会用到equals方法,因为HashMap通过一个哈希表管理所有元素,当我们调用put存值时,HashMap首先会调用K的hashCode方法,获取哈希码,通过哈希码快速找到某个存放位置,这个位置可以被称之为bucketIndex,通过上面所述hashCode的协定可以知道,如果hashCode不同,equals一定为false,如果hashCode相同,equals不一定为true。

所以理论上,hashCode可能存在冲突的情况,有个专业名词叫碰撞,当碰撞发生时,计算出的bucketIndex也是相同的,这时会取到bucketIndex位置已存储的元素,最终通过equals来比较,equals方法就是哈希码碰撞时才会执行的方法,所以前面说HashMap很少会用到equals。HashMap通过hashCode和equals最终判断出K是否已存在,如果hashCode和equals相等,则使用新V值替换旧V值,(若hashCode相等当equals为false,则链式存储存储解决冲突)并返回旧V值,如果不存在 ,则存放新的键值对<K, V>到bucketIndex位置。
存放元素的过程如下图所示:

hashmap存放对象遇到的几种情况

HashMap去除一个元素就很简单了,如下代码所示

public V get(Object key) {
        // 若为null,调用getForNullKey方法返回相对应的value
        if (key == null)
            return getForNullKey();
        // 根据该 key 的 hashCode 值计算它的 hash 码
        int hash = hash(key.hashCode());
        // 取出 table 数组中指定索引处的值
        for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
            Object k;
            //若搜索的key与查找的key相同,则返回相对应的value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
    }

需要注意的是在HashMap中,不是直接用的对象的hashcode作为对象存储地址的,而是再次hash,这么做可以防止重写hashCode方法以后使存储地址非法。内部hash实现如下

static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

(>>>是无符号右移)

HashMap内部定义了一个Entity泛型对象来存储每个键值对信息。如下所示:

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
        .......
    }

Entry为HashMap的内部类,它包含了键key、值value、下一个节点next,以及hash值,这是非常重要的,正是由于Entry才构成了table数组的项为链表。

HashSet

知道了HashMap以后,HashSet其实就是HashMap的键的存储。所以就不赘述啦~~

hash在其他地方的应用

  1. MD5加密是将任意长度的字符串散列成32位的定长字符串(由小写字母和数字组成)。
  2. SSH中对证书信息进行散列获取证书信息。
  3. SHA1加密算法也是一种运用散列进行加密的算法。

参考资料

  1. hash表总结
  2. 维基百科hash表定义
  3. hashCode和equals详解
  4. hashmap深度解析
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容