Bootstrap

[杂学笔记]STL容器简单介绍、高并发原理、数据库索引

目录

1.STL容器简单介绍

2.高并发实现原理

3.MySQL数据库索引

介绍

底层原理

注意事项


1.STL容器简单介绍

vector容器

        vector容器底层管理了一个动态开辟的空间,有三个指针start、finish以及end_of_storage费别指向开头,存储数据的结尾以及开辟的动态内存空间的结尾处。该容器可以自动的管理内存空间,当内存不足的时候,会自动的申请更大的空间,然后将数据拷贝到新的空间,并释放原有的内存。

        该容器对于尾部的插入和删除的操作时间复杂度为O(1),而对于中间元素的插入和删除操作需要进行数据的移动,所以时间复杂度为O(N)。对于访问元素来说,时间复杂度也是O(1),因为其内部重载了operator[],同时还支持随机迭代器,所以迭代器直接加上距离就可以了。

        模拟实现插入操作的过程中,迭代器失效的问题:在插入的时候,可能空间不足了,需要进行扩容处理,扩容的话是异地扩容,所以原来的迭代器指向的位置就被释放了,所以会有迭代器失效的问题,可以记录以下迭代器指向的元素在数组中的相对位置,扩容之后,更新迭代器的值即可。

        模拟实现删除操作的过程中,迭代器失效的问题:对于erase操作来说,它内部实现是将后面的元素直接覆盖在删除的位置,实现了删除的操作,如果我们想要连续删除的话,在使用迭代器的时候就不能够对迭代器进行++处理,因为对于连续删除的话,我们要删除的元素通过移动会一直处于迭代器的位置。

        模拟实现扩容操作的过程中,深层次的浅拷贝问题:当我们扩容的时候,不可以使用memcpy函数将元素拷贝到新的空间,因为memcpy函数是浅拷贝,他会将旧空间内部存放的内容拷贝给新空间,如果说旧空间存放的是string类型的数据的话,在释放旧空间数据的时候,string类型数据会调用他们的析构函数释放掉,所以新空间内部存放的string类型都是被析构的string对象了。

list容器

        该容器底层是一个带头双向循环链表,包含一个指针,指向头部位置,对于数据的个数是通过变量进行记录的。

        该容器是链表所以不涉及到扩容操作,而且链表的形式使得插入和删除元素的操作变得方便,时间复杂度为O(1),而查找或者访问指定元素的操作就麻烦了,需要遍历去查找访问元素。

        对于list,STL单独给他提供了一个sort是以为,list容器支持的是双向迭代器,而全局的sort函数需要传递的参数是随机迭代器,不符合要求,所以单独提供了一个支持双向迭代器的sort函数。

deque容器

        双端队列容器,支持随机访问,他底层是由多个固定大小的连续内存块组成的,每个内存块之间是通过指针进行关联起来的,相当于是一个指针数组,每一个指针又指向了一个空间。

        他的插入和删除操作对于头部和尾部来说时间复杂度是O(1)直接操作即可。但是对于中间的插入和删除操作,就和vector一样了需要进行数据的挪动,时间复杂度就变为了O(N),所以说对于双端的操作使用deque还可以。

forward_list容器

        是C++11引入的一个新的单向链表容器,支持的是单项迭代器,只能单向遍历。和list的底层原理基本上是一样的,只不过每一个节点都节省了一个指向前一个节点的指针。

set容器和map容器

        两者的底层实现都是红黑树,set是一个元素唯一的集合,而map则是键值对的集合同时也需要确保key值的唯一性。因为底层是红黑树,红黑树也是一种平衡二叉搜索树,所以会对插入的元素根据键自动排序,默认是升序序列。

        对于查找操作基于特定的二叉搜索树结构,所以会很快,时间复杂度为O(logN)。对于插入和删除操作的时候,可能会破坏红黑树的平衡结构,所以需要改变树的结构,那么就需要获取到一些其他节点的地址,也相当于查找,所以就是多个logn的时间,所以时间复杂度也是O(logN)。

multiset容器和multimap容器

        和上述的set、map相比最大的区别就是允许键值的重复。在插入的时候,不检查键值的唯一性。其他的都一样。

unordered_set容器和unordered_map容器

        底层是用哈希表来存储数据的,元素存储的数据是无序的。当数据量较小的时候,使用的是哈希桶结构,每一个桶也一个链表,当数据量过大,导致一个桶中的元素非常多,但是整体数据量还没有打破平衡因子的时候,就会将链表转化为红黑树结构了。

        查找的时间复杂度为O(1),插入和删除的时间复杂度为O(N)。

unordered_multiset容器和unordered_multimap容器

        和上述的唯一区别也是可以允许键值的重复。

容器适配器

        容器适配器是一种特殊的容器,他们本身不直接实现存储数据的功能,而是基于其他的容器,例如vector、list、deque等基础容器来实现一些操作。相当于把这些基础容器在进行了一次封装操作,通过这些基础容器的接口实现特定的功能。

        stack和queue就是容器适配器,他们两个底层默认都是使用的deque作为底层容器,模拟了栈和队列的相关操作。

        priority_queue是一个实现堆操作的容器,底层默认使用vector作为底层容器实现了一个大顶堆。

2.高并发实现原理

硬件层面:

        基于多核处理器与多线程技术,在现代的服务器中通常配备的是多核的处理器,允许系统同时运行多个线程,每一个核心都可以独立的处理一个线程的指令,通过多线程技术,将程序任务分解成多个子任务,分配到不同的核心上同时执行就可以了。例如,服务器接收了多个用户的请求,那么将每一个请求分配给线程池中的一个线程,这些线程在多核处理器上并行运行,就实现了高并发处理。

软件层面:

  • 异步非阻塞I/O模型:允许线程在发起I/O操作的时候,继续执行其他的任务,当I/O操作结束之后,通过回调函数或者事件通知机制来处理后续的相关操作。这样的话,可以让一个线程去同时执行多个用户的请求,而不用因为一个用户的I/O操作而进行线程等待了。在等待的过程中处理其他用户的请求。
  • 分布式系统与负载均衡:当服务器无法处理大量并发请求的时候,可以将一个大型应用服务拆分为多个小的服务,部署到不同的服务器上。在通过负载均衡器将客户不同的请求分配到不同的服务器上,提高整个系统的并发处理能力。

3.MySQL数据库索引

介绍

        索引的作用就是提高查询数据的效率。索引分为主键索引、唯一键索引、普通索引以及全文索引。

底层原理

        索引底层原理就是改变数据结构来实现提高效率。为一个是数据库表创建索引,底层就是创建了一个B+树结构,将这些数据组织起来,每一个叶子节点都是一个4KB的内存块结构,内部由数据链表,目录以及指向上一个和下一个内存块的指针构成。

        使用链表的结构管理数据是方便数据的插入和删除操作。同时提供目录,根据数据的数量,每隔一段区间就记录以下索引值与数据地址之间的映射关系,这些映射关系就是目录项。如果说数据量过大的话,一个内存块存放不下,就需要多个内存块存储了,多个内存块之间通过指针进行相互连接。但是如果数据量很大的话,遍历目录也很浪费时间,根据B+树的性质,可以对目录进行创建目录,通过遍历目录的目录就可以确定数据在哪一个目录里面了。这样创建出来的数据库文件就方便了用户的查询。

        查询的时候,会先读取B+树的根节点,通过根节点中目录记录的区间,一层一层向下找,最后确定所需要的数据在哪个叶子节点里面,将该节点的数据加载到内存中,这样的话,只需要非叶子节点以及少量的叶子节点加载到内存就可以了,提高了效率。

注意事项
  • 选择适合建立索引的列,如频繁查询列,如果是普通索引的话,前提是唯一性不能太差。
  • 更新频繁的列不适合作为索引,如果更新的话,该数据在B+树的位置就需要变化了,会连带的更改多个位置。
  • 一般不在where子句中出现的列不会作为索引列,如果一个列都不能作为筛选条件的话,说明该列对于查询指定数据并不能起到太大的一个作用了。而且索引结构也要占用空间的。

        

;