哈希算法
-
什么是哈希算法
哈希算法不是一个特定的算法,而是一类算法的统称,也叫散列算法。
-
算法的作用
哈希算法是为了把任意一个数据,通过计算生成一个定长的key。一个数据只能与一个key对应,通过数据可以得出key值,不能通过key逆推出数据值。
比较官方的说法:f(data)=key,输入任意长度的data数据,经过哈希算法处理后输出一个定长的数据key。同时这个过程是不可逆的,无法由key逆推出data。
-
哈希表
如果是一个data数据集,经过哈希算法处理后得到key的数据集,然后将keys与原始数据进行一一映射就得到了一个哈希表。
一般来说哈希表M符合M[key]=data这种形式。
哈希表的好处是当原始数据较大时,我们可以用哈希算法处理得到定长的哈希值key,那么这个key相对原始数据要小得多。我们就可以用这个较小的数据集来做索引,达到快速查找的目的。
-
哈希冲突
因为输入数据不定长,而输出的哈希值却是固定长度的,这意味着哈希值是一个有限集合,而输入数据则可以是无穷多个。
那么建立一对一关系明显是不现实的。所以"碰撞"(不同的输入数据对应了相同的哈希值)是必然会发生的,所以一个成熟的哈希算法会有较好的抗冲突性。
所以在哈希表实现时候,也要考虑哈希冲突的情况。
-
常见哈希算法
密码上常用的MD5,SHA都是哈希算法,因为key的长度(相对大家的密码来说)较大所以碰撞空间较大,有比较好的抗碰撞性,所以常常用作密码校验。
HashMap实现原理
-
数组与链表
- 数组:占用连续存储空间,占用内存大,空间复杂度较高。但查找很容易,特点是查找容易,插入和删除困难
- 链表:存储方式是离散存储,空间复杂度低,但查找的时间复杂度较高,特点是查找麻烦,插入删除容易
-
哈希表
是一种综合了数组和链表的特点,做出的一种查找、插入、删除的复杂度都比较低的数据结构。
哈希表有很多中类型,我们看一种较为常见的类型——拉链型,这种数据结构可以理解为链表的数组
如上图所示,假设通过某个哈希算法,可以把数据转化为0-15中的数字,然后就可以把数据用如上方式存储。
其中数据对应的hash值代表数组的下标。当同一个hash值有多个数据时,可以在当前下标的链表后面继续添加数据,直到所有的数据归类完成。
采用哈希表后,数据的查找、删除、插入的平均时间复杂度会有显著的降低,空间的利用也更加高效。
-
HashMap原理简介
-
概述
HashMap也可以理解为一个hash表来实现的。
HashMap有个内部类
Entry
,其重要属性有key value next
,其中key/value用来保存对应的键值对,next作为链表的指针。然后内部所有的数据存储在
Entry[] table
中。 -
存数据
public V put(K key, V value) { if (key == null) return putForNullKey(value); //null总是放在数组的第一个链表中 int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); //遍历链表 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //如果key在链表中已存在,则替换为新value if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
首先会生成hash值,用来对应
Entry
数组下标,然后会遍历当前下标对应链表的所有数据,如果对应的key已经存在,则替换,否则在当前数据末尾添加数据。void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //参数e, 是Entry.next //如果size超过threshold,则扩充table大小。再散列 if (size++ >= threshold) resize(2 * table.length); }
当随着数据的不断增多,为了保证性能的优越性,会扩充
Entry
数组的长度。此时通过hash值转数组下标的方法也会改变,有兴趣的可以查看hashmap源码。 -
取数据
public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); //先定位到数组元素,再遍历该元素处的链表 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }
取数据对应的原理通过代码可以很好理解,就不专门介绍了。
-
总结
hashMap是具有超快查询速度的数据存储方式,查找的时间复杂度基本是O(1)级别,唯一的缺点可能就是需要进行一次额外的hash值计算。所以在日常使用中,对键值对的存储可以优先考虑hashMap。