C++ map和HashMap原理详解

一、Map成员

上面可以看到Map接口的几个实现方式。简要说明:

TreeMap是基于树(红黑树)的实现方式,即添加到一个有序列表,在O(log n)的复杂度内通过key值找到value,优点是空间要求低,但在时间上不如HashMapC++Map的实现就是基于这种方式

HashMap是基于HashCode的实现方式,在查找上要比TreeMap速度快,添加时也没有任何顺序,但空间复杂度高。C++ unordered_Map就是基于该种方式。

HashTableHashMap类似,只是HashMap是线程不安全的,HashTable是线程安全的,现在很少使用

ConcurrentHashMap也是线程安全的,但性能比HashTable好很多,HashTable是锁整个Map对象,而ConcurrentHashMap是锁Map的部分结构

二、HashMap详解

HashMap简称哈希表,下面介绍下主要思想和流程。

HashMap在添加值是需要给定两个参数,一个是key,一个是value。为了能很快的通过key值找到对应的value,因此有必要建立一个key值和内存指针的映射,举个简单的例子,如果说key值是int型,那么其实最简单的方式就是定义一个数组,以这个key值作为下标,value作为内存中的值。然而由于key值可能会很大,或者是string或着其他类型的值,因此就不能单纯的简单对应了,这时候就需要做一个转换。这个在Java和C#中是通过一个int HashCode()的函数实现的。具体的实现可能是通过地址、字符串或数字算出来的值,然后如果是自己定义的对象,则需要自己实现HashCode()和equal().

注意,hashcode的实现需要满足以下要求:

1、如果两个对象equals相等,那么这两个对象的HashCode一定也相同

2、如果两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置

 

好,那么在计算出hashcode之后再怎么做呢,由于hashcode算出来的值可能很大,定义一个大小能包含所有hashcode的数组显然是不合理的。在实际的实现是这样的,事先定义一个大小为2的幂次方的数组(稍后解释为什么是2的幂次方)。为了能保证所有的hashcode都能对应到数组的下标,可以采用hashcode对数组大小(一般称为bucket)取余的方式。而具体的实现就是:

static int indexFor(int h, int length) {  
    return h & (length-1);
}

通过按位与运算巧妙的求得了余数,并且很大程度上减少了运算效率。但由于可能会有多个key值对应同一个index,为了避免冲突,其实每个数组元素里存储的是链表结构。当添加函数检测到index对应的元素已经有值了以后,它就会将key值和value作为子节点添加到该index所在元素的尾部节点。如果检测到key值相同,则更新value。

当链表的长度大于8后,会自动转为红黑树,方便查找。如果hashMap里的元素越来越多,那么冲突的概率会越来越大,因此有必要即时的对数组长度扩容。当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。扩容的操作是这样的:

int capacity = 1;
    while (capacity < initialCapacity)  
        capacity <<= 1;

这个就表示,每次扩容都是在原有的基础上×2,这也就是为什么大小是2的幂次的原因。

没有更多推荐了,返回首页