为什么哈希表是无序的? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
balabalaXMX
V2EX    C++

为什么哈希表是无序的?

  •  
  •   balabalaXMX 2023-01-04 20:29:40 +08:00 5267 次点击
    这是一个创建于 1085 天前的主题,其中的信息可能已经有所发展或是发生改变。

    C++ STL 里说 unorderd_map 遍历是无序的,意思是说每次遍历它返回的元素顺序不一样?还是说它返回的顺序和输入顺序不一样?还是两者都有?

    第一个问题我的理解是因为 unorderd_map 会随着 map 元素增加或者减少做 rehash ,也就是会调整 mod 的值或者桶的大小,所以元素之间的相对顺序会改变,但是如果不对 map 做增加或者删除操作的话,是不是每次遍历的顺序就是一样了?

    第二个问题我的理解是这个遍历可能是按照 key 数组的下标的顺序来返回的,那么原始 key 值经过了 hash后存入 key 数组的下标是不确定的,所以这个输入顺序和输出顺序完全没有关系。

    这样理解对吗?

    17 条回复    2023-01-06 09:27:10 +08:00
    mind3x
        1
    mind3x  
       2023-01-04 21:04:14 +08:00   2
    一般是指不保证任何特定的枚举顺序,例如(但不限于)枚举顺序与插入顺序无关。

    虽然就常见实现而言,如果没有元素增删,大概率每次遍历顺序会一样,但使用者不应该依赖这种假设。Golang 更进一步,直接把枚举顺序搞成了随机的。
    lovelylain
        2
    lovelylain  
       2023-01-04 21:12:42 +08:00 via Android
    印象中有一门语言为了让人记住哈希表是无序的,故意在枚举时打乱顺序,即使元素无变化,每次枚举顺序也不一样。
    viruscamp
        3
    viruscamp  
       2023-01-04 21:21:57 +08:00
    无序的意思是不保证每次读取顺序相同。
    请在编程时当作: 每次遍历它返回的元素顺序不一样.
    实际代码中基本不会遍历 unorderd_map, 如果需要遍历(比如保存到文件), 最好是插入 vec 然后排序 最后再遍历.

    实际上,对于同一个程序同一次运行, 插入 a, b, c 首次遍历得到 c,a,b , 那么再重复多次也是 c,a,b.
    只要编译器从 vc 换到 gcc , 或者 debug 改 release , 或者优化 O2 改 O3 等等, 很可能 插入 a, b, c 遍历得就变成 b,a,c 之类.
    thinkershare
        4
    thinkershare  
       2023-01-04 21:27:38 +08:00   2
    不要依赖于于实现,依赖于规范,规范保证的东西不会变化,其它实现的行为具有不确定性。
    BBCCBB
        5
    BBCCBB  
       2023-01-04 21:56:58 +08:00
    这个依赖于实现,

    map 也是可以实现成有序的, 但在性能上可能就不如无序的??

    比如 java 里有 linkedhashMap 可以保证按插入顺序, 但性能并不如 HashMap 好..



    key 在数组里下标不确定这个问题, 可以看 python3.5 后的 dict 实现..

    https://www.kingname.info/2019/07/13/python-dict/

    所以给我的感觉就是目前实现就是这样, 可能是多方取舍后得到的结果..
    maocat
        6
    maocat  
       2023-01-04 22:42:00 +08:00 via iPhone
    @jobmailcn 没错你说的就是 golang
    agagega
        7
    agagega  
       2023-01-04 22:52:50 +08:00 via iPhone
    因为它规定了 map 是有序的而 unordered_map 是无序的。更深究一点的话,因为散列表的内部元素顺序是高度依赖实现细节的,没必要规定死;对于用户 key 构成的外部顺序来说,你的第二点是对的。相当于 unordered_map 以无法排序为代价获得了更好的性能。
    ksc010
        8
    ksc010  
       2023-01-04 23:32:23 +08:00
    无需的就是在整个生命周期,不保证它的顺序是一直不变的(没有认为改动顺序的情况下)
    这样在语言具体实现的时候,就没有额外的负担
    geelaw
        9
    geelaw  
       2023-01-05 03:55:03 +08:00 via iPhone
    https://stackoverflow.com/questions/18301302/is-forauto-i-unordered-map-guaranteed-to-have-the-same-order-every-time

    如果没有修改容器导致的 rehash ,那么枚举顺序不变。

    正确的理解是枚举顺序和输入顺序没关系,但数次枚举之间不修改容器的话,数次枚举顺序相同。

    “key 数组”是不存在的概念。
    msg7086
        10
    msg7086  
       2023-01-05 03:57:09 +08:00   1
    不是一定不一样,而是不保证一样。(做到一样也行,但是没必要。)
    unordered_map 用哈希表实现,map 用红黑树实现,红黑树的性能要差一些。
    xqk111
        11
    xqk111  
       2023-01-05 09:13:46 +08:00
    我是这么理解的,数组一般是顺序存储,哈希表一般是随机存储,所以就认为数组是有序的,哈希表是无序,个人浅见
    e7
        12
    e7  
       2023-01-05 09:41:19 +08:00
    这把我问到了,就像“为什么球是圆的?”
    forcecharlie
        13
    forcecharlie  
       2023-01-05 10:15:46 +08:00
    如果你知道 unordered_map 的存储原理,你就知道它为啥是无序的。
    unordered_map 使用的是字符串哈希算法去将 Key 转变成一个数字,然后这个数对 bucket 取余,这样实现存储和读取,但是你迭代的时候可不是这么玩的,而是 bucket 一个个遍历。
    当然,实际情况比这复杂。

    不同的 STL 采用的哈希算法一般是不同的,比如 MSVC STL 使用的是 FNV1a:

    ```
    // These FNV-1a utility functions are extremely performance sensitive,
    // check examples like that in VSO-653642 before making changes.
    #if defined(_WIN64)
    _INLINE_VAR constexpr size_t _FNV_offset_basis = 14695981039346656037ULL;
    _INLINE_VAR constexp size_t _FNV_prime = 1099511628211ULL;
    #else // defined(_WIN64)
    _INLINE_VAR constexpr size_t _FNV_offset_basis = 2166136261U;
    _INLINE_VAR constexpr size_t _FNV_prime = 16777619U;
    #endif // defined(_WIN64)

    _NODISCARD inline size_t _Fnv1a_append_bytes(size_t _Val, const unsigned char* const _First,
    const size_t _Count) noexcept { // accumulate range [_First, _First + _Count) into partial FNV-1a hash _Val
    for (size_t _Idx = 0; _Idx < _Count; ++_Idx) {
    _Val ^= static_cast<size_t>(_First[_Idx]);
    _Val *= _FNV_prime;
    }

    return _Val;
    }

    ```

    https://github.com/microsoft/STL/blob/main/stl/inc/xhash

    而 libc++ 用的是 murmur2 ( 32bit ) cityhash64 ( 64bit ): https://github.com/llvm/llvm-project/blob/main/libcxx/include/__functional/hash.h
    cheng6563
        14
    cheng6563  
       2023-01-05 10:27:59 +08:00
    不是无序,是难以预计而当做无序。
    dobelee
        15
    dobelee  
       2023-01-05 11:15:06 +08:00
    没有人反对你实现一个有序哈希表。
    Miy4mori
        16
    Miy4mori  
       2023-01-05 21:09:30 +08:00
    哈希表这个名字更像是一个接口定义,本身就不要求具体实现保证有序。所以你问为什么无序,因为“哈希表”不要求具体实现保证有序........
    balabalaXMX
        17
    balabalaXMX  
    OP
       2023-01-06 09:27:10 +08:00
    谢谢大家,明白了。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5670 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 51ms UTC 02:51 PVG 10:51 LAX 18:51 JFK 21:51
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86