stl扩容:
vector相关:
vector扩容,linux下是2倍率扩容,vs是1.5倍扩容。扩容的时候,会将原先的数据区给释放掉,开辟一段新的内存区域,将所有的值复制到另一端内存当中。会造成迭代器失效的问题。1.5倍扩容机制有一个好处就是能够,复用之前释放的内存。
map与unordered_map的扩容机制:
在 C++ 中,std::map
是基于**红黑树(Red-Black Tree)**实现的关联容器,其底层数据结构与哈希表(如 std::unordered_map
)不同。因此,std::map
的“扩容”机制与哈希表的扩容有本质区别。
std::map
的实现基于平衡二叉搜索树(红黑树),其特性如下:
-
动态节点分配:每个键值对作为一个树节点(
Node
)动态分配在堆内存中。 -
自动平衡:插入或删除操作后,红黑树通过旋转和重新着色维持平衡,确保操作时间复杂度为 O(log N)。
-
无需显式扩容:树的节点按需分配,没有固定容量的概念,也不会像哈希表那样触发“扩容-重新哈希”的过程。
std::unordered_map扩容:
-
触发条件:
当插入元素后,元素数量超过负载因子(load factor) × 桶数量(bucket_count)
时触发扩容。-
默认负载因子为
1.0
,可通过max_load_factor(float)
调整。
-
-
扩容过程:
-
分配新的更大的桶数组(通常为原大小的两倍左右)。
-
将所有元素重新哈希到新桶中(时间复杂度 O(N))。
-
-
优化手段:
-
提前调用
reserve(n)
预分配足够的桶,避免多次扩容。
-
对比:
特性 | std::map (红黑树) | std::unordered_map (哈希表) |
---|---|---|
底层结构 | 平衡二叉搜索树 | 桶(数组) + 链表/红黑树(解决冲突) |
动态调整方式 | 节点动态分配,插入时调整树结构 | 桶数组扩容,重新哈希所有元素 |
时间复杂度 | 插入/删除/查找:O(log N) | 平均 O(1),最坏 O(N)(哈希冲突严重时) |
内存连续性 | 节点分散在堆内存中 | 桶数组连续存储,链表节点分散 |
扩容触发条件 | 无显式扩容,按需分配节点 | 元素数量超过 负载因子 × 桶数量 |
扩容代价 | 每次插入可能触发树的再平衡(局部调整) | 触发全局重新哈希,代价较高 |
private跟protected的区别
private:访问范围限定于成员类的内部。子类无法访问父类的private成员。外部类也不能访问。实现完全封装,隐藏实现的细节。
protected:定义在该成员的类内部。所有子类都可以访问。允许子类扩展父类的功能。
特性 | private | protected |
---|---|---|
类内部 | ✔️ | ✔️ |
子类 | ❌ | ✔️ |
同一包的其他类 | ❌ | ✔️(如 Java/C#) |
不同包的其他类 | ❌ | ❌(除非是子类) |
封装性 | 完全隐藏 | 部分暴露给子类和同一包 |
-
用
private
:隐藏实现细节(如敏感数据、工具方法)。 -
用
protected
:允许子类重写或扩展,但不向外界公开(如模板方法模式)。
map与unordered插入元素之后,迭代器会失效嘛?
map:红黑树结构,他的插入操作通过旋转和调整结点指针来维护平衡,不会改变已有节点的内存地址。即使插入的元素重新平衡了,迭代器也不会失效。唯一失效的场景就是删除元素(被删除元素的迭代器会失效)。
unorderedmap:底层结构是hash+桶或链表的方式。如果插入元素的时候导致扩容机制,那么所有的迭代器会失效。hash表会分配新桶并重新散列所有的元素,原有的布局会被破坏。如果没触发rehash的话,所有的迭代器都会有效,除了新元素所在的桶可能会失效
特性 | std::map | std::unordered_map |
---|---|---|
插入后迭代器失效 | 不会失效 | 仅当触发 rehash 时失效 |
底层结构 | 红黑树 | 哈希表 |
内存稳定性 | 高(节点地址固定) | 低(rehash 后地址全变) |
避免失效的策略 | 无需特殊处理 | 预分配桶(reserve() )避免 rehash |
总结
-
std::map
的插入操作是迭代器安全的。 -
std::unordered_map
的插入操作仅在触发rehash
时使迭代器失效,可通过预留桶容量规避。 -
在需要高频插入且依赖迭代器稳定性的场景中,优先选择
std::map
。
uniqueptr move到sharedptr会出现什么?
会导致所有权的转移和内存管理方式的改变。
unique_ptr
:独占资源所有权,不可复制,只能移动。
shared_ptr: 可以共享资源(通过计数器的方式)
移动后:unique_ptr的资源被转移到了shared_ptr当中,unique_ptr不再占有资源,被置为nullptr。变成有shared_ptr占有资源,并且有计数器管理生命周期。
将unique_ptr的资源转移到shared_ptr的话会导致,管理资源的成本变高,比如拷贝、赋值构造等等(因为有原子计数等等操作)。
一个注意点:转移的过程只能通过move进行转发。若直接复制的话,会报错!
应用场景
-
动态切换所有权模式:
初始使用unique_ptr
管理资源,后期需共享所有权时转为shared_ptr
。 -
工厂函数返回值扩展:
工厂函数返回unique_ptr
,调用者根据需要转为shared_ptr
:
行为/特性 | unique_ptr → shared_ptr 的转移 |
---|---|
所有权 | unique_ptr 释放所有权,shared_ptr 接管 |
unique_ptr 状态 | 变为 nullptr |
删除器 | 自动继承(若有自定义删除器) |
合法性 | 合法且安全 |
风险点 | 误用转移后的 unique_ptr (需立即停止操作) |
适用场景 | 需要从独占所有权切换到共享所有权的场景 |
vector的resize和reverse有什么区别?
resize:调整容器的大小。若新的大小>现有大小,那么新增的元素会被默认初始化(例如初始化为0),若新的大小<现有大小,多余的元素就会被删掉(超过新的大小的元素,触发析构函数)。总的来说可能会触发内存的重新分配。
reverse:预留内存空间,预先分配足够的内存(capacity),避免后续插入元素的时候多次扩容。如果参数》当前capacity的话,则会分配新内存,但是不创建新的元素(size大小不变)。若参数《当前capacity的话,通常无效果。只改变capacity,不改变实际的size大小。
注意:你通过resize分配内存后,通过index直接访问扩容位置的元素的时候,你是可以访问的(一般是初始化值)。那么如果你通过reverse去分配capacity去访问预定位置的元素的话,你会触发未定义事件,就报错。
特性 | resize(n) | reserve(n) |
---|---|---|
修改对象 | size() | capacity() |
新增元素 | 初始化新增元素 | 不创建元素 |
内存分配 | 可能触发(若超过当前容量) | 可能触发(若参数 > 当前容量) |
缩小操作 | 销毁多余元素 | 通常无效果 |
访问新空间 | 可直接访问(vec[i] 合法) | 需通过插入元素(如 push_back ) |
典型用途 | 需要立即拥有固定数量元素 | 优化性能,减少扩容次数 |
vector是存储在堆上还是栈上?
分两种情况,一种是通过new分配的对象,一种是不通过new分配的。
通过new分配的:不管是vector对象还是具体的资源都分配在堆上。
不通过new分配的:vector对象在栈上,但他的资源部分分配在堆上。
why?
因为栈的地址是不断增加的,但是你要分配资源的话,分配资源的这个动作是动态的,元素可能增加也可能减少,那么就不能满足栈的出栈和入栈的有序性。
-
动态扩容需求:
栈内存大小固定且有限,无法支持vector
运行时动态扩容(如push_back
时容量倍增)。 -
灵活性:
堆内存允许按需分配和释放,适合存储不确定数量的元素。
简化的vector示意图:
栈(Stack) 堆(Heap)
+----------------+ +----------------+
| vector 对象 | | 元素存储的数组 |
| - 指针 -------| -------> | [元素1][元素2]... |
| - 大小(size) | +----------------+
| - 容量(capacity)| +----------------+
情景 | vector 对象位置 | 元素存储位置 |
---|---|---|
局部变量(非 new ) | 栈 | 堆 |
动态分配(new ) | 堆 | 堆 |
如何防止DNS劫持?
什么是dns劫持:攻击者通过篡改DNS解析过程,将用户引导至恶意网站而非目标网站。
dns劫持的类型:
-
本地劫持
-
恶意软件修改设备DNS设置或Hosts文件,强制使用攻击者的DNS服务器。
-
-
路由器劫持
-
攻击者入侵路由器(如利用默认密码),篡改其DNS配置,影响所有连接设备。
-
-
中间人攻击(MITM)
-
在用户与DNS服务器间拦截并伪造响应,常见于不安全的公共WiFi。
-
-
DNS缓存投毒
-
向DNS服务器注入虚假记录,污染缓存,导致后续查询返回错误IP。
-
危害:1.诱导用户输入敏感信息(账户密码账号等等)。2.恶意软件传播(下载病毒或者勒索软件)3.广告欺诈(插入30秒复活的广告牟利)4.流量监控(窃取用户数据)
防范错书:
1.使用加密的dns服务(DNS over HTTPS (DoH) 或 DNS over TLS (DoT))加密dns查询,防止监听篡改。
2.启动DNS安全扩展(DNSSEC)通过数字签名验证响应真实性,防止缓存投毒
3.保护路由器安全:更改默认管理员密码,定期更新固件,禁用远程管理功能。
4.安装杀毒软件:定期检查C:\Windows\System32\drivers\etc\hosts hosts文件是否被修改。
什么是僵尸进程和孤儿进程,操作系统怎么处理这些进程?
僵尸进程:子进程终止,父进程还没有通过wait,waitpid系统调用读取他的退出状态的时候,这个子进程就会变成僵尸进程。
特点:进程描述符(PID)还存在于系统进程表当中,但不占用内存或者cpu。内核还会保存他的状态码等信息,直到父进程读取。
操作系统如何处理:操作系统会暂时保留僵尸进程的进程项,确保父进程能获得紫禁城的退出状态。
长期僵尸进程的风险:如果长期占用有限的PID号,大量的僵尸进程可能会导致PID号枯竭,导致系统无法创建新的进程。
如何避免:
父进程主动调用wait或者waitpid回收子进程资源。
父进程注册SIIGCHLD信号处理函数,在信号中调用wait
通过双fork技巧,让子进程fork实际任务进程并退出,让实际任务变成孤儿进程(交给初始进程收养)。
孤儿进程:当一个父进程先于子进程终止,子进程失去父进程,变为孤儿进程。
特点:孤儿进程仍在运行,但不再有父进程管理。操作系统会将孤儿进程的父进程重置为 init
进程(PID 1)。
操作系统如何处理:操作系统自动将孤儿进程的父进程设置为 init。init
进程会周期性地调用 wait()
回收孤儿进程的退出状态,避免其变为僵尸进程。孤儿进程终止后,会由 init
进程完成资源回收。
孤儿进程:通常无害,因为init进程会接管并且会调用wait回收孤儿进程的资源。但是孤儿进程长期运行,可能会占用资源,需要手动管理。
什么是init进程
init进程是类Unix操作系统(如Linux)中第一个用户空间进程(PID=1),由内核在系统启动时直接创建。它是所有其他进程的祖先,负责初始化系统环境、启动关键服务和管理系统运行状态,直到系统关闭。在linux当中一般书systemd,一般你想在开机初始化某个服务的话,你可以把你的脚本交由这个初始化进程管理。例如:systemctl enable httpd
(1) 系统初始化
-
启动系统服务:根据配置文件(如
/etc/inittab
或 systemd的单元文件),按顺序或并行启动系统关键服务(如网络、日志、SSH等)。 -
设置运行级别(Runlevel):传统SysV init通过运行级别(如0-6)定义系统状态(如多用户模式、图形界面、关机等),决定启动哪些服务。
(2) 进程管理
-
孤儿进程收养:当某个进程的父进程终止后,init进程会接管其子进程(孤儿进程),确保它们正常退出并回收资源。
-
僵尸进程清理:通过调用
wait()
回收已终止子进程的状态,防止僵尸进程堆积。
(3) 系统关闭与重启
-
协调关机流程:终止所有用户进程,卸载文件系统,同步磁盘数据。
-
发送信号(如
SIGTERM
、SIGKILL
)确保服务有序停止。
假如在某次远过程服务调用(并不一定是http)中,对方服务返回了connection refused,怎么排查这个问题?会用到那个命令?
在远程服务调用中出现 Connection refused
错误时,通常意味着客户端尝试连接目标服务的端口时,目标主机明确拒绝了连接请求。
那么可以从以下几个维度自查。
1.确认目标服务是不是在运行状态
可能的原因:目标服务未启动或者未指定监听端口。
# 查看服务进程是否运行(如Nginx、MySQL等)
systemctl status <service-name>
# 查看目标端口是否被监听(Linux)
netstat -tulnp | grep <端口号>
# 或使用更现代的 ss 命令
ss -tuln | grep <端口号>
# 检查特定进程是否监听端口(如Java服务)
lsof -i :<端口号>
如果服务未运行,启动服务;如果端口未被监听,检查服务配置(如配置文件、绑定的IP地址等)。
2.检查防火墙和安全组规则
可能是目标主机的防火墙或云平台安全组规则阻止了连接。
如果是这种错误的话,你可以关闭防火墙的相关规则。
3.网络连通性
可能是网络路由问题或中间设备(如路由器、负载均衡器)阻断了连接。
那么首先使用
tracert 检查整个路由的链路是否可达。
然后执行telenet 《目标ip》 《端口号》检查目标端口的连通性
4.检查dns解析:也可能是dns解析错误导致连到错误的ip了
# 查看域名解析结果
dig <域名>
nslookup <域名>
5.检查日志:
可能是服务端或中间件(如代理、负载均衡器)日志中可能有拒绝连接的记录。
命令表:
场景 | 命令示例 |
---|---|
检查端口监听 | ss -tuln | grep <端口> |
测试端口连通性 | nc -zv <IP> <端口> |
查看防火墙规则 | iptables -L -n -v |
跟踪路由路径 | traceroute <IP> |
检查DNS解析 | dig <域名> |