【c++】【STL】unordered_set 底层实现(简略版)

news2025/3/22 20:28:15

【c++】【STL】unordered_set 底层实现(简略版)

ps:这个是我自己看的不保证正确,觉得太长的后面会总结整个调用逻辑

unordered_set 内部实现

template <class _Kty, class _Hasher = hash<_Kty>, class _Keyeq = equal_to<_Kty>, class _Alloc = allocator<_Kty>>
class unordered_set : 
public _Hash<_Uset_traits<_Kty, _Uhash_compare<_Kty, _Hasher, _Keyeq>, _Alloc, false>> {
    // hash table of key-values, unique keys
public:
    static_assert(!_ENFORCE_MATCHING_ALLOCATORS || is_same_v<_Kty, typename _Alloc::value_type>,
        _MISMATCHED_ALLOCATOR_MESSAGE("unordered_set<T, Hasher, Eq, Allocator>", "T"));

private:
    using _Mytraits      = _Uhash_compare<_Kty, _Hasher, _Keyeq>;
    using _Mybase        = _Hash<_Uset_traits<_Kty, _Mytraits, _Alloc, false>>;
    using _Alnode        = typename _Mybase::_Alnode;
    using _Alnode_traits = typename _Mybase::_Alnode_traits;
    using _Key_compare   = typename _Mybase::_Key_compare;

public:
    using hasher    = _Hasher;
    using key_type  = _Kty;
    using key_equal = _Keyeq;
    ......

1 unordered_set

  • unordered_set 是从 _Hash 继承而来。
  • _Hash 是实际的哈希表实现,包含哈希冲突解决、扩容等核心逻辑。

2 _Uset_traits

unordered_set 通过模板参数传入到 _Uset_traits

_Uset_traits<_Kty, _Uhash_compare<_Kty, _Hasher, _Keyeq>, _Alloc, false>
参数解释例子
_Kty存储的键的类型int, std::string
_Hasher哈希函数std::hash<int>
_Keyeq键比较器std::equal_to<int>
_Alloc分配器std::allocator<int>
false是否允许重复键(为 false 表示不允许)unordered_set 需要唯一键
2.1 _Uset_traits 负责配置

unordered_set 中,_Uset_traits 负责设置哈希表的行为,包括:
✅ 元素的存储方式
✅ 哈希冲突解决方式

  • 哈希冲突解决方式 由 _Hash 通过 _Vec(桶) 和 _List(链表)来完成。
  • _Uset_traits 通过 hasher 和 key_equal 定义了哈希冲突的对比方式。

✅ 扩容策略
_Uset_traits 定义如下:

template <class _Kty, // key type (same as value type)
    class _Tr, // comparator predicate type
    class _Alloc, // actual allocator type (should be value allocator)
    bool _Mfl> // true if multiple equivalent keys are permitted
class _Uset_traits : public _Tr { // traits required to make _Hash behave like a set
public:
    using key_type            = _Kty;
    using value_type          = _Kty;
    using _Mutable_value_type = _Kty;
    using key_compare         = _Tr;
    using allocator_type      = _Alloc;
    ......
2.2 _Uhash_compare 负责哈希和比较

_Uset_traits 中,_Hasher_Keyeq 通过 _Uhash_compare 进行包装。
_Uhash_compare 是构造哈希表的关键:

  • _Myval1 = _Hasher
  • _Myval2 = _Keyeq 和最大负载因子

🔑 总结继承链

  1. unordered_set
    ⬇️ 继承
  2. _Hash
    ⬇️ 通过 _Uset_traits 配置
  3. _Uhash_compare
    ⬇️ 存储哈希函数、比较器、负载因子

调用路径完整总结

std::unordered_set<int> s;

➡️ unordered_set 构造
➡️ _Uset_traits 设置 key_type, hash, equal, allocator
➡️ _Uhash_compare 负责存储哈希和比较器
➡️ _Hash 处理哈希表创建、冲突解决、扩容等核心逻辑

1 _Uhash_compare

_Uhash_compare 的作用是:封装哈希函数_Hasher)和键值比较器_Keyeq

  • 哈希函数:用于生成键的哈希值—> 哈希函数 _Hasher
  • 比较函数:用于判断键是否相等—> 键值比较器 _Keyeq
  • 最大桶大小的管理:管理哈希桶的最大负载因子

class _Uhash_compare 封装了这些操作。
下面是vs2022对应代码:

template <class _Kty, class _Hasher, class _Keyeq>
class _Uhash_compare
    : public _Uhash_choose_transparency<_Kty, _Hasher, _Keyeq> { // traits class for unordered containers
public:
    enum { // parameters for hash table
        bucket_size = 1 // 0 < bucket_size
    };

    _Uhash_compare() noexcept(
        conjunction_v<is_nothrow_default_constructible<_Hasher>, is_nothrow_default_constructible<_Keyeq>>)
        : _Mypair(_Zero_then_variadic_args_t{}, _Zero_then_variadic_args_t{}, 0.0f) {}

    explicit _Uhash_compare(const _Hasher& _Hasharg) noexcept(
        conjunction_v<is_nothrow_copy_constructible<_Hasher>, is_nothrow_default_constructible<_Keyeq>>)
        : _Mypair(_One_then_variadic_args_t{}, _Hasharg, _Zero_then_variadic_args_t{}, 0.0f) {}

    explicit _Uhash_compare(const _Hasher& _Hasharg, const _Keyeq& _Keyeqarg) noexcept(
        conjunction_v<is_nothrow_copy_constructible<_Hasher>, is_nothrow_copy_constructible<_Keyeq>>)
        : _Mypair(_One_then_variadic_args_t{}, _Hasharg, _One_then_variadic_args_t{}, _Keyeqarg, 0.0f) {}

    template <class _Keyty>
    _NODISCARD size_t operator()(const _Keyty& _Keyval) const noexcept(_Nothrow_hash<_Hasher, _Keyty>) {
        // hash _Keyval to size_t value
        return static_cast<size_t>(_Mypair._Get_first()(_Keyval));
    }

    template <class _Keyty1, class _Keyty2>
    _NODISCARD bool operator()(const _Keyty1& _Keyval1, const _Keyty2& _Keyval2) const
        noexcept(_Nothrow_compare<_Keyeq, _Keyty1, _Keyty2>) {
        // test if _Keyval1 NOT equal to _Keyval2
        return !static_cast<bool>(_Mypair._Myval2._Get_first()(_Keyval1, _Keyval2));
    }

    _NODISCARD float& _Get_max_bucket_size() noexcept {
        return _Mypair._Myval2._Myval2;
    }

    _NODISCARD const float& _Get_max_bucket_size() const noexcept {
        return _Mypair._Myval2._Myval2;
    }

    void swap(_Uhash_compare& _Rhs) noexcept(
        conjunction_v<_Is_nothrow_swappable<_Hasher>, _Is_nothrow_swappable<_Keyeq>>) {
        _Swap_adl(_Mypair._Get_first(), _Rhs._Mypair._Get_first());
        auto& _Lsecond = _Mypair._Myval2;
        auto& _Rsecond = _Rhs._Mypair._Myval2;
        _Swap_adl(_Lsecond._Get_first(), _Rsecond._Get_first());
        _STD swap(_Lsecond._Myval2, _Rsecond._Myval2);
    }

    _Compressed_pair<_Hasher, _Compressed_pair<_Keyeq, float>> _Mypair;
};
_Compressed_pair<_Hasher, _Compressed_pair<_Keyeq, float>> _Mypair;
  • 通过一个 pair(即 _Compressed_pair)存储哈希函数、键值比较器和最大负载因子(即扩容因子)。
  • _Mypair结构:
    在这里插入图片描述

1.1 _Uhash_compare 定义

template <class _Kty, class _Hasher, class _Keyeq>
class _Uhash_compare
    : public _Uhash_choose_transparency<_Kty, _Hasher, _Keyeq> {

模板参数解释

参数作用
_Kty关键字类型(key type)
_Hasher哈希函数类型
_Keyeq键值比较器类型

1.2 构造函数解释

在这里插入图片描述

1.2.1 默认构造函数
_Uhash_compare() noexcept(
    conjunction_v<is_nothrow_default_constructible<_Hasher>, is_nothrow_default_constructible<_Keyeq>>)
    : _Mypair(_Zero_then_variadic_args_t{}, _Zero_then_variadic_args_t{}, 0.0f) {}

✅ 作用:

  • 默认构造 _Hasher_Keyeq
  • 最大负载因子初始化为 0.0f

✅ _Zero_then_variadic_args_t{} 是一个标签,用于指示在 Compressed_pair 中使用默认构造。

1.2.2 带哈希函数的构造函数
explicit _Uhash_compare(const _Hasher& _Hasharg) noexcept(
    conjunction_v<is_nothrow_copy_constructible<_Hasher>, is_nothrow_default_constructible<_Keyeq>>)
    : _Mypair(_One_then_variadic_args_t{}, _Hasharg, _Zero_then_variadic_args_t{}, 0.0f) {}

✅ 作用:

  • 传入一个哈希函数,并用默认构造创建比较器。
  • 最大负载因子初始化为 0.0f

✅ _One_then_variadic_args_t{} 是一个标签,表示先传入 _Hasher,再调用 _Keyeq 的默认构造。

1.2.3 带哈希函数和比较器的构造函数
explicit _Uhash_compare(const _Hasher& _Hasharg, const _Keyeq& _Keyeqarg) noexcept(
    conjunction_v<is_nothrow_copy_constructible<_Hasher>, is_nothrow_copy_constructible<_Keyeq>>)
    : _Mypair(_One_then_variadic_args_t{}, _Hasharg, _One_then_variadic_args_t{}, _Keyeqarg, 0.0f) {}

✅ 作用:

  • 同时传入哈希函数比较器
  • 将**最大负载因子(扩容因子)**初始化为 0.0f

_One_then_variadic_args_t{} 作用:

  • 第一个 _One_then_variadic_args_t{}:传入哈希函数
  • 第二个 _One_then_variadic_args_t{}:传入键比较器
1.2.4 举例表示
1:默认构造 unordered_map
std::unordered_map<int, int> m;

➡️ 执行路径:

  1. 默认构造会触发 _Uhash_compare() 默认构造函数:
_Uhash_compare() 
    : _Mypair(_Zero_then_variadic_args_t{}, _Zero_then_variadic_args_t{}, 0.0f) {}
  1. _Compressed_pair 的这个构造函数会被调用:
template <class... _Other2>
constexpr explicit _Compressed_pair(_Zero_then_variadic_args_t, _Other2&&... _Val2)
  • _Myval1 被默认构造(即哈希函数为 std::hash 默认构造)
  • _Myval2 被构造成 {std::equal_to{}, 0.0f}

ps:_Compressed_pair 在后面会提到
_Myval1存储哈希函数对象(_Hasher)
_Myval2存储键值比较对象(_Keyeq)最大负载因子

➡️ 构造完成后,最大负载因子 被设置为 1.0f(在 rehash() 之后调整)。

2 传入哈希函数构造 unordered_set
std::unordered_set<int, std::hash<int>> m;

➡️ 执行路径:

  1. 触发 _Uhash_compare 的这个构造函数:
explicit _Uhash_compare(const _Hasher& _Hasharg)
    : _Mypair(_One_then_variadic_args_t{}, _Hasharg, _Zero_then_variadic_args_t{}, 0.0f) {}
  1. _Compressed_pair 的这个构造函数被调用:
template <class _Other1, class... _Other2>
constexpr _Compressed_pair(_One_then_variadic_args_t, _Other1&& _Val1, _Other2&&... _Val2)
    : _Myval1(_STD forward<_Other1>(_Val1)), _Myval2(_STD forward<_Other2>(_Val2)...) {}
  • _Myval1 被设置为 std::hash<int>
  • _Myval2 被设置为 {std::equal_to<int>(), 0.0f}

➡️ 最大负载因子在 rehash() 之后被调整为 1.0f

3 传入哈希函数 + 比较器构造 unordered_set
std::unordered_set<int, std::hash<int>, std::equal_to<int>> m;

➡️ 执行路径:

  1. 触发 _Uhash_compare 的这个构造函数:
explicit _Uhash_compare(const _Hasher& _Hasharg, const _Keyeq& _Keyeqarg)
    : _Mypair(_One_then_variadic_args_t{}, _Hasharg, _One_then_variadic_args_t{}, _Keyeqarg, 0.0f) {}
  1. _Compressed_pair 的这个构造函数被调用:
template <class _Other1, class... _Other2>
constexpr _Compressed_pair(_One_then_variadic_args_t, _Other1&& _Val1, _Other2&&... _Val2)
    : _Myval1(_STD forward<_Other1>(_Val1)), _Myval2(_STD forward<_Other2>(_Val2)...) {}
  • _Myval1 被设置为 std::hash<int>
  • _Myval2 被设置为 {std::equal_to<int>(), 0.0f}

➡️ 最大负载因子在 rehash() 之后被调整为 1.0f

  1. _Uhash_compare 构造时,创建 _Compressed_pair 对象。
  2. _Compressed_pair 里第一个参数存储哈希函数
    第二个参数存储一个新的 _Compressed_pair,用于存储比较器最大负载因子
  3. 默认的最大负载因子(扩容因子)设为 0.0f
  • 0.0f 在初始化时作为占位值 ✅
  • rehash()_Forced_rehash() 中,最大负载因子会被设置为 1.0f
  • rehash() 的扩容策略根据当前的负载情况和桶大小动态调整 ✅

rehash()是扩容策略的函数

1.3 其它涉及到的函数

在这里插入图片描述
在这里插入图片描述

在_Uhash_compare 里面是_Compressed_pair

_Compressed_pair<_Hasher, _Compressed_pair<_Keyeq, float>> _Mypair;

2 _Compressed_pair

2.1 源码

template <class _Ty1, class _Ty2>
class _Compressed_pair<_Ty1, _Ty2, false> final { // store a pair of values, not deriving from first
public:
    _Ty1 _Myval1;
    _Ty2 _Myval2;

    template <class... _Other2>
    constexpr explicit _Compressed_pair(_Zero_then_variadic_args_t, _Other2&&... _Val2) noexcept(
        conjunction_v<is_nothrow_default_constructible<_Ty1>, is_nothrow_constructible<_Ty2, _Other2...>>)
        : _Myval1(), _Myval2(_STD forward<_Other2>(_Val2)...) {}

    template <class _Other1, class... _Other2>
    constexpr _Compressed_pair(_One_then_variadic_args_t, _Other1&& _Val1, _Other2&&... _Val2) noexcept(
        conjunction_v<is_nothrow_constructible<_Ty1, _Other1>, is_nothrow_constructible<_Ty2, _Other2...>>)
        : _Myval1(_STD forward<_Other1>(_Val1)), _Myval2(_STD forward<_Other2>(_Val2)...) {}

    constexpr _Ty1& _Get_first() noexcept {
        return _Myval1;
    }

    constexpr const _Ty1& _Get_first() const noexcept {
        return _Myval1;
    }
};

_Compressed_pair 被用来存储两个值:

  • _Myval1存储哈希函数对象(_Hasher)
  • _Myval2存储键值比较对象(_Keyeq)最大负载因子
class _Compressed_pair final : private _Ty1 { // store a pair of values, deriving from empty first
public:
    _Ty2 _Myval2;

    using _Mybase = _Ty1; // for visualization

    template <class... _Other2>
    constexpr explicit _Compressed_pair(_Zero_then_variadic_args_t, _Other2&&... _Val2) noexcept(
        conjunction_v<is_nothrow_default_constructible<_Ty1>, is_nothrow_constructible<_Ty2, _Other2...>>)
        : _Ty1(), _Myval2(_STD forward<_Other2>(_Val2)...) {}

    template <class _Other1, class... _Other2>
    constexpr _Compressed_pair(_One_then_variadic_args_t, _Other1&& _Val1, _Other2&&... _Val2) noexcept(
        conjunction_v<is_nothrow_constructible<_Ty1, _Other1>, is_nothrow_constructible<_Ty2, _Other2...>>)
        : _Ty1(_STD forward<_Other1>(_Val1)), _Myval2(_STD forward<_Other2>(_Val2)...) {}

    constexpr _Ty1& _Get_first() noexcept {
        return *this;
    }

    constexpr const _Ty1& _Get_first() const noexcept {
        return *this;
    }
};

扩容因子先被初始化为0.0f
在这里插入图片描述
在这里插入图片描述
_Myval2 是一个对象,其结构如下:

struct _Myval2_type {
    _Keyeq _Keyeqarg;
    float _Max_load_factor;
};

所以 _Mypair 实际结构如下:
在这里插入图片描述

2.2 上面涉及到_Uhash_compare构造函数的部分

构造 _Mypair 的地方:

explicit _Uhash_compare(const _Hasher& _Hasharg, const _Keyeq& _Keyeqarg) noexcept(
    conjunction_v<is_nothrow_copy_constructible<_Hasher>, is_nothrow_copy_constructible<_Keyeq>>)
    : _Mypair(_One_then_variadic_args_t{}, _Hasharg, _One_then_variadic_args_t{}, _Keyeqarg, 0.0f) {}

_Compressed_pair 里对应的是:

template <class _Other1, class... _Other2>
constexpr _Compressed_pair(_One_then_variadic_args_t, _Other1&& _Val1, _Other2&&... _Val2) noexcept(
    conjunction_v<is_nothrow_constructible<_Ty1, _Other1>, is_nothrow_constructible<_Ty2, _Other2...>>)
    : _Myval1(_STD forward<_Other1>(_Val1)), _Myval2(_STD forward<_Other2>(_Val2)...) {}
  • _Val1_Hasher(哈希函数对象)
  • _Val2..._Keyeq(键值比较对象)和 0.0f(最大负载因子)

等价于:

_Mypair._Myval1 = _Hasharg; // 存储哈希函数对象
_Mypair._Myval2 = { _Keyeqarg, 0.0f }; // 存储键比较对象和最大负载因子
  1. _Myval1 = 存储哈希函数 _Hasher
  2. _Myval2 = 存储键值比较对象 _Keyeq + 最大负载因子

2.3 扩容因子

在这里插入图片描述
这里就是获取扩容因子
在这里插入图片描述
rehash()中–>_Forced_rehash()
扩容因子会在 rehash() 之后更新:

_Mypair._Get_second()._Myval2 = 新的最大负载因子;
  • _Mypair._Get_second() 访问的是 _Myval2
  • _Myval2._Max_load_factor 会根据当前桶数和元素数量动态调整

2.4 总结

成员含义作用
_Myval1_Hasher存储哈希函数对象,负责将键映射为哈希值
_Myval2_Keyeq + 最大负载因子存储键值比较器 + 最大负载因子,负责元素比较和控制扩容行为

所以:

  • _Myval1 👉 控制哈希映射
  • _Myval2 👉 控制键值比较 + 控制负载因子(扩容因子)

3 扩容部分

unordered_set 第一次触发扩容后,内部会调用 rehash() 将最大负载因子设置成 1.0

3.1 rehash代码

 void rehash(size_type _Buckets) { // rebuild table with at least _Buckets buckets
        // don't violate a.bucket_count() >= a.size() / a.max_load_factor() invariant:
        _Buckets = (_STD max) (_Min_load_factor_buckets(_List.size()), _Buckets);
        if (_Buckets <= _Maxidx) { // we already have enough buckets; nothing to do
            return;
        }
        _Forced_rehash(_Buckets);
    }
3.1.2 _Min_load_factor_buckets(_List.size())
  • _Min_load_factor_buckets() 计算的目的是:
    • 维护最大负载因子的约束:
NODISCARD size_type _Min_load_factor_buckets(const size_type _For_size) const noexcept {
        // returns the minimum number of buckets necessary for the elements in _List
        return static_cast<size_type>(_CSTD ceilf(static_cast<float>(_For_size) / max_load_factor()));
    }

目的: 通过最大负载因子(max_load_factor)计算出当前存储元素所需的最小桶数量

  • _For_size:表示当前容器中已有的元素数量。
  • max_load_factor():最大负载因子,默认值为 1.0
  • ceilf():向上取整,确保计算结果为整数,保证即使余数很小也会分配一个完整的桶。

推导公式
最大负载因子=元素数量/桶数
可以通过最大负载因子计算出当前的桶数–>桶数=元素数量/最大负载因子

eg:

  • 当前有 10 个元素
  • 最大负载因子为 0.8
    10/0.8 = 12.5—>向上取整= 13—>(需要至少 13 个桶)
3.1.3 _Buckets = (_STD max) (…)
  • std::max 选择当前计算出的最小桶数量和传入的 _Buckets 值。
  • 这一步确保在 rehash() 过程中,桶数量不会低于当前负载因子要求的最低桶数。
3.1.4 if (_Buckets <= _Maxidx)
  • _Maxidx 表示当前桶的数量(即 bucket_count())。
  • 如果 _Buckets 小于等于当前桶数量,表示桶数量足够,无需扩容,直接返回。

4 扩容函数 _Forced_rehash(_Buckets)

4.1 源代码
void _Forced_rehash(size_type _Buckets) {
// Force rehash of elements in _List, distrusting existing bucket assignments in _Vec.
// Assumes _Buckets is greater than _Min_buckets, and that changing to that many buckets doesn't violate
// load_factor() <= max_load_factor().

// Don't violate power of 2, fits in half the bucket vector invariant:
 // (we assume because vector must use single allocations; as a result, its max_size fits in a size_t)
const unsigned long _Max_storage_buckets_log2 = _Floor_of_log_2(static_cast<size_t>(_Vec.max_size() >> 1));
const auto _Max_storage_buckets               = static_cast<size_type>(1) << _Max_storage_buckets_log2;
if (_Buckets > _Max_storage_buckets) {
_Xlength_error("invalid hash bucket count");
}

// The above test also means that we won't perform a forbidden full shift when restoring the power of
// 2 invariant
// this round up to power of 2 in addition to the _Buckets > _Maxidx above means
// we'll at least double in size (the next power of 2 above _Maxidx)
_Buckets                       = static_cast<size_type>(1) << _Ceiling_of_log_2(static_cast<size_t>(_Buckets));
const _Unchecked_iterator _End = _Unchecked_end();

 _Vec._Assign_grow(_Buckets << 1, _End);
_Mask   = _Buckets - 1;
_Maxidx = _Buckets;

_Clear_guard _Guard{this};

_Unchecked_iterator _Inserted = _Unchecked_begin();

// Remember the next _Inserted value as splices will change _Inserted's position arbitrarily.
for (_Unchecked_iterator _Next_inserted = _Inserted; _Inserted != _End; _Inserted = _Next_inserted) {
++_Next_inserted;

auto& _Inserted_key     = _Traits::_Kfn(*_Inserted);
const size_type _Bucket = bucket(_Inserted_key);

// _Bucket_lo and _Bucket_hi are the *inclusive* range of elements in the bucket, or _Unchecked_end() if
// the bucket is empty; if !_Standard then [_Bucket_lo, _Bucket_hi] is a sorted range.
_Unchecked_iterator& _Bucket_lo = _Vec._Mypair._Myval2._Myfirst[_Bucket << 1];
_Unchecked_iterator& _Bucket_hi = _Vec._Mypair._Myval2._Myfirst[(_Bucket << 1) + 1];

if (_Bucket_lo == _End) {
// The bucket was empty, set it to the inserted element.
_Bucket_lo = _Inserted;
_Bucket_hi = _Inserted;
continue;
}
// Search the bucket for the insertion location and move element if necessary.
_Unchecked_const_iterator _Insert_before = _Bucket_hi;
if (!_Traitsobj(_Inserted_key, _Traits::_Kfn(*_Insert_before))) {
// The inserted element belongs at the end of the bucket; splice it there and set _Bucket_hi to the
// new bucket inclusive end.
++_Insert_before;
if (_Insert_before != _Inserted) { // avoid splice on element already in position
_Mylist::_Scary_val::_Unchecked_splice(_Insert_before._Ptr, _Inserted._Ptr, _Next_inserted._Ptr);
}
_Bucket_hi = _Inserted;
continue;
}
// The insertion point isn't *_Bucket_hi, so search [_Bucket_lo, _Bucket_hi) for insertion point; we
// go backwards to maintain sortedness when !_Standard.
for (;;) {
if (_Bucket_lo == _Insert_before) {
// There are no equivalent keys in the bucket, so insert it at the beginning.
// Element can't be already in position here because:
// * (for !_Standard) _Inserted_key < *_Insert_before or
// * (for _Standard) _Inserted_key != *_Insert_before
_Mylist::_Scary_val::_Unchecked_splice(_Insert_before._Ptr, _Inserted._Ptr, _Next_inserted._Ptr);
_Bucket_lo = _Inserted;
break;
}
if (!_Traitsobj(_Inserted_key, _Traits::_Kfn(*--_Insert_before))) {
// Found insertion point, move the element here, bucket bounds are already okay.
++_Insert_before;
// Element can't be already in position here because all elements we're inserting are after all
// the elements already in buckets, and *_Insert_before isn't the highest element in the bucket.
_Mylist::_Scary_val::_Unchecked_splice(_Insert_before._Ptr, _Inserted._Ptr, _Next_inserted._Ptr);
break;
          }
       }
   }

_Forced_rehash() 是 unordered_set触发扩容(rehash)时的核心操作。
扩容的主要目的是为了:
✅ 解决哈希冲突
✅ 保持最大负载因子(即 load_factor <= max_load_factor()
✅ 保证哈希桶大小2 的幂次方(以便通过位运算提升效率)

4.2 函数定义
void _Forced_rehash(size_type _Buckets);
参数名说明
_Buckets需要的桶数量(由 _Min_load_factor_buckets() 计算得出)

工作原理(整体思路)

  1. 计算新的桶数量(满足最小的,根据负载因子计算出的桶数),位运算–>将桶数量调整为最接近的 2 的幂
  2. 如果桶数量超过最大允许数量,抛出异常。
  3. 重新分配存储空间。
  4. 将所有元素从旧桶重新分配到新桶(按照新的哈希分布)。
  5. 调整哈希表元信息(掩码、最大桶索引等)。
4.3 ① 确认桶数量是否超限
const unsigned long _Max_storage_buckets_log2 = _Floor_of_log_2(static_cast<size_t>(_Vec.max_size() >> 1));
const auto _Max_storage_buckets = static_cast<size_type>(1) << _Max_storage_buckets_log2;
if (_Buckets > _Max_storage_buckets) {
    _Xlength_error("invalid hash bucket count");
}
  • _Vec.max_size():返回当前 _Vec 支持的最大元素数量。
  • >> 1:为保证 vector 的容量不会超出限制,最大桶数量为 vector 最大长度的一半。
  • 超出最大桶数量限制时,直接抛出长度错误。
4.4 ② 调整桶数量到 2 的幂
_Buckets = static_cast<size_type>(1) << _Ceiling_of_log_2(static_cast<size_t>(_Buckets));
  • _Ceiling_of_log_2(x):返回不小于 log2(x) 的最小整数,确保桶数量为2 的幂
  • 1 << n:将桶数量调整为最接近的 2 的幂(位运算)。

eg :插入元素后需要 5 个桶

  1. _Min_load_factor_buckets() 返回 5
  2. _Ceiling_of_log_2(5) = 3
  3. 1 << 3 = 8

➡️ 结果:调整后桶数量为 8(最接近的 2 的幂)

为什么要调整成 2 的幂?

  1. 便于使用位运算替代取模

    • hash % bucket_sizehash & (bucket_size - 1)
    • 位运算速度快于取模运算
  2. 更高效的内存分配

    • 2 的幂次分配,内存对齐,提升访问速度
  3. 减少哈希冲突

    • 通过位运算定位索引更均匀
      结论
  • unordered_mapunordered_set 在内部调整桶数量时,最终都会转换为最接近的 2 的幂
  • _Min_load_factor_buckets() 可能会返回非 2 的幂的值,但实际分配桶数量最终通过 _Ceiling_of_log_2() 转换为 2 的幂。
4.5 ③ 分配新的存储空间
_Vec._Assign_grow(_Buckets << 1, _End);
  • << 1将桶数量扩大一倍,原因是存储 _Bucket_lo_Bucket_hi
  • _Assign_grow():为哈希桶重新分配内存。
    • _Bucket_lo:桶的起始位置。
    • _Bucket_hi:桶的结束位置。
4.6 ④ 更新哈希表的元信息
_Mask = _Buckets - 1;
_Maxidx = _Buckets;
  • _Mask:用于通过位运算快速定位桶索引
  • _Maxidx:保存当前最大桶索引
4.7 ⑤ 重新分配元素
_Unchecked_iterator _Inserted = _Unchecked_begin();

for (_Unchecked_iterator _Next_inserted = _Inserted; _Inserted != _End; _Inserted = _Next_inserted) {
    ++_Next_inserted;
  1. 遍历所有元素
  2. 根据新桶数量和哈希函数,重新定位每个元素的桶位置
  3. 将元素放入新的桶中
4.8 ⑥ 重新插入桶
const size_type _Bucket = bucket(_Inserted_key);

_Unchecked_iterator& _Bucket_lo = _Vec._Mypair._Myval2._Myfirst[_Bucket << 1];
_Unchecked_iterator& _Bucket_hi = _Vec._Mypair._Myval2._Myfirst[(_Bucket << 1) + 1];
  • _Bucket = bucket(_Inserted_key):用新的哈希函数定位元素所在桶。
  • _Bucket_lo_Bucket_hi:维护当前桶的首尾位置。
4.9 ⑦ 插入到桶内

根据哈希冲突策略(开放链地址法):

  • 若桶为空,直接插入:
if (_Bucket_lo == _End) {
    _Bucket_lo = _Inserted;
    _Bucket_hi = _Inserted;
    continue;
}
  • 若桶已存在,检查插入位置:
if (!_Traitsobj(_Inserted_key, _Traits::_Kfn(*_Insert_before))) {
    ++_Insert_before;
    if (_Insert_before != _Inserted) {
        _Mylist::_Scary_val::_Unchecked_splice(_Insert_before._Ptr, _Inserted._Ptr, _Next_inserted._Ptr);
    }
    _Bucket_hi = _Inserted;
    continue;
}
  • 采用直接插入法尾部插入法

关键操作定位

步骤关键操作解释
调整桶数量_Ceiling_of_log_2()调整到 2 的幂次方
分配内存_Assign_grow()分配新桶
更新索引_Mask = _Buckets - 1设置新桶索引的位掩码
重新插入_Mylist::_Scary_val::_Unchecked_splice()将元素插入到桶中
更新桶指针_Bucket_lo = _Inserted更新桶起始位置

5 负载因子修改—>max_load_factor()

最大负载因子在扩容后通常不会会被调整。
实际负载因子会到 0.5 ~ 1.0 的区间内。
这个调整发生在 max_load_factor() 执行后,由用户手动调用才会修改,通过重新设置 _Myval2 来改变

void max_load_factor(float _Newmax) noexcept /* strengthened */ {
    _STL_ASSERT(!(_CSTD isnan) (_Newmax) && _Newmax > 0, "invalid hash load factor");
    _Max_bucket_size() = _Newmax;
}
  1. max_load_factor()unordered_mapunordered_set 的公开接口,用于设置最大负载因子。
  2. _Max_bucket_size() 其实是 _Mypair._Myval2._Myval2 的封装,最终修改的正是存储负载因子的内部值。

_Max_bucket_size()
unordered_map 的实现中, _Max_bucket_size() 是对 _Mypair._Myval2._Myval2 的封装,最终会设置到 _Myval2,即:
在这里插入图片描述

NODISCARD const float& _Get_max_bucket_size() const noexcept {
        return _Mypair._Myval2._Myval2;
    }

修改的本质是:

_Mypair._Myval2._Myval2 = _Newmax;

如何被触发
用户手动调用:

max_load_factor(1.0f)

更新 _Myval2._Myval2 的值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2319130.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络安全设备配置与管理-实验4-防火墙AAA服务配置

实验4-p118防火墙AAA服务配置 从这个实验开始&#xff0c;每一个实验都是长篇大论&#x1f613; 不过有好兄弟会替我出手 注意&#xff1a;1. gns3.exe必须以管理员身份打开&#xff0c;否则ping不通虚拟机。 win10虚拟机无法做本次实验&#xff0c;必须用学校给的虚拟机。首…

【论文阅读】Contrastive Clustering Learning for Multi-Behavior Recommendation

论文地址&#xff1a;Contrastive Clustering Learning for Multi-Behavior Recommendation | ACM Transactions on Information Systems 摘要 近年来&#xff0c;多行为推荐模型取得了显著成功。然而&#xff0c;许多模型未充分考虑不同行为之间的共性与差异性&#xff0c;以…

基于协同过滤推荐算法的景点票务数据系统(python-计算机毕设)

摘 要 I ABSTRACT II 第 1 章 引言 1 研究背景及意义 1 研究背景 1研究意义 1 国内外研究现状 2 智慧旅游 3旅游大数据 3 研究内容 4本章小结 4 第 2 章 相关技术概述 5 基于内容的推荐算法 5 基于内容的推荐算法原理 5基于内容的推荐算法实现 5 协同过滤推荐算法 6 协同过…

Ubuntu 24 常用命令方法

文章目录 环境说明1、账号管理1.1、启用 root 2、包管理工具 apt & dpkg2.1、apt 简介 & 阿里源配置2.2、dpkg 简介2.3、apt 和 dpkg 两者之间的关系2.4、常用命令 3、启用 ssh 服务4、防火墙5、开启远程登录6、关闭交换分区7、build-essential&#xff08;编译和开发软…

Flask多参数模版使用

需要建立目录templates&#xff1b; 把建好的html文件放到templates目录里面&#xff1b; 约定好参数名字&#xff0c;单个名字可以直接使用&#xff1b;多参数使用字典传递&#xff1b; 样例&#xff1a; from flask import render_template # 模板 (Templates) #Flask 使用…

自然语言处理|深入解析 PEGASUS:从原理到实践

一、引言 在信息爆炸的时代&#xff0c;互联网上的文本数据以极快的速度增长。无论是新闻资讯、学术论文、社交媒体动态&#xff0c;还是各类报告文档&#xff0c;我们每天接触到的文字信息量巨大。如何快速、准确地提取关键内容成为一项重要任务。文本摘要技术通过将长篇文本…

Spring AI Alibaba快速使用

AI 时代&#xff0c;Java 程序员也需要与时俱进&#xff0c;这两个框架必须掌握。 一个是 Spring AI一个是 Spring Alibaba AI。 Spring AI 是一个AI工程领域的应用程序框架&#xff0c;它的目标是将 Spring生态系统的设计原则应用于人工智能领域。 但是&#xff0c; Spring…

socks 协议介绍

SOCKS协议详解 一、基本定义与核心功能 SOCKS&#xff08;Socket Secure&#xff09;是一种网络传输协议&#xff0c;主要用于通过代理服务器转发客户端与目标服务器之间的通信请求。其核心功能包括隐藏用户真实IP地址、穿透防火墙限制以及支持多种网络协议&#xff08;如TCP…

Linux --centos安装显卡驱动

显卡下载页面 https://www.nvidia.com/en-us/drivers/unix/ 随便下载一个即可 安装过程 查看当前设备的显卡信息 lspci | grep -i vga安装gcc相关依赖 yum update -y yum update gcc yum install build-essential yum install gcc-multilibdkms yum groupinstall "Dev…

统信UOS中使用Vscode编程

写在前面&#xff1a;统信UOS其实就是套壳的Linux系统&#xff0c;所以有问题如果搜不到解决方法&#xff0c;可以参考Linux下的解决方法。 1.环境配置 Vscode : 1.85.0 Vscode就直接下载安装就行&#xff0c;然后安装插件&#xff1a;Volar、中文汉化包 node&#xff1a;18…

docker安装向量数据库Milvus及可视化工具 Attu

前置条件 1.安装了docker 2.服务器网络正常&#xff0c;可以连接到容器下载地址 3.服务器磁盘空间正常&#xff0c;docker磁盘占用过大&#xff0c;请参考docker容量占用过大解决办法 一、下载yml文件 可在文章资源下载或者自行下载&#xff1a;下载yml 下载这个单机版本的…

DeepSeek + Kimi 自动生成 PPT

可以先用deepseek生成ppt大纲&#xff0c;再把这个大纲复制到Kimi的ppt助手里&#xff1a; https://kimi.moonshot.cn/kimiplus/conpg18t7lagbbsfqksg 选择ppt模板&#xff1a; 点击生成ppt就制作好了。

Apache Paimon 在抖音集团多场景中的优化实践

资料来源&#xff1a;火山引擎-开发者社区 本文将基于抖音集团内部两大业务的典型实时数仓场景&#xff0c;介绍Paimon在抖音集团内部的生产实践。 作者&#xff1a;李明、苏兴、文杰 抖音集团大数据工程师 目前抖音集团内部主要使用 Lambda 架构进行实时数仓建设&#xff0c;其…

当下主流 AI 模型对比:ChatGPT、DeepSeek、Grok 及其他前沿技术

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 1. 引言 人工智能&#xff08;AI&#xff09;领域近年来取得了巨大的突破&#xff0c;特别是在大语言模型&#xff08;LLM&#…

【自用】NLP算法面经(5)

一、L1、L2正则化 正则化是机器学习中用于防止过拟合并提高模型泛化能力的技术。当模型过拟合时&#xff0c;它已经很好地学习了训练数据&#xff0c;甚至是训练数据中的噪声&#xff0c;所以可能无法在新的、未见过的数据上表现良好。 比如&#xff1a; 其中&#xff0c;x1和…

体育直播视频源格式解析:M3U8 vs FLV

在体育直播领域&#xff0c;视频源的格式选择直接影响着直播的流畅度、画质以及兼容性。目前&#xff0c;M3U8 和 FLV 是两种最为常见的视频流格式&#xff0c;它们各有优劣&#xff0c;适用于不同的场景。本文将从技术原理、优缺点以及应用场景等方面对 M3U8 和 FLV 进行详细解…

Ubuntu20.04安装并配置Pycharm2020.2.5

一. 下载pycharm 社区版 1. 下载地址&#xff1a; PyCharm: the Python IDE for data science and web developmentThe Python IDE for data science and web development with intelligent code completion, on-the-fly error checking, quick-fixes, and much more.https:/…

Filter Solutions学习-02 【高级设计】界面介绍

这是高级界面的各种控件的功能。 其中说一下filter type。这不是根据自己想当然决定的&#xff0c;而是根据实际的需要&#xff0c;比如带外衰减的程度&#xff0c;带内波动&#xff08;平坦&#xff09;如何&#xff0c;还有群时延等等决定的。比如不要求矩形系数选什么。。 …

用Python实现交互式数据可视化:从基础图表到动态仪表板

用Python实现交互式数据可视化&#xff1a;从基础图表到动态仪表板 一、项目背景 本文将通过一个完整的Python项目&#xff0c;展示如何使用Plotly和ipywidgets构建从基础统计到动态交互的全栈数据可视化方案。 二、核心功能模块 1. 数据生成与预处理 np.random.seed(100)…

【深度学习与大模型基础】第6章-对角矩阵,对称矩阵,正交矩阵

一、对角矩阵 对角矩阵&#xff08;Diagonal Matrix&#xff09;是一种特殊的方阵&#xff0c;其非对角线上的元素均为零&#xff0c;只有对角线上的元素可能非零。具体来说&#xff0c;对于一个 nn的矩阵 A[]&#xff0c;如果满足 则 AA 称为对角矩阵。对角矩阵通常表示为&am…