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. 2. 引用计数法(Reference Counting)

    1960年,George E. Collins 在论文中发布了引用计数的GC算法. 引用计数法意如了一个概念,那就是"计数器",计数器表示的是对象的人气指数, 也就是有多少程序引用 ...

  2. 关于Burp Suite Intruder 的四种攻击方式

    以下面这一段参数为例,被§§包围的部分为需要破解的部分: user=§ss§&password=§zxcv§&imageField.x=17&imageField.y=1 (1 ...

  3. qrcode在手机上不显示的问题

    可以试试以下解决方案: 1.修改qrcode.min.js:里的function n()红线区域替换成这个 , 原因是这样子才能支持安卓机显示.

  4. 二 Hibernate 改写学生管理系统的业务功能

    public class StudentDaoImpl implements StudentDao { @Override /** * 查询所有学生 * * @throws SQLException ...

  5. 【QSBOJ】字符串编辑

    题目链接:https://bbs.csdn.net/topics/390289884?page=1 AC代码: #include<bits/stdc++.h> using namespac ...

  6. SciPy 信号处理

    章节 SciPy 介绍 SciPy 安装 SciPy 基础功能 SciPy 特殊函数 SciPy k均值聚类 SciPy 常量 SciPy fftpack(傅里叶变换) SciPy 积分 SciPy ...

  7. vs2010编译C++ 结构体

    //结构体的测试// CTest.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> usi ...

  8. POJ 3273:Monthly Expense 二分好题啊啊啊啊啊啊

    Monthly Expense Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 19207   Accepted: 7630 ...

  9. 旧iPhone遭禁,会让苹果产业链迎来新转机吗?

    过去几个月,苹果的日子并不好过,先是新iPhone定价过高导致销售疲软,股价连续下跌,万亿市值失守,被微软和亚马逊超越:手机销量上则被华为赶超,整个iPhone产业链都有点儿"哀鸿遍野&qu ...

  10. Thinkcmf任意漏洞包含漏洞分析复现

    简介 ThinkCMF是一款基于PHP+MYSQL开发的中文内容管理框架,底层采用ThinkPHP3.2.3构建.ThinkCMF提出灵活的应用机制,框架自身提供基础的管理功能,而开发者可以根据自身的 ...