STL 总结

STL

  • 在 C++ 标准模板库(STL)中,主要包含了一系列的容器迭代器算法函数对象适配器

容器

  • 容器是用于存储数据的类模板。STL 容器可以分为序列型容器、关联型容器和链表型容器三类:
  • 序列型容器:vectordequearray
  • 关联型容器:setmapmultisetmultimap
  • 链表型容器:forward_listlistunordered_setunordered_mapunordered_multisetunordered_multimap

迭代器

算法

  • STL 算法通过迭代器与容器进行交互,进行数据处理和操作。
  • 非修改序列算法:findcount
  • 修改序列算法:copymovetransform
  • 排序算法:sortstable_sort
  • 二分搜索算法:lower_boundupper_bound
  • 数值算法:accumulateinner_product

函数对象

  • STL 中的函数对象是实现了 operator() 的对象,常用于算法中作为策略或条件表达式。包括算术运算类、关系运算类、逻辑运算类。

适配器

  • 适配器用于修改容器或迭代器的接口。
  • 容器适配器:stackqueuepriority_queue

一、vector

vector 底层实现原理

  • 底层实现了一个动态数组
  • 类构成:
    • protected 的方式继承自 _Vector_base,基类的 public 在子类中将变为 protected,其他的权限不变。
      class vector : protected _Vector_base<_Tp, _Alloc> {}
      
    • _Vector_base 组成:
      在这里插入图片描述
      • _M_start:指向第一个元素的位置 → vec.begin()
      • _M_finish:指向最后一个实际存储元素的下一个位置 → vec.end()
      • _M_end_of_storage指向为 vector 所分配的内存块的末尾之后的第一个位置
      • _M_start_M_finish 之间的内存是 vector 实际使用的空间 → vec.size()
      • _M_start_M_end_of_storage 之间的内存是 vector 可以用来存储元素的空间 → vec.capacity()
      • _M_finish_M_end_of_storage 之间的内存是已经分配好可以随时使用的,但是目前未使用的。
      template<typename _Tp, typename _Alloc>
      struct _Vector_base
      {struct _Vector_impl_data{pointer _M_start;pointer _M_finish;pointer _M_end_of_storage;...}...
      }
      
  • 构造函数:
    • 无参构造函数:不会申请动态内存,保证性能优先。
    • 初始化元素个数的构造函数:一次性申请足够的动态内存避免多次申请动态内存,影响性能。
  • 插入元素:
    • 往最后位置插入:
      • 检查空间是否需要动态分配内存,是否需要扩容(_M_finish 是否等于 _M_end_of_storage)。
      • 插入到最后:push_back()emplace_back()++_M_finish
    • 往其他位置插入。
      • 检查空间是否需要动态分配内存,是否需要扩容。
      • 将插入位置之后的元素往后平移一位,然后插入元素:insert()
  • 删除元素:不会释放现有已经申请的内存
    • 删除最后一个元素 pop_back()_M_finish 往前移动一位(--_M_finish)。
    • 删除其他元素 erase():将待删元素之后的元素向前平移一位,_M_finish 往前移动一位。
  • 读取元素:返回的是具体元素的引用
    • 操作符[]
    • at():比操作符[] 多了一个检查越界的动作,越界后会抛出错误。
  • 修改元素:vector 不支持直接修改某个位置的元素
    • 通过读取元素,获取引用,然后进行修改。
    • 先删除后插入。
  • 释放空间:
    • swap() 交换一个空容器。
      std::vector<int>().swap(vec); 
      
    • clear(),然后 shrink_to_fit()释放掉未使用的内存

vector 内存增长机制

  • 特点:
    • 内存空间只会增加不会减少
    • vector 的内存是连续的
    • 不同平台的增长方式不一样,Linux 下是以翻倍的方式进行增长
  • 增长特征:
    • 无参构造,连续插入一个,增长方式:1、2、4、8 …
    • 有参构造,连续插入一个,增长方式:10、20、40 …
  • 增长时的具体操作:
    • 此时 _M_finish == _M_end_of_storage将内存空间大小翻倍并产生新的内存空间。
    • 将容器原来内存空间中的数据复制到新的内存空间中
    • 释放容器原来的内存空间
    • 插入新元素。
  • 注意:vector 中的元素为指针时,不会调用析构函数,需要手动释放内存

vector 中 reserve 和 resize 的区别

  • 共同点:
    • 调用它们,容器内原有的元素不受影响
    • 它们都起到增加的作用,对于缩小操作直接无视。
  • 区别:
  • reserve 能增加 vectorcapacity,但是它的 size 不会改变。
  • resize 既增加了 vectorcapacity,又增加了 size
  • 应用场景:
    • reserve 用来避免多次内存分配
    • resize 确保操作符[] at() 的安全性

vector 中的元素类型为什么不能是引用

  • 引用的特征:
    • 引用必须要进行初始化,不能初始化为空对象,初始化后不能改变指向。
    • 引用是别名,不是对象,没有实际的地址,不能定义引用的指针,也不能定义引用的引用。
  • 不能为引用分配内存 → 不能定义引用的指针。
  • push_back() 传入左值时会调用拷贝构造函数 → 不能定义引用的引用。
  • 基于操作符[]at(),将会获取引用的引用 → 不能定义引用的引用。

vector 中 push_back 和 emplace_back 的区别

  • push_back 如果传入的是左值,会调用拷贝构造函数;如果传入的是右值,会调用移动构造函数。
  • emplace_back 利用传入的参数在容器内存中直接构造元素,而无需首先创建临时对象再将其拷贝或移动到容器中,它使用 forward 完美转发将参数直接传递给元素的构造函数。
    vector<string> vec;// 使用 push_back 添加字符串
    string str = "Hello";
    vec.push_back(str);                   // 调用拷贝构造函数
    vec.push_back(string("World"));       // 调用移动构造函数// 使用 emplace_back 添加字符串
    vec.emplace_back("Hello, World");     // 直接在容器中构造字符串
    
    vector<string> vec;string hello = "Hello";
    // 这里会调用拷贝构造函数,因为传递的是一个现有的 string 对象
    vec.emplace_back(hello);// 这里直接在容器中构造一个新的 string,不调用拷贝或移动构造函数
    vec.emplace_back("World");
    

二、list

  • 底层实现了一个双向循环链表
  • 类构成:
    • protected 的方式继承自 _List_base
      class list : protected _List_base<_Tp, _Alloc> {}
      
    • _List_base_List_impl_List_node_M_storage:存储具体的值。
    • _List_node 继承自 _List_node_base_M_next_M_prev
      _List_node_base* _M_next;
      _List_node_base* _M_prev;
      
  • 构造函数:
    • 不管如何构造,初始都会构建一个空节点 dummyHead
    • 空节点用来表示整个双向循环链表。
  • 迭代器:
    • ++ 往下移动指针 → _M_node = _M_node->_M_next
    • -- 向上移动指针 → _M_node = _M_node->_M_prev
  • 获取第一个元素:空节点的下一个节点 → this->_M_impl._M_node._M_next
  • 获取最后一个元素:空节点的上一个节点 → 先获取空节点 this->_M_impl._M_node,再 --
  • 插入元素:每插入一个元素,都临时为该节点分配内存,修改指向,size++

在这里插入图片描述


三、deque

  • 目的是实现双端数组,底层实现了一个双向开口的连续线性空间
  • 类构成:
    • protected 的方式继承自 _Deque_base
      class deque : protected _Deque_base<_Tp, _Alloc> {}
      
    • _Deque_base_Deque_impl
      • _M_map指针数组
      • _M_map_size_M_map 的容量。
      • _M_start:记录 _M_map 指针数组中首个连续空间的信息。
      • _M_finish:记录 _M_map 指针数组中最后一个连续空间的信息。
  • 迭代器 _Deque_iterator
    • _M_cur:指向当前正在遍历的元素。
    • _M_first:指向当前连续空间的首地址。
    • _M_last:指向当前连续空间的尾地址。
    • _M_node: 指向 _M_map 指针数组中存储的指向连续空间的指针。
  • __deque_buf_size:连续空间中能容纳的元素个数;如果元素大小小于 512 字节,则能容纳 512 / 元素大小个元素,否则只能容纳一个元素。
  • _M_initialize_map
    • 创建 _M_map,最小为 8,并配置缓冲区。
    • _M_start_M_finish 指向中间的位置,方便公平地往上或者向下扩展空间。
  • push_back
    • 先看当前连续空间够不够,够就直接插入。
    • 不够的话,再看 _M_map 空间够不够,够就生成新的连续空间。
    • 不够的话,就生成新的 _M_map,把旧的_M_map 中的数据挪到新的 _M_map 中。
  • pop_back:删除当前连续空间的最后一个元素,如果当前连续空间没有数据了,则释放该连续空间。

在这里插入图片描述


四、vector、list、deque 使用场景

  • 如果需要高效地快速访问(随机存取),并且不在乎插入和删除的效率,使用 vector
  • 如果需要大量地插入和删除,并且不关心快速访问(随机存取),使用 list
  • 如果需要快速访问(随机存取),并且关心两端数据的插入和删除,使用 deque

五、priority_queue

  • 底层实现了一个堆(Heap),堆是一种高效维护集合中最大或最小元素的数据结构。
    • 大根堆:根节点最大的堆,用于维护和查询 max
    • 小根堆:根节点最小的堆,用于维护和查询 min
    • 堆是一棵树,并且满足堆性质:
      • 大根堆任意节点的关键码 ≥ \geq 它所有子节点的关键码(父 ≥ \geq 子)
      • 小根堆任意节点的关键码 ≤ \leq 它所有子节点的关键码(父 ≤ \leq 子)
  • 底层容器:默认是 vector,也可以使用 deque
  • 选择容器的前提是容器要提供这三个接口:
    • front()
      top() const {return c.front();
      }
      
    • push_back()
      push(const value_type& __x) { c.push_back(__x); 
      }
      
    • pop_back()
      pop() {std::pop_heap(c.begin(), c.end(), comp);c.pop_back();
      }
      
  • STL 对堆结构提供的接口:
    • p r i o r i t y _ q u e u e priority\_queue priority_queue 默认是大根堆,比较器为 std::less<>
    • make_heap(first, last, compare):根据 compare 构建堆。
    • push_heap(first, last, compare):添加元素,并重新构建堆。
    • empty()
    • size()
    • top()
    • push()
    • pop()
  • 二叉堆是堆的一种简易实现,本质上是一棵满足堆性质的完全二叉树
    在这里插入图片描述
  • 二叉堆的实现:
    • 二叉堆一般使用一个一维数组来存储,利用完全二叉树的节点编号特性。
    • 假设第一个元素存储在索引(下标)为 1 1 1 的位置
      • 索引为 p p p 的节点的左孩子的索引为 p ∗ 2 p * 2 p2
      • 索引为 p p p 的节点的右孩子的索引为 p ∗ 2 + 1 p * 2 + 1 p2+1
      • 索引为 p p p 的节点的父亲的索引为 p / 2 p / 2 p/2(下取整)
        在这里插入图片描述
  • 二叉堆的操作:
    • 插入( p u s h push push):
      • 新元素一律插入到数组 h e a p heap heap 的尾部。
        • 设插入到索引 p p p 的位置。
      • 然后向上进行一次调整( H e a p i f y U p Heapify \ Up Heapify Up)。
        • 若已到达根,停止。
        • 若满足堆性质( h e a p [ p ] ≤ h e a p [ p / 2 ] heap[p] \leq heap[p/2] heap[p]heap[p/2]),停止。
        • 否则交换 h e a p [ p ] heap[p] heap[p] h e a p [ p / 2 ] heap[p/2] heap[p/2],令 p = p / 2 p = p / 2 p=p/2,继续调整。
      • 时间复杂度: O ( l o g n ) O(logn) O(logn)
    • 取出堆顶( p o p pop pop):
      • 把堆顶( h e a p [ 1 ] heap[1] heap[1])与堆尾( h e a p [ n ] heap[n] heap[n])交换,删除堆尾(数组最后一个元素)。
      • 然后从根向下进行一次调整( H e a p i f y D o w n Heapify \ Down Heapify Down)。
        • 每次与左、右子节点中较大的一个比较,检查堆性质,不满足则交换。
        • 注意判断子节点是否存在 。
      • 时间复杂度: O ( l o g n ) O(logn) O(logn)
  • 二叉堆代码:
    struct Node {int key;ListNode* node;Node(int key, ListNode* node) : key(key), node(node) {}
    };class BinaryHeap {
    public:BinaryHeap() {// 从 1 开始存, 0 位置存一个无意义的值heap.push_back(Node(0, nullptr));}bool empty() { return heap.size() == 1; }Node getMin() { return heap[1]; }void insert(const Node& node) {heap.push_back(node);heapifyUp(heap.size() - 1);}void deleteMin() {heap[1] = heap[heap.size() - 1];heap.pop_back();heapifyDown(1);}private:void heapifyUp(int p) {while (p > 1) {if (heap[p / 2].key > heap[p].key) {swap(heap[p / 2], heap[p]);p = p / 2;} elsebreak;}}void heapifyDown(int p) {int child = p * 2; // 要交换的节点while (child < heap.size()) {int other_child = p * 2 + 1; // 另一个节点if (other_child < heap.size() &&heap[other_child].key < heap[child].key) {child = other_child;}if (heap[p].key > heap[child].key) {swap(heap[p], heap[child]);p = child;child = p * 2;} elsebreak;}}vector<Node> heap;
    };
    

六、multiset

  • 基于红黑树实现,允许键值重复的有序集合
    • mapsetmultisetmultimap 都是基于红黑树实现。
  • 红黑树性质:
    • 红黑树是二叉排序树中序遍历是顺序的
    • 每个节点是红色或者黑色。
    • 根节点是黑色的
    • 所有叶子节点都隐藏,并且为黑色
    • 如果一个节点是红色的,则它的两个儿子都是黑色的 → 红色节点不相邻
    • 对每个节点,从该节点到其子孙节点的所有路径上的包含相同数目的黑节点 → 黑色节点高度一样
      • 从根节点到叶子节点的最大深度和最小深度的关系是 2n - 1 : n
        在这里插入图片描述
      typedef struct _rbtree_node {struct _rbtree_node *parent;struct _rbtree_node *left;struct _rbtree_node *right;int key;     // 维持有序void *data;  // 节点额外信息unsigned char color; // 节点颜色
      } rbtree_node;struct _rbtree {struct _rbtree_node root;struct _rbtree_node *nil; // 所有叶子节点都隐藏,并且为黑色
      } rbtree;
      
  • STL 中红黑树的实现:
    • 结构:
      template<typename _Key, typename _Val, typename _KeyOfValue, typename _Compare, typename _Alloc = allocator<_Val> >
      class _Rb_tree {}
      
      • _Keykey 的类型。
      • _Val 是节点数据的类型,keydata 的结合。
      • _KeyOfValue 是从 _Val 中取得 _Key 值的方法。
      • _Compare 是比较 key 的方法。
      • _Alloc 是分配内存的方法。
    • 原理图:
      在这里插入图片描述
    • 重要接口:
      • _M_insert_unqiue:插入数据时,保证 key 唯一;插入相同节点会覆盖。
      • _M_insert_equal:插入数据时,允许 key 重复;插入相同节点会成为其的右孩子。
  • multiset 实现:
    • value_type 存储的就是 keykey 是不允许被修改的,所以 multiset 中的节点不允许被修改 → const_iterator
    • insert 的时候调用的是 _M_insert_equal
      typedef _Rb_tree<key_type, value_type, _Identity<value_type>, key_compare, _Key_alloc_type> _Rep_type;
      
      typedef _Key     key_type;
      typedef _Key     value_type; 
      
      struct _Identity : public unary_function<_Tp, _Tp> {_Tp& operator()(_Tp& __x) const { return __x;} 
      }
      
  • map 实现:
    • map 中的 data 是可以被修改的。
      typedef _Rb_tree<key_type, value_type, _Select1st<value_type>,key_compare, _Pair_alloc_type> _Rep_type;
      
      typedef _Key    key_type;
      typedef _Tp     mapped_type;
      
      struct _Select1st : public unary_function<_Pair, typename _Pair::first_type> {operator()(_Pair& __x) const { return __x.first;}
      }
      
  • 例子:
    // <1, 2, 3>
    set<int> s; // <1, 2, 2, 3>
    multiset<int> ms;// <1->2, 3->2, 4->2>
    map<int, int> m;// <1->2, 1->3, 3->2, 4->3>
    multimap<int, int> mm;
    
  • 红黑树旋转:
    • 当红黑树性质被破坏的时候,需要调整 → 左旋,右旋

    • 红黑树的插入或者删除最多旋转树的高度次就可以达到平衡
      在这里插入图片描述
      在这里插入图片描述

      void rbtree_left_rotate (rbtree *T, rbtree_node *x) {rbtree_node *y = x->right;// 第一步x->right = y->left;if (y->left != T->nil) {y->left->parent = x;}// 第二步y->parent = x->parent;if (x->parent == T->nil){ // x 为根节点T->root = y;} else if (x == x->parent->left) {x->parent->left = y;} else {x->parent->right = y;}// 第三步y->left = x;x->parent = y;
      }void rbtree_right_rotate(rbtree *T, rbtree_node *y) {rbtree_node *x = y->left;y->left = x->right;if (x->right != T->nil) {x->right->parent = y;  }x->parent = y->parentl;if (y->parent == T->nil) { // y 为根节点T->root = x;} else if (y == y->parent->right) {y->parent->right= x; } else {y->parent->left= x;}x->right = y;y->parent = x;
      }
      
  • 红黑树插入:
    • 红黑树在插入节点以前,它已经是一棵红黑树了。
    • 插入节点上色为红色因为不会改变黑色节点高度
    • 父节点是祖父节点的左子树
      • 叔节点是红色的:
        在这里插入图片描述
      • 叔节点是黑色的,并且当前节点是右子树。
        在这里插入图片描述
      • 叔节点是黑色的,并且当前节点是左子树。
        在这里插入图片描述

七、unordered_map

  • 基于散列表实现了 map
  • 散列表:
    • kv 对 通过对 key 进行 hash 运算映射到一个散列表中的具体位置。
    • hash 碰撞:将多个不同的 key 映射到散列表中同一个位置的行为。解决方案有线性探查拉链法(STL 采用的方法) 等。
    • 拉链法
      • hash 函数依然用于计算数组下标
      • 数组的每个位置存储一个了链表的表头指针
      • 每个链表保存具有相同 hash 值的数据
        在这里插入图片描述
    • 负载因子 = 实际存储的元素个数 / / / 散列表中桶的个数
    • 当负载因子大于 1 的时候,将发起 rehash
      • 创建一个更大的桶数组,通常是原来大小的两倍。
      • 散列表中的每个元素根据新的桶数组大小重新计算其 hash 值。在 unordered_map 中,不会重新经过 hash 函数计算,而是将 cache 中的 hash 值对新的桶数组大小取余
      • 元素根据新的 hash 值被重新插入到新的桶中。
  • STL 中散列表的实现(使用的单链表,而不是每个桶都指向一个链表):
    在这里插入图片描述
    • 数据定义:
      • _M_buckets:指针数组。每一个桶都指向该层节点的上一个节点 → 实现头插法
      • _M_bucket_count:数组长度。
      • _M_element_count:实际存储元素的个数。
      • _M_before_begin:头节点。
      • _M_rehash_policyrehash 的策略对象。
    • 节点定义:
      • 头节点和桶不需要 _M_storage其他节点需要
      struct _Hash_node_value_base : _Hash_node_base {_M_storage;
      }
      
    • 主要接口:
      • _M_insert_bucket_begin
      _M_insert_bucket_begin(size_type __bkt, __node_type* __node) {if (_M_buckets[__bkt]) {__node->_M_nxt = _M_buckets[__bkt]->_M_nxt;_M_buckets[__bkt]->_M_nxt = __node;}else {__node->_M_nxt = _M_before_begin._M_nxt;_M_before_begin._M_nxt = __node;if (__node->_M_nxt)_M_buckets[_M_bucket_index(__node->_M_next())] = __node;_M_buckets[__bkt] = &_M_before_begin;}
      }
      
  • unordered_map 实现:
    • typedef __umap_hashtable<_Key, _Tp, _Hash, _Pred, _Alloc> _Hashtable;
    • insert 实际调用的是 _M_insert_bucket_begin
    • 无序的结构,搜索的时间复杂度为 O ( 1 ) O(1) O(1)

八、迭代器底层实现原理、有哪些种类

  • 提供一种访问容器内元素而不会暴露容器内部实现的方式
  • 主要作用:
    • 解引用(*->)和成员访问。
    • 统一不同容器的访问方式,粘合容器和算法。(STL 提供了容器、算法、迭代器、内存分配器等)
      • find
      • min_element
      • upper_bound
      • reverse
      • sort
  • 底层原理:
    • 考虑哪些问题:
      • 不是面向对象思想编程,而泛型思想编程。
      • 通用算法问题:
        • 迭代器类型:
          • typename 类型别名。
          • 指针类型怎么办 ?
        • 指针类型不能定义类型别名。
        • 怎么根据迭代器类型实现算法。
    • 定义了 5 种类型的迭代器:输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器。
    • 迭代器的关系:
      在这里插入图片描述
    • 泛型编程中使用了萃取技术,把迭代器内部的各个类型萃取出来
      • 萃取迭代器用来生成模板,具体的迭代器需要按照这个模板来生成。
      template<typename _Category, typename _Tp, typename _Distance = ptrdiff_t,typename _Pointer = _Tp*, typename _Reference = _Tp&>
      struct iterator {// 具体的迭代器类型typedef _Category  iterator_category;// 迭代器所指向的元素的类型typedef _Tp        value_type;// 两个迭代器之间的距离typedef _Distance  difference_type;// 指向迭代器所指向元素的指针类型// 如果迭代器指向 int,pointer 将是 int*typedef _Pointer   pointer;// 引用类型,指向迭代器所指向的元素// 如果 value_type 是 int,reference 将是 int&typedef _Reference reference;
      };
      
      • 但是指针类型不能定义别名,所以就需要进行泛型特化如果出现指针类型,就不使用别名了,直接指定为随机访问迭代器
      template<typename _Tp>
      struct iterator_traits<_Tp*> {typedef random_access_iterator_tag iterator_category;typedef _Tp                         value_type;typedef ptrdiff_t                   difference_type;typedef _Tp*                        pointer;typedef _Tp&                        reference;
      };
      
    • 通过函数重载的方式,根据迭代器类型来选择不同的算法实现
  • 迭代器类型属性:
    • 输入迭代器:istream_iterator
      • 只能在一个方向上进行 ++,一个元素一个元素的挪动。
      • 因为迭代器传递过程中,上一个迭代器会失效,所以迭代器不能被保存。
      • 是只读的并且只能读一次。
    • 输出迭代器:ostream_iterator。是只写的并且只能写一次,其他同上。
    • 前向迭代器:forward_listunordered_mapunordered_set
      • 可读可写,并且可以读写多次。
      • 可以被保存。
    • 双向迭代器:listmapsetmultimapmultiset
      • 可读可写,并且可以读写多次。
      • 可以被保存。
      • 可正向遍历和反向遍历。
    • 随机访问迭代器:vectordeque。可以 += n

在这里插入图片描述


九、迭代器失效、连续和非连续存储容器的失效

  • 迭代器提供一种访问容器内元素而不会暴露容器内部实现的方式。
  • C++11 之前容器类型:
    • 序列型:vectordequearray
    • 关联型:setmapmultisetmultimap
    • 链表型:forward_listlistunordered_setunordered_mapunordered_multisetunordered_multimap
  • 失效情况:
    • 单独删除或插入:首先持有了某个迭代器,然后通过删除或插入修改了容器,导致持有的迭代器失效了
      • 插入:insertemplace*push_backpush_front
      • 删除:erasepop_backpop_frontclear
        在这里插入图片描述
    • 遍历删除:
      • 序列型容器:it = container.erase(it);
        • 因为元素都存储在连续空间中,所以删除某个元素时,后面的元素都要往前移,那么对应的迭代器也就都失效了,所以就需要返回元素移动后的、新的、有效的迭代器。
      • 关联型容器:
        • 因为元素存储在不连续空间中,所以删除某个元素时,只有该元素的迭代器失效,后面元素的迭代器都不会失效。
        • C++11 之前:container.erase(it++)
          • erase 返回的是 void
          • it 失效以前进行 it++
        • C++11 及以后:it = container.erase(it);container.erase(it++);
      • 链表型容器:it = container.erase(it);container.erase(it++);
      for (auto it = c.begin();it != c.end();) {if (shouldDelete(it)){ it = c.erase(it); // c.erase(it++); } else {it++;}
      }
      
  • C++11 容器类型:
    • 连续存储容器:it = container.erase(it);
    • 非连续存储容器:it = container.erase(it);container.erase(it++);

十、STL 容器线程安全性

  • 容器内的实现已经固定,内部没有加锁,不能修改源码加锁
  • 容器内部实现原理:
    • 扩缩容:
      • vector(扩容)。
      • deque(扩缩容)。
        • priority_queue 默认使用 vector 实现,也可以使用 deque
        • queue 默认使用 deque 实现,也可以使用 list
        • stack 默认使用 deque 实现,也可以使用 list
    • rehash:
      • unordered_*
    • 节点关系:
      • vector 中插入或删除会引起后面的节点移动。
      • setmapmultisetmultimap 用红黑树实现,插入或删除会引起红黑树重新平衡。
  • 解决方案:
    • 加锁:
      • 互斥锁。
      • 读多写少的情况下,可以使用读写锁:C++14 为 shared_timed_mutex、C++ 17 为 shared_mutex
      • 并发插入优化
        • 把插入行为转换为修改数据的行为
        • 提前分配好节点,提前构建好节点之间的关系
          • vector 使用 resize
          • 红黑树 4 兄弟、unordered_*,提前拿到所有的 key。在对同一个 key 并发修改的时候,仍然需要加锁,可使用原子操作
      • 并发插入删除优化:list:使用生产消费者模型,只对 list 的前后进行操作,生产者线程和消费者线程分别加锁deque 不可以,基于 deque 的其他容器也不可以。
    • 不加锁、避免加锁:
      • 提前分配好节点,并将数据分成多份,每个线程只操作专属那份数据
      • 缺点:可能出现线程操作不均衡的情况。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网工路由基础——静态路由

一、静态路由的定义 静态路由是一种需要管理员手动配置的特殊路由。 二、静态路由的目的或背景 1&#xff09;当网络结构比较简单时&#xff0c;只需要配置静态路由就可以使网络正常工作&#xff1b; 2&#xff09;在复杂网络中&#xff0c;配置静态路由可以改进网络的性能&am…

智能BI(后端)-- 系统异步化

文章目录 系统问题分析什么是异步化&#xff1f;业务流程分析标准异步化的业务流程系统业务流程 线程池为什么需要线程池&#xff1f;线程池两种实现方式线程池的参数线程池的开发 项目异步化改造 系统问题分析 问题场景&#xff1a;调用的服务能力有限&#xff0c;或者接口的…

5月数学进度应该到哪里?听说24更难了,进度要加快吗?

刷一本习题册够吗&#xff1f;刷哪本&#xff1f;什么时候刷&#xff1f; 确实&#xff0c;24考完&#xff0c;大家都发现&#xff0c;没有一本习题册&#xff0c;覆盖了考试的所有知识点。 主流的模拟卷&#xff0c;都没有达到24卷的难度。 如何才能在最短的时间内&#xff…

LeetCode例题讲解:快乐数

编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结果为 1&#xff0c…

Electron项目中将CommonJS改成使用ES 模块(ESM)语法preload.js加载报错

问题 将Electron项目原CommonJS语法改成使用ES 模块&#xff08;ESM&#xff09;语法&#xff0c;preload.js一直加载不到&#xff0c;报错如下&#xff1a; VM111 renderer_init:2 Unable to load preload script: D:\Vue\wnpm\electron\preload.js VM111 renderer_init:2 E…

地下管线管网三维参数化建模软件MagicPipe3D V3.5

经纬管网建模系统MagicPipe3D&#xff08;www.magic3d.net&#xff09;自主安全可控&#xff0c;本地离线参数化构建三维管网模型&#xff08;管道、接头、附属物等&#xff09;&#xff0c;输出标准3DTiles、Obj等格式&#xff0c;支持Cesium、Unreal、Unity等引擎可视化查询分…

蓝桥杯EDA客观题

目录 前言 一、PCB类知识点和题目分析 1.电阻 2.电容 3.封装类 4.单位转换类 5.电路板结构类 6.PCB绘制规则 7.立创软件 8.PCB硬件 线性电源和开关电源 二、数电知识点和题目分析 1.门电路 2.逻辑代数 3.组合逻辑电路 4.触发器 5.时序逻辑电路 6.其他 三、模…

电表远程抄表方案实施价格是多少?

1.电表远程抄表技术性简述 电表远程抄表&#xff0c;又被称为智能化电表或自动化技术电表载入系统软件&#xff0c;是一种现代化电力工程管理模式。它利用先进的通讯技术和自动化机械&#xff0c;完成对电表数据信息的远程、自动采集&#xff0c;大大提升了电力行业的经营效率…

go mod

常用命令 初始化模块 go mod init 模块名下载 go.mod 文件中指明的所有依赖 go mod download github.com/gin-gonic/ginv1.9.(依赖路径)依赖对其&#xff08;使引用的都是所依赖的&#xff09; go mod tidy编辑go.mod go mod edit go mod edit -require"github.com/g…

虚拟化技术 使用Vsphere Client管理ESXi服务器系统

使用Vsphere Client管理ESXi服务器系统 一、实验目的与要求 1.掌握使用vSphere Client管理ESXi主机 2.掌握将CentOS的安装介质ISO上传到ESXi存储 3.掌握在VMware ESXi中创建虚拟机 4.掌握在所创建的虚拟机中安装CentOS6.5操作系统 5.掌握给CentOS6.5安装VMware Tools 6.掌…

管理学SCI期刊,中科院4区,审稿快易录用,性价比超高!

一、期刊名称 Central European Journal of Operations Research 二、期刊简介概况 期刊类型&#xff1a;SCI 学科领域&#xff1a;管理学 影响因子&#xff1a;1.7 中科院分区&#xff1a;4区 出版方式&#xff1a;订阅模式/开放出版 版面费&#xff1a;选择开放出版需…

Python - 金三银四心路历程 之 数据结构与算法 刷题

目录 一.引言 二.心路历程 三.刷题经历 四.刷题历程 五.总结 一.引言 <夜深人静写算法> 是 23 年 12 月底博主打算跳槽时开始做刷题准备做的专栏&#xff0c;前后准备了大约一个月&#xff0c;刷题完毕后简单准备了项目和简历后就开始加入找工作大军了&#xff0c;最…