http://www.cnblogs.com/mumuxinfei/p/4441826.html

前言:
  我以前在百度的mentor, 在面试时特喜欢考察哈希表. 那时的我满是疑惑和不解, 觉得这东西很基础, 不就的分桶理念(以空间换时间)和散列函数选择吗? 最多再考察点冲突解决方案. 为何不考察类似跳跃表, LSM树等高级数据结构呢?
  随着工程实践的积累, 慢慢发现了自己当初的肤浅. 面试的切入点, 最好是大家所熟悉的, 但又能从中深度挖掘/剖析和具有区分度的. 
  本文结合自己的工程实践, 来谈谈对哈希表的优化和实践的一些理解.

基础篇:
  哈希表由一定大小的连续桶(bucket)构成, 借助散列函数映射到具体某个桶上. 当多个key/value对聚集到同一桶时, 会演化构成一个链表.

哈希表结构有两个重要的参数, 容量大小(Capacity)和负载因子(LoadFactor). 两者的乘积 Capacity * LoadFactor决定了哈希表rehash的触发条件.

以空间换时间为核心思想, 确保其数据结构的访问时间控制在O(1).
  哈希表隐藏了内部细节, 而对外的使用则非常的简单. 只需定义key的hash函数compare函数即可.
  以Java为例, 其把默认的hash函数和equals函数置于顶层的Object基类中.

1
2
3
4
5
6
class Object {
    public native int hashCode();
    public boolean equals(Object obj) {
        return (this == obj);
    }
}

所有的子类, 需要重载hashCode和equals就能方便的使用哈希表.

进阶篇:
  hash函数的选择需保证一定散列度, 这样才能充分利用空间. 事实上哈希表的使用者, 往往关注hash函数的快速计算和高散列度, 却忽视了其潜在的风险和危机.

1). hash碰撞攻击
  前段时间, php爆出hash碰撞的攻击漏洞. 其攻击原理, 简单可概括为: 特定的大量key组合, 让哈希表退化为链表访问, 进而拖慢处理速度, 请求堆积, 最终演变为拒绝服务状态.

具体可参考博文: PHP哈希表碰撞攻击原理
  大致的思路是利用php哈希表大小为2的幂次, 索引位置计算由 hash(key) % size(bucket) 转变为 hash(key) & (1^n - 1).
  

黑客(hacker)知晓time33算法和hash函数, 可以构造/收集特定的key系列, 使得其hash(key)为同一桶索引值. 通过post请求附带, 导致php构造超长链的哈希表.
  其实如果能理解hash碰撞攻击的原理, 说明其对hash的冲突处理和哈希表本身的数据结构模型有了较深的理解了.

2). 分段锁机制
  如果加锁是不可避免的选择, 那能否减少锁冲突的概率呢?

答案是肯定的, 不同桶之间的key/value操作彼此互不影响. 在此前提下, 对哈希桶进行分段加锁. 这样全局锁就退化为多个分段锁, 而锁冲突的概率由于分区的原因, 降低至1/N (N为分段锁个数).

Java并发类中的ConcurrentHashMap也是采用类似的思想来实现, 不过比这复杂多了.

难度篇:
  哈希表单key的操作复杂度为O(1), 性能异常优异. 但需要对哈希表进行迭代遍历其所有元素时, 其性能就非常的差. 究其原因是各个key/value对分散在各个桶中, 彼此并无关联. 元素遍历转化为对哈希桶的全扫描.
  那如果存在这样的需求, 既要保证O(1)的单key操作时间复杂度, 又要让迭代遍历的复杂度为O(n) (n为哈希表的key/value对个数, 不是桶个数), 那如何去实现呢?
  1). LinkedHashMap&LRU缓存
  是否存在一个复合数据结构, 既有Hashmap的特性, 又具备DoubleLinkedList线性遍历的特征?
  答案是肯定的, 该复合结构就是LinkedHashmap.

注: 依次添加key1, key2, ..., key6, 其按插入顺序构成一个双向列表.
  一图胜千言, 该图很形象的描述了LinkedHashMap的构成. 可以这么认为: 每个hash entry的结构的基础上, 添加prev和next成员指针用于维护双向列表. 实现就这么简单.
  在工程实践中, 往往采用LinkedHashMap的变体来实现带LRU机制的Cache.
  简单描述其操作流程:
  (1). 查询/添加key, 则把该key/value对搁置于LRU队列的末尾
  (2). 若key/value对个数超过阈值时, 则选择把LRU队列的首元素淘汰掉.
  模拟key5元素被查询访问, 成为最近的热点, 则内部的链接模型状态转变如下:

注: key5被访问后, 内部双向队列发生变动, 可以理解为删除key5, 然后再添加key5至末尾.
  JAVA实现带LRU机制的Cache非常的简单, 用如下代码片段描述下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> { 
    
    private int capacity = 1024
 
    public LRULinkedHashMap(int initialCapacity, float loadFactor, int lruCapacity) {
     // access order=> true:访问顺序, false:插入顺序
        super(initialCapacity, loadFactor, true); 
        this.capacity = lruCapacity; 
    
 
    @Override 
    protected boolean removeEldestEntry(Entry<K, V> eldest) { 
        if(size() > capacity) { 
            return true
        
        return false
    
}

注: 需要注意access order为true, 表示按访问顺序维护. 使用Java编程的孩子真幸福.

  当哈希表中的元素数量超过预定的阈值时, 就会触发rehash过程. 但是若此时的hash表已然很大, rehash的完整过程会阻塞服务很长时间. 这对高可用高响应的服务是不可想象的灾难.
  面对这种情况, 要么避免大数据量的rehash出现, 预先对数据规模进行有效评估. 要么就继续优化哈希的rehash过程.

  2). 0/1切换和渐进式rehash
  redis的设计者给出了一个很好的解决方案, 就是0/1切换hash表+渐进式rehash.
  其渐进的rehash把整个迁移过程拆分为多个细粒度的子过程, 同时0/1切换的hash表共存.
  redis的rehash过程分两种方式:
  • lazy rehashing: 在对dict操作的时候附带执行一个slot的rehash
  • active rehashing:定时做个小时间片的rehash

总结:
  哈希表作为常用的数据结构, 被人所熟知. 但对其进一步的理解和挖掘, 需要真正的工程实践积累. 洗尽铅华始见真.

hash表系列(转)的更多相关文章

  1. leetcode的Hot100系列--347. 前 K 个高频元素--hash表+直接选择排序

    这个看着应该是使用堆排序,但我图了一个简单,所以就简单hash表加选择排序来做了. 使用结构体: typedef struct node { struct node *pNext; int value ...

  2. 十一、从头到尾彻底解析Hash 表算法

    在研究MonetDB时深入的学习了hash算法,看了作者的文章很有感触,所以转发,希望能够使更多人受益! 十一.从头到尾彻底解析Hash 表算法 作者:July.wuliming.pkuoliver  ...

  3. 索引,B+ tree,动态hash表

    数据库课索引部分的学习笔记. 教材: Database System: The Complete Book, Chapter 15 Database System Implementation, Ch ...

  4. [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (5) 嵌入式hash表

    [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (5) 嵌入式hash表 目录 [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (5) 嵌入式hash表 ...

  5. [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (6) --- Distributed hash表

    [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (6) --- Distributed hash表 目录 [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- ...

  6. [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器 --(9)--- Local hash表

    [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器 --(9)--- Local hash表 目录 [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器 --(9)--- ...

  7. hash表长度优化证明

    hash表冲突的解决方法一般有两个方向: 一个是倾向于空间换时间,使用向量加链表可以最大程度的在节省空间的前提下解决冲突. 另外一个倾向于时间换空间,下面是关于这种思路的一种合适表长度的证明过程: 这 ...

  8. 6.数组和Hash表

    当显示多条结果时,存储在变量中非常智能,变量类型会自动转换为一个数组. 在下面的例子中,使用GetType()可以看到$a变量已经不是我们常见的string或int类型,而是Object类型,使用-i ...

  9. PHP数组/Hash表的实现/操作、PHP变量内核实现、PHP常量内核实现 - [ PHP内核学习 ]

    catalogue . PHP Hash表 . PHP数组定义 . PHP变量实现 . PHP常量实现 1. PHP Hash表 0x1: 基本概念 哈希表在实践中使用的非常广泛,例如编译器通常会维护 ...

随机推荐

  1. python读取csv转换为dataframe

    前言: 由于在处理结构性数据的时候经常会读取本地形如:.xls.xlsx.csv等的数据.所以今天就花了点时间来总结一下利用python读取csv数据并且转换为dataframe的数据框架.话不多说, ...

  2. 关于syx的npy

    请认准官方女友----- STL 任何人在不得syx同意下不能传播其它谣言

  3. arm linux 移植 udhcp 与 使用

    背景 在一些网络环境下,需要静态IP不够现实,需要使用DHCP进行自动获取IP地址. udhcpc是一个面向嵌入式系统的非常小的DHCP客户端,字母的缩写微μ- DHCP -客户端client(μDH ...

  4. 2.12 学习总结 之 表单校验插件validate

    一.说在前面 昨天 学习了ajax的相关知识 今天 学习表单校验插件validate, 并使用ajax 自定义校验规则 二.validate 插件 1.网络上有许多成熟的插件共使用者参考,插件就是将j ...

  5. git使用问题二删除远程仓库文件,本地保留不动

    git rm --cached filename/-r directory git commit "xxxx" git push

  6. QT5安装

    Windows+Qt5.3.1+VS2013安装教程 https://blog.csdn.net/two_ye/article/details/96109876 (已成功)windows下,VS201 ...

  7. BubbleSort

    看见了一些乱乱的东西,就想着整理一下,基础的冒泡排序 //BubbleSort #include<iostream> using namespace std; void BubbleSor ...

  8. 转载-select、poll、epoll区别总结

    I/O多路复用——epoll函数 select.poll.epoll区别总结 一.select.poll.epoll区别总结   1 本质上都是同步I/O 三者都是I/O复用,本质上都属于同步I/O. ...

  9. leetcode617 Merge Two Binary Trees

    """ Given two binary trees and imagine that when you put one of them to cover the oth ...

  10. 针对Quartus IP Core的MIF文件格式小记

    Quartus里面的ROM IP核进行内容分配,需要在Wizard里面指定.mif文件. 本文有关细节和详细说明,请参照Quartus Prime 帮助文档v15.1 一个常见的mif文件如下所示: ...