(1)为什么要学习hashmap?
hashmap的增删改查时间复杂度都能低至O(1)
(2) hashmap原理
hashmap通过数组和链表,实现对元素的存储,从而可以高速的访问元素值
(4)什么是哈希表
哈希表也叫散列表,哈希表是一种数据结构,提供了快速的插入操作和查找操作,
无论哈希表有多少条数据,他的增删改查的时间复杂度都是O(1),但是哈希表也有缺点,他是基于数组实现的,所以他的扩容效率很慢,
哈希表采用的是一种转换思想,其中一个重要的概念就是如何将键或者关键字
转换为数组下标,这个过程由哈希函数完成,并不是所有的键或者关键字都需要转换,对于int类型的键,则可以直接使用
(5)什么是哈希函数?
哈希函数的作用是帮我们把非int的「键」或者「关键字」转化成int,可以用来做数组的下标。比如我们上面说的将学生的姓名作为「键」或者「关键字」,这是就需要哈希函数来完成。
ex:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
「HashMap」中利用了「hashCode」来完成这个转换。哈希函数不管怎么实现,都应该满足下面三个基本条件:
- 散列函数计算得到的散列值是一个非负整数
- 如果 key1 = key2,那 hash(key1) == hash(key2)
- 如果 key1 ≠ key2,那 hash(key1) ≠ hash(key2)
结论:任意一个键值通过哈希函数,有且生成唯一的一个index值,并且大于或等于0 ,但是实际上第三条很难保证,因为我们是把最后生成的索引值放在一个较小的数组空间,在这种情况下,很容易出现哈希冲突;
(6)哈希冲突
哈希冲突,不同的键值通过哈希函数生成了相同的索引值
常用的解决哈希冲突的方法:
- 开放地址法
1、线性探测法:当发生冲突时,就顺序查看下一个存储位置,如果写一个位置还有冲突,继续往后找,知道最后一个位置还是冲突的话,就从头开始找。
2、平方探测法:平方探测法的规定是以1^2,-1^2, 2^2, -2^2,...,探测新的存储位置是否能存储元素。
3、再散列法:利用两个散列函数,当通过第一个散列函数得到关键字的存储地址发生重冲突时,再利用第二个散列函数计算出地址增量,地址计算方式如下:Hi = (Hash(key)+ i*Hash2(key) % p;
4、伪随机数法: 当发生地址冲突时,加入一个随机数作为地址增量寻找新的存储地址,地址计算方式如下 Hi = (Hash(key)+ di) p;
- 线性拉链法
将通过哈希函数生成相同索引值的元素连成一个单链表,m个索引值就设m个单链表,然后用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构。
拉链结构如下:
(7)C++ hashmap开发
1、导入头文件 #include<unordered_map>
2、哈希表的声明和初始化
unordered_map var_name;
unordered_map hmap{ {1,10}, {2, 20}, {3, 30}};
unordered_map hmap{ {{1,10}, {2, 20}, {3, 30}}, 3};
3、添加元素
hmap[4] = 40;
hmap[5] = 50;
hmap.insert({6,60}); //insert可以避免覆盖问题,同一个键值只能插入一次,第二次无效
unordered_map hmap2(hmap);
4、常用函数
- begin()函数, 该函数返回有一个指向哈希表开始位置的迭代器
- end()函数,指向哈希表结尾位置的下一个元素的迭代器
- cbegin()函数和cend()函数:这两个函数和begin()函数和 end()函数功能相同,唯一的区别是cbegin()和cend()函数是面向不可变的哈希表,也就是const 声明的哈希表。
- empty()函数:判断哈希表是否为空,空则返回true,非空返回false
- size()函数: 返回哈希表的大小
- erase()函数:删除某个位置的元素,或者删除某个位置到某个位置这一范围的元素,或者传入key值,删除键值对
- at()函数:根据key查找哈希表中的元素对应的值
- clear()函数:清空哈希表中的元素
- find()函数,以key作为参数寻找哈希表中的元素,如果哈希表中存在该key值,则返回该位置上的迭代器,否则返回end迭代器。
- bucket()函数,以key寻找哈希表中该元素储存的bucket编号(unordered_map的源码是基于拉链式的哈希表,所以是通过一个bucket存储元素)
- bucket_count()函数:该函数返回哈希表中存在的存储桶总数(一个存储桶可以用来存放多个元素,也可以不存放元素,一般bucket的个数大于等于元素个数)
- count()函数: 统计某个key值对应的元素个数,因为unordered_map不允许重复元素,所以count()函数返回的结果是0或者1
- 哈希表的遍历,通过迭代器遍历。
(7)hashmap是线程不安全的,因为它的操作函数都没有加锁,多线程操作时容易抛出异常;为了解决这个问题,推出了concurrenthashmap, 他是线程安全的hashmap,专门用于多线程环境。相比于hashtable在put方法上加上锁,保证插入时阻塞其他线程的插入操作,concurrenthashmap是通过原子操作和局部加锁的方法保证了多线程的线程安全,尽可能减少了性能损耗。