C++后端面试问题总汇
目录
- 1.C++基础
- 2.操作系统
- 3.计算机网络
- 4.数据库
- 5.场景题
C++基础
1. static 有什么应用场景
-
静态局部变量 :
局部的静态变量只能被初始化一次,多次调用函数操作的都是同一元素。作用:可用于追踪函数调用次数
-
静态全局变量:
static
全局变量的作用域被限制在声明它的文件内。即便其他文件使用extern
关键字尝试访问这个变量,也会因为找不到该变量的定义而失败。这可以用来避免命名冲突,并提供一种形式的封装。 -
静态函数:
仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命名空间里的函数重名,可以将函数定位为 static。(和全局变量一样限制了作用域而已)
-
静态类成员变量:
用static修饰类的数据成员实际使其成为类的全局变量,会被类的所有对象共享,包括派生类的对象。因此,static成员必须在类外进行初始化,而不能在构造函数内进行初始化。不过也可以用const 修饰static数据成员在类内初始化 。
-
静态类成员函数:
用static修饰成员函数,使这个类只存在这一份函数,所有对象共享该函数,不含this指针。
静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。不可以同时用const和static修饰成员函数。
2. extern 解决了什么问题
extern
用于修饰变量和函数,表明此变量和函数来自与其他文件,要在此处调用;- 当用到了C语言写的
dll
库时,可通过extern "C"
来导入
3. 菱形继承
- 问题:二义性、数据重复
- 解决:虚继承
4. 写一个 lambda 表达式
- 如依序排序:
sort(vec.begin(), vec.end(), [=](int x, int y){return x > y});
5. move 语义和完美转发
-
move语义
-
允许资源再不进行拷贝的情况下转发给另一个对象,从而避免不必要的拷贝,提升了效率。例如对于一个很大的
vector
,拷贝过程比较费时,我们只需要将他的权限转交给其他变量/对象例如:
vector<int> a; // 假设a已初始化,且有很多元素 vector<int> b = a; // 会将a中的元素一个个拷贝给b vector<int> c = move(a);// 通过move方法,直接让c接管a,此时a已经为空了,但a仍然存在
-
-
完美转发
-
右值引用是独立于值的,当一个右值引用作为函数参数的形参,且这个函数把他的形参转发给它内部所调用的其他函数时,此参数会变成一个左值,而不是原来的右值了
例如:
void f1(T& t){...}; void f2(T && t){ f1(t); // 此时 t 变成了一个左值 f1(forward<T>(t)); // 完美转发 t , f1中的 t 仍然是一个右值 }
-
6. 介绍一下 C++ 的 RAII
-
RAII 是 Resource Acquisition Is Initialization(资源获取即初始化)
利用类来管理资源,将资源与类对象的生命周期绑定,即在对象创建时获取对应的资源,在对象生命周期内控制对资源的访问,使之始终保持有效,最后在对象析构时,释放所获取的资源。
例如:
class Resource { public: Resource() { /* 获取资源 */ } ~Resource() { /* 释放资源 */ } }; void functionUsingRAII() { Resource resource; // 获取资源 // ... 使用资源 } // 函数结束,resource对象的生命周期结束,资源自动释放
7. C++ STL 迭代器的原理
- STL 中每种容器在实现的时候设计了一个内嵌的
iterator
类,不同的容器有自己专属的迭代器,使用迭代器来访问容器中的数据。 - 迭代器对指针的一些基本操作如 *、->、++、==、!=、= 进行了重载,使其具有了遍历复杂数据结构的能力,其遍历机制取决于所遍历的数据结构,所有迭代的使用和指针的使用非常相似。
8. 迭代器失效的情况
- 对于序列容器
vector
,deque
来说,使用erase
后,后边的每个元素的迭代器都会失效,后边每个元素都往前移动一位,erase
返回下一个有效的迭代器。 - 对于关联容器
map
,set
来说,使用了erase
后,当前元素的迭代器失效,但是其结构是红黑树,删除当前元素,不会影响下一个元素的迭代器,所以在调用erase
之前,记录下一个元素的迭代器即可。 - 对于
list
来说,它使用了不连续分配的内存,并且它的erase
方法也会返回下一个有效的迭代器,因此上面两种方法都可以使用
9. weak_ptr 怎么判断对象是否被销毁
weak_ptr
的expired()
成员函数用来判断它所管理的对象是否已经被释放,如果对象已经被销毁释放,expired()
将返回true
10. deque 的底层实现?如何扩容?
- 通常是通过一系列固定大小的数组块实现的,这些数组块在内存中可能不连续,但通过索引映射表来保持它们之间的逻辑连续性。当
deque
需要扩容时,它会分配新的数组块并将其添加到索引映射表中,而不是像vector
那样复制整个数据到一个更大的连续内存空间中,这也使得deque
能够在两端高效地添加或删除元素而不影响其它元素。
11. C++ 的编译过程
- 预处理:处理源代码文件中的预处理指令,如宏定义的展开、条件编译指令和头文件包含。
- 编译:将预处理后的源代码转换成汇编代码。
- 汇编:将汇编代码转换成机器码,生成目标文件(.obj 或 .o 文件)。
- 链接:将所有的目标文件和必需的库文件链接在一起形成最终的可执行文件。
12. 虚函数的原理
- 虚函数的原理基于一个叫做虚函数表(也叫虚表)的特殊数据结构,每个含有虚函数的类都有一个对应的虚表;每个对象实例都包含一个指向其类的虚表的指针(vptr)。当调用一个对象的虚函数时,程序会通过这个指针查找虚函数表,从而找到正确的函数实现来执行,这就支持了运行时多态,即允许在运行时根据对象的实际类型来调用适当的函数版本。
- 虚表:是类的所有对象共有的。每个类只有一个虚表,这个表在编译时被创建,并且所有该类的对象都包含一个指向这个共享虚表的指针(vptr)。当类的对象被创建时,每个对象的
vptr
会被初始化指向它所属类的虚表。虽然虚表是共享的,但每个对象都有自己的vptr
,确保了即使是同一个类的不同对象,如果它们属于不同的派生类,也能通过各自的vptr
找到正确的虚函数实现。
13. unique_ptr 的原理
-
unique_ptr
的原理基于所有权模型,它包含对动态分配资源的唯一所有权,确保资源只能通过一个unique_ptr
实例来管理。当该unique_ptr
实例被销毁或其所有权被转移时,它会自动释放其所管理的资源。这种智能指针不支持拷贝,但支持移动语义,允许资源所有权的安全转移,从而防止资源泄漏和重复释放。std::unique_ptr<Type> ptr1(new Type); std::unique_ptr<Type> ptr2 = std::move(ptr1);
14. vector 如何扩容,new 还是 malloc?
std::vector
进行扩容时通常会分配一个更大的连续内存块来存储元素,这个过程是通过调用分配器的allocate
函数来完成的。分配器通常使用::operator new
来分配内存,而不是malloc
,因为**new
除了分配内存,还会调用对象的构造函数来初始化对象。**- 当
std::vector
需要扩容时,它会按照其实现定义的增长策略(通常是当前容量的1.5倍或2倍)来确定新的容量大小,然后使用分配器来分配新的内存块,并将现有的元素移到新的内存块中。
15. 引用传递和指针传递的区别
- 引用传递与指针传递的主要区别在于引用是别名,它必须被初始化且不能重新绑定到另一个对象,而指针是一个实际的对象,可以重新指向不同的地址。
- 引用更安全易用,因为它保证了引用的有效性,且不需要检查空值;指针提供了更灵活的间接寻址和动态内存管理的能力,但使用时需要更多的注意以避免诸如解引用空指针之类的错误。
16. 内存越界
- 内存越界是指访问数组、指针或其他数据结构时,读取或写入的内存地址超出了为该数据结构分配的内存范围。这是一种常见的编程错误,可能导致各种问题,包括数据损坏、程序崩溃、不可预测的行为,甚至安全漏洞。
- 避免内存越界的最佳做法是使用C++标准库提供的数据结构和算法,如
vector
和array
,它们提供了边界检查的方法。在手动处理指针和数组时,应该仔细确保所有的索引都在合法范围内,并且在必要时进行边界检查。
17. 为什么不能虚构造
- 对象实例化时的类型确定性:当创建一个对象时,需要知道对象的确切类型以分配适当大小的内存。虚函数的调用依赖于对象的虚表指针(vptr),而这个指针是在对象内存分配后、构造函数执行前设置的。如果构造函数是虚的,那么在构造之前,还没给对象分配内存,就产生了悖论。(内存空间对于构造函数,引入了先有鸡先有蛋的问题)
- 多态性的需求:多态性是在对象生命周期中后期才需要的,它允许通过基类指针或引用来调用派生类的方法。而在对象构造阶段,对象的类型是明确的,因此不需要多态性。
18. 基类如何禁止子类重写某个函数
-
将该函数标记为
final
。这个关键字可以在成员函数声明的尾部使用,来指示该函数不可以在派生类中被重写例如:
class Base { public: virtual void someFunction() final; // 这个函数不能被派生类重写 };
-
引入一个新问题:一个
final
的纯虚函数有什么意义?(个人觉得没啥意义)
19. 空类有哪些函数
- 默认构造函数
- 析构函数
- 拷贝构造函数
- 拷贝赋值操作符
- 移动构造函数(C++11及以后版本)
- 移动赋值操作符(C++11及以后版本)
20. STL 有哪几部分组成?
- **容器:**一种数据结构,如 vector、list、deque、map/multimap、set/multiset;
- **算法:**操作容器中数据的模板函数;
- **迭代器:**提供了访问容器中对象的方法。例如,可以使用一对迭代器指定 list 或 vector 中的一定范围的对象。 迭代器就如同一个指针。事实上,C++ 的指针也是一种迭代器。 迭代器可看成是对指针的封装;
- **仿函数:**重载了
()
操作符,使之具有函数的表现形式; - **适配器:**封装了一些基本的容器,使之具备了新的函数功能,比如
deque
封装得到的stack
、queue
, 和vector
封装得到的priority_queue
(优先队列是以vector为基础,然后通过算法对其排序); - **空间置配器:**为 STL 提供空间配置的系统。其中主要工作包括两部分: (1)对象的创建与销毁;(2)内存的获取与释放。
21. 智能指针用什么方式避免内存泄漏
- 它们使用RAII(Resource Acquisition Is Initialization)原则,将保资源的获取与对象的生命周期绑定。
22. 说说new和malloc的区别,各自底层实现原理。
-
区别
- new 是操作符,而 malloc 是函数;
- new 在调用的时候先分配内存,再调用构造函数,释放的时候调用析构函数;而 malloc 没有构造函数和析构函数;
- malloc 需要给定申请内存的大小,返回的指针需要强转;new会调用构造函数,不用指定内存的大 小,返回指针不用强转。
- new 可以被重载;malloc 不行;
- new 分配内存更直接和安全;
- new 发生错误抛出异常,malloc 返回 null。
-
malloc底层实现:当开辟的空间小于 128K 时,调用 brk()函数;当开辟的空间大于 128K 时,调用 mmap()。malloc采用的是内存池的管理方式,以减少内存碎片。先申请大块内存作为堆区,然后将堆区分为多个内存块。当用户申请内存时,直接从堆区分配一块合适的空闲快。采用隐式链表将所有空闲块连接起来,每一个空闲块记录了一个未分配的、连续的内存地址。
-
new底层实现:
关键字new在调用构造函数的时候实际上进行了如下的几个步骤:
- 创建一个新的对象
- 将构造函数的作用域赋值给这个新的对象(因此this指向了这个新的对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
操作系统
1. 零拷贝是否听说过
-
概念:是一种减少CPU拷贝次数,提高数据传输效率的技术。核心原理是尽量避免用户空间和内核空间之间进行不必要的数据拷贝
-
主要有:
-
mmap
通过
mmap()
系统调用,用户空间的程序可以将文件映射到其虚拟地址空间,从而直接访问磁盘上的数据; -
sendfile
通过
sendfile()
系统调用,可以直接在内核空间中将数据从一个文件描述符发送到另一个文件描述符。常用于网络通信。
-
2. 说说 select 工作流程
-
select
是使用一个位图(上限1024
)来维护文件描述符集合fd_set read_fds; // 创建 bitmap,用以维护 fd 集合 FD_ZERO(&read_fds); // 初始化 bitmap FD_SET(fd, &read_fds); // 设置位图上的 fd,例如 fd = 3、5,则 read_fds = 00010100...00; FD_ISSET(fd, &read_fds); // 判断 read_fds 上是否设置了fd
-
调用
select(max_fd+1, &read_fds, NULL, NULL, NULL)
-
首先,会将维护的
bitmap
拷贝到内核空间,然后又内核去判断哪一个fd
有数据到达。(为什么在内核态判断?①内核态高效;②用户态无权限)
-
接着,内核会将整个
bitmap
从内核态拷贝回用户态,并且返回就绪的fd
数量。此时,我们只知道有多少个fd
就绪,并不知道是哪几个fd
就绪, 因此,还需要遍历以便这张bitmap
(判断时用FD_ISSET
)
由上:我们每次监听
fd
集合,都需要调用一次select
将fd
集合从用户空间拷贝到内核空间去判断,后续还需要将fd
集合从内核空间拷贝回用户空间,接着还需要在用户态进行遍历,是非常低效的。 -
3. 说说 poll 工作流程
-
大致和
select
类似,亮点是 定义了一个poolfd
结构体struct pollfd{ int fd; // 文件描述符 short events; // 注册的事件 short revents; // 实际发生的事件 }
pool
使用了一个pollfd
数组存储每个fd
,所以没有了1024的限制。相比于位图,由于可以通过下标访问,pollfd
可以单个初始化而无需统一初始化。
4. 谈谈 epoll
-
结构:红黑树(存文件描述符)+ 双向链表(存就绪 fd)
-
流程:
-
首先调用
epoll_create
,创建一个epoll
对象(包含一个空的红黑树和一个空的双向链表); -
然后通过
epoll_ctl
将需要监听的文件描述符注册到红黑树(也可以删除、修改),每次调用epoll_ctl
,都会为注册的文件描述符分配一个epitem; -
最后调用
epoll_wait
。内核会先去检查就绪链表中是否有就绪事件,若有,epoll_wait
立刻返回且不阻塞;若无,则将本进程阻塞,置于等待队列wq
中。当有数据到达,可通过调用 回调函数(非遍历)将就绪事件插入就绪链表中。然后 OS 检查
wq
中是否有阻塞进程,若此进程刚好需要执行就绪链表中fd
的事件,则将其唤醒。 -
优势
- epoll_ctl 函数中,为每个 fd 指定了回调函数,基于此回调函数就可以以 O(1) 时间复杂度将就绪事件加入就绪链表;
- epoll_ctl 时传递一次 fd,后续 epoll_wait 时无需再传 fd;
- epoll 基于红黑树与就绪链表,无连接数量的限制。
-
劣势
- 占用较大空间
具体内容请参考链接: https://zhuanlan.zhihu.com/p/552580039
5. epoll 为什么采用红黑树,不用 hash 和 b+ 树
hash
需要预置内存空间,而我们无法预知客户端数量;且红黑树查询效率稳定,hash
会有哈希冲突的问题;b+
树是多叉树,适用于磁盘索引,不适用于内存;
6. epoll 一定 比 poll 快吗?
- 不一定,当并发量很小,遍历数组也很快,此时
epoll
调用回调函数,将就绪事件插入就绪队列就显得很慢了。
7. TCP 和 UDP 能使用同一端口号吗?
- 可以。对于TCP和UDP来说,尽管它们作为传输层的协议共享相同的端口号空间,但它们的端口是独立管理的。这意味着TCP和UDP可以使用相同的端口号而不会相互冲突。例如,TCP的80端口通常用于HTTP服务,而UDP的80端口可以被另一个服务使用,且两者不会相互干扰。实际上,在操作系统中,TCP和UDP端口是分别维护和管理的,因此它们可以独立地使用相同的端口号。
8. 死锁?如何解决死锁?
-
概念:
死锁是多线程或多进程环境中的一种情况,其中一组进程或线程都在等待其他进程或线程释放资源,或者是在等待某些条件得到满足,而这些条件永远不会满足,导致所有进程或线程都无法继续执行。
-
**条件:**互斥、请求和保持、不可剥夺、循环等待
-
解决:
-
**预防:**通过锁机制来实现
-
避免:在资源分配时使用算法(如银行家算法)来避免系统进入不安全状态。
(银行家算法通过模拟资源分配和预测可能的安全状态来避免死锁,确保系统不会进入无法满足所有进程资源需求的不安全状态。)
-
**检测和解除:**检查到死锁,可以通过剥夺资源、终止进程或回滚操作等方式来解除死锁。
-
9. 进程、线程、协程的区别?
-
概念:
-
进程:进程则是程序的运行实例 ;
-
**线程:**微进程,一个进程里包含多个线程并发执行任务;
-
**协程:**协程是微线程,在子程序内部执行,可在子程序内部中断,转而执行别的子程序,在适当的时候再返回来接着执行。
-
-
区别:
- 线程与进程的区别:
- 一个线程从属于一个进程;一个进程可以包含多个线程;
- 一个线程挂掉,对应的进程可能挂掉;一个进程挂掉,不会影响其他进程;
- 进程是系统资源调度的最小单位;线程 CPU 调度的最小单位;
- 进程系统开销显著大于线程开销;线程需要的系统资源更少;
- 进程在执行时拥有独立的内存单元,多个线程共享进程的内存,如代码段、数据段、扩展段;但每个线程拥有自己的栈段和寄存器组;
- 进程切换时需要刷新 TLB 并获取新的地址空间,然后切换硬件上下文和内核栈,线程切换时只需要切换硬件上下文和内核栈;
- 通信方式不一样;
- 进程适应于多核、多机分布;线程适用于多核 。
- 线程与协程的区别:
- 协程执行效率极高。协程直接操作栈基本没有内核切换的开销,所以上下文的切换非常快, 切换开销比线程更小;
- 协程不需要多线程的锁机制,因为多个协程从属于一个线程,不存在同时写变量冲突,效率比线程高;
- 一个线程可以有多个协程。
- 线程与进程的区别:
10. 共享内存
-
共享内存是一种进程间通信(IPC)机制,它允许两个或多个进程共享同一块物理内存区域。共享内存是最快的 IPC 方法之一,因为它避免了数据的复制,直接允许进程访问同一内存地址空间。可以通过操作系统提供的API来实现。在 Linux 系统中,可以使用
shmget()
,shmat()
,shmdt()
, 和shmctl()
-
使用共享内存时,通常需要解决同步问题,以确保进程不会同时写入共享内存造成数据损坏。可以使用互斥锁、信号量或其他同步机制来保护共享内存的访问。
11. 进程、线程通信同步
-
通信:
-
进程通信:管道、系统IPC(消息队列、信号、信号量、共享内存)、套接字;
-
线程通信:互斥锁、信号量、条件变量、读写锁;
-
-
同步:
-
进程同步:信号量、管道、消息队列
-
线程同步:临界区、互斥量、信号量、条件变量、读写锁
-
12. 内存池
-
概念:
内存池(Memory Pool)是一种内存分配策略,它事先从操作系统中申请一大块内存空间,然后按需从这块空间中分配小块内存给程序使用。内存池可以减少频繁调用系统API申请和释放内存所带来的开销,同时减少内存碎片。
-
内存池的优点:
- 性能提升: 由于减少了系统调用的次数,内存分配的速度通常比直接使用
malloc
或new
快。 - 内存碎片减少: 统一管理内存分配可以减少外部碎片。
- 内存分配控制: 更容易跟踪内存使用情况,对于调试和性能分析有好处。
- 性能提升: 由于减少了系统调用的次数,内存分配的速度通常比直接使用
-
内存池的缺点:
- 内存浪费: 如果内存池的大小不能很好地匹配实际需求,可能会导致内存浪费。
- 内部碎片: 内存池可能会产生内部碎片,特别是当它分配了很多不同大小的内存块时。
- 复杂性: 实现和维护一个内存池比直接使用标准内存分配方式要复杂。
-
内存池的实现方式:
- 预分配: 事先分配一大块内存,然后根据需要划分给不同的对象使用。
- 内存对齐: 为了提高访问速度,内存池通常会进行内存对齐操作。
- 内存回收: 对于不再使用的内存块,内存池需要有一种机制能够回收并重新利用这些内存。
-
内存池的应用场景:
- 对象池: 用于频繁创建和销毁的小对象。
- 游戏开发: 游戏中常常需要快速地分配和释放内存来处理大量的游戏实体。
- 实时系统: 在实时系统中,内存分配的时间要求确定性,内存池可以提供这种确定性。
13. 了解哪几种锁?
- **互斥锁:**互斥锁加锁失败后,线程会释放CPU,给其他进程使用;
- **自旋锁:**自旋锁加锁失败后,线程会一直等待,直到它拿到锁;
- **读写锁:**共享读,互斥写
- 悲观锁:悲观锁默认会出现冲突,所以访问共享资源前,先要上锁
- **乐观锁:**默认不会出现冲突,乐观锁全程不加锁。机制是:先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。乐观锁虽然去除了加锁解锁的操作,但是一旦发生冲突,重试的成本非常高,所以只有在冲突概率非常低,且加锁成本非常高的场景时,才考虑使用乐观锁。
14. 说说动态库和静态库
- 静态库代码装载的速度快,执行速度略比动态库快;
- 动态库更加节省内存,可执行文件体积比静态库小很多;
- 静态库是在编译时加载,动态库是在运行时加载;
- 生成的静态链接库,Windows 下以
.lib
为后缀,Linux 下以.a
为后缀。生成的动态链接库,Windows 下以.dll
为后缀,Linux 下以.so
为后缀。
15. Linux下怎么查看Linux服务器的性能指标
- **top:**提供一个实时更新的系统状态视图,包括CPU、内存使用情况,以及进程信息;
- **htop:**彩色的 top ;
- **iostat:**为系统输入/输出设备提供统计信息,可以帮助识别I/O瓶颈;
- free: 显示内存使用情况,包括物理内存、交换空间等;
- netstat: 显示网络接口的统计信息,如网络连接、路由表、接口统计等。
16. 一个进程能开多少个线程、协程?
-
线程:
32
位计算机,4G
内存中,内核占用1G
,一个线程大小大概8M
,所以最多大概有3G / 8M = 375
个线程。但实际会比这个数小一点,因为程序本身占内存,还有些管理线程 -
协程:
同理,一个协程大概几时到几百KB大小,上限
< 8M / 协程大小
17. Linux 中能开多少进程
[ubuntu@melon ~]$ ulimit -u # 查看进程上限指令
4096
18. 软硬链接?
-
**软链接:**也叫符号链接,是包含了源文件的位置信息的特殊文件。他间接指向一个文件或目录,当文件删除或者移动和,软连接会失效;
-
**硬链接:**通过索引节点(
inode
)进行链接。Linux 中每个文件都有一个对应的索引节点,记录文件的日期、大小、所在块等信息。硬链接即指向其索引节点的链接。当源文件删除,不影响硬链接的使用。(当删除一个文件时,实际上是移除了文件名与
inode
之间的映射关系,并且减少了inode
的链接计数。只有当inode
的链接计数降到0
时,文件系统才会认为文件被完全删除,并且释放inode
所指向的数据块供其他文件使用。但是只要硬链接存在,inode
就不会变为0
。) -
区别:
- 软链接是个独立的文件。硬链接是源文件的引用,不占空间;
- 软链接可跨分区,硬链接不可以跨文件系统;
- 软链接可以链接目录,硬链接不可以。
计算机网络
1. TCP、UDP、IP 头部常用字段和占用字节
-
TCP:头部长 20 个字节
-
包括:源端口号(2B)、目标端口号(2B)、序列号(4B)、确认号(4B)、头部长度(4位)、
保留位(6位)、标志位(6位)、窗口大小(2B)、校验和大小(2B)、紧急数据偏移量(2B)
-
-
UDP:头部长 8 个字节
- 包括:源端口号(2B)、目标端口号(2B)、数据包长度(2B)、校验和(2B)
-
IP:头部长 20 字节固定长度 + 可变长部分(<=40字节)
-
包括:版本号(4位)、首部长度(4位)、服务类型(1B)、总长度(2B)、标识(2B)、标志(4位)、
片偏移(12位)、生存时间(1B)、协议(1B)、首部校验和(2B)、
源地址(4B)、目标地址(4B)、可变长部分
-
2. TCP 的流量控制和拥塞控制
-
流量控制:
- 滑动窗口
-
拥塞控制
-
慢启动:
滑动窗口指数增加;
-
拥塞避免:
当滑动窗口达到阈值,由指数增加变为每次加1;
(规定:慢启动、拥塞避免时发生网络拥塞,即更新滑动窗口阈值为滑动窗口大小的一半,
同时让滑动窗口更新为1,接着重复慢启动和拥塞避免算法)
-
快重传:
当TCP报文段丢失或者接收端收到乱序的TCP报文段等情况下,发送端都会收到重复的确认报文。当发送方连续收到三个重复的确认报文段时,发送方认为网络拥塞了,然后会立即重传丢失的报文;
-
快恢复:
当发送方连续收到三个重复的确认报文段时,会更新滑动窗口阈值为滑动窗口大小的一半,此后不会执行慢启动算法,而是将滑动窗口更新为新的窗口阈值大小,再执行拥塞避免
-
3. 说说 Nginx 的正向代理、反向代理及负载均衡
-
直接看大佬博客
-
简单总结:
- 正向代理:对于
目标服务器
来说,客户端
是隐藏的 - 反向代理:对于
客户端
来说,服务器
是隐藏的 - 负载均衡:是
结合反向
代理来实现的,将客户端请求均发给集群服务器
- 正向代理:对于
4. 说说 GET请求和 POST 请求的区别
- GET 请求在 URL 中传送的参数是有长度限制的,而POST没有。
- GET 比 POST 更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息。
- GET 参数通过 URL 传递,POST 放在 Request body 中。
- GET 请求参数会被完整保留在浏览器历史记录里,而 POST 中的参数不会被保留。
- GET 请求只能进行 url 编码,而 POST 支持多种编码方式。
- GET 请求会被浏览器主动 cache,而 POST 不会,除非手动设置。
- GET 产生的 URL 地址可以被 Bookmark ,而 POST 不可以。
- GET 在浏览器回退时是无害的,而 POST 会再次提交请求。
5. TCP 和 UDP 的区别
- TCP协议是有连接的,有连接的意思是开始传输实际数据之前TCP的客户端和服务器端必须通过三 次握手建立连接,会话结束之后也要结束连接。而UDP是无连接的;
- TCP协议保证数据按序发送,按序到达,提供超时重传来保证可靠性,但是UDP不保证按序到达, 甚至不保证到达,只是努力交付,即便是按序发送的序列,也不保证按序送到;
- TCP协议所需资源多,TCP首部需20个字节(不算可选项),UDP首部字段只需8个字节。;
- TCP有流量控制和拥塞控制,UDP没有,网络拥堵不会影响发送端的发送速率了;
- TCP是一对一的连接,而UDP则可以支持一对一,多对多,一对多的通信;
- TCP面向的是字节流(无边界)的服务,UDP面向的是报文(有边界)的服务。
6. TCP 的 time_wait 状态是什么,close_wait?
-
time_wait:
当一个端点执行主动关闭,并发送了最后一个ACK后(即第四次挥手后),这个端点就会进入
TIME_WAIT
状态。这个状态通常会持续一段时间(2MSL),以确保对方收到了最后的ACK
。TIME_WAIT
可以确保连接被正确关闭,并允许旧的重复分段在网络中消失。弊端:
- 短期大量资源释放,端口资源耗尽;
time_wait
保持连接会占用一定资源,影响服务器性能;- 影响网络性能
代码中解决:
- 设置套接字为地址复用,通过设置套接字的
SO_REUSEADDR
属性
-
close_wait:
CLOSE_WAIT
是另一个TCP
状态,当一个端点收到另一端点的FIN
报文,表明对方想要关闭连接时,它就会发送一个ACK
并进入CLOSE_WAIT
状态。在CLOSE_WAIT
状态下,等待本地用户关闭连接(即第四次挥手,发送 ACK 包)。如果本地用户不关闭连接,那么这个端点将一直停留在CLOSE_WAIT
状态(如果超时了未收到对方回复的ACK
,则自动断连)。
7. Time_wait 为什么要 2MSL
-
MSL(Maximum Segment Lifetime,最大报文段生存时间)
这 2个MSL 中的第一个 MSL 是为了等自己发出去的最后一个 ACK 从网络中消失,而第二 MSL 是为了等在对端收到ACK 之前的一刹那可能重传的 FIN 报文从网络中消失。以此避免:当新的连接在相同的端口和IP地址上建立时,旧的报文段错误地被当作新连接的一部分。
8. HTTP 各个版本的区别
- HTTP/0.9:功能简陋,只支持
GET
方法,只能支持HTML
格式字符串; - HTTP/1.0:增加了
POST
等方法,增加了头信息,每次只能发送一个请求(无持久连接),且支持多种数据格式; - HTTP/1.1:默认持久连接、请求管道化、增加缓存处理、增加Host字段、支持断点传输分块传输等;
- HTTP/2.0:二进制分帧、多路复用、头部压缩、服务器推送(允许服务器未经请求,主动向客户端发送资源);
具体参考:https://blog.youkuaiyun.com/qq_40860852/article/details/93632106
9. HTTP 和 HTTPS
-
HTTP:
超文本传输协议。一种用于从 Web 服务器传输超本文到本地浏览器的协议,定义了服务器和客户端之间请求和响应的格式,是一种无状态的协议,每个请求皆独立。
-
HTTPS:
在 HTTP 的基础上加入了 SSL/TLS 层
-
区别:
-
安全性:
- HTTP 是不安全的,它以明文形式传输数据,这意味着数据可以被中间人攻击。
- HTTPS 通过使用
SSL/TLS
(安全套接层/传输层安全)协议加密数据,提供了进行加密传输、身份认证。这使得HTTPS 比 HTTP 更安全,因为它可以防止数据被中间人读取或修改。
-
端口:
- HTTP 默认使用端口号
80
。 - HTTPS 默认使用端口号
443
。
- HTTP 默认使用端口号
-
证书:
-
HTTPS需要使用
SSL/TLS
证书。这些证书由证书颁发机构颁发,用于验证服务器的身份并建立安全连接。 -
HTTP不需要证书。
-
-
URL格式:
-
HTTP的URL以
http://
开头。 -
HTTPS的URL以
https://
开头。
-
-
10. 常见的 HTTP 请求有哪些
- GET: 请求获取指定资源。
- POST: 提交数据进行处理,通常用于提交表单。
- PUT: 请求服务器存储一个资源,通常要指定存储的位置。
- DELETE: 请求服务器删除指定资源。
- HEAD: 类似于 GET 请求,但服务器只返回头部信息,不返回实际内容。
- OPTIONS: 请求获取服务器支持的 HTTP 方法。
11. GET 和 POST 的区别
- GET 请求在 URL 中传送的参数是有长度限制的,而 POST 没有;
- GET 比 POST 更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息;
- GET 参数通过 URL 传递,POST 放在 Request body 中;
- GET 请求参数会被完整保留在浏览器历史记录里,而 POST 中的参数不会被保留;
- GET 请求只能进行 url 编码,而 POST 支持多种编码方式。
- GET 请求会被浏览器主动 cache,而 POST 不会,除非手动设置。
- GET 产生的 URL 地址可以被 Bookmark,而 POST 不可以。
- GET 在浏览器回退时是无害的,而 POST 会再次提交请求。
12. 说说 HTTP 状态码
- **1xx:**指示信息,标识请求已受理,继续处理;
- **2xx:**成功,表示请求已被成功接受和处理;
- **3xx:**重定向,表示完成请求,需要更进一步操作;
- **4xx:**客户端错误,请求语法错误或请求未能实现;
- **5xx:**服务端错误,服务器未能实现合法请求。
13. CSRF 攻击?
-
概念:
CSRF
全称叫做跨站请求伪造。就是黑客可以伪造用户的身份去做一些操作,进而满足自身目的。 要完成一次CSRF
攻击,受害者必须依次完成两个步骤:- 登录受信任网站 A,并在本地生成
Cookie
- 在不登出 A 的情况下,访问危险网站 B
- 登录受信任网站 A,并在本地生成
-
解决方法:
请求令牌验证(
token
验证)。服务器会生成一个随机的字符串保存在session
中,并作为令牌(token)返回给客户端,以隐藏的形式保存在客户端中,客户端每次请求都会带着这个token
,服务器根据该token
判断该请求是否合法
14. SYN 攻击
-
概念:
利用 TCP 协议的三次握手过程。攻击者在不断地发送伪造的 SYN(同步)请求给目标服务器,但是不完成后续的握手过程(即不发送 ACK 包来完成连接),导致服务器为了等待完成握手而保留资源,从而耗尽服务器的连接资源,使合法用户无法建立连接。
-
解决方法:
- 启用
SYN Cookies
来验证合法连接请求; - 配置防火墙规则过滤异常流量;
- 减少半开连接超时时间;
- 限制来自单一 IP 的连接请求数量;
- 启用
15. DNS 劫持呢?
- DNS 劫持就是通过劫持了 DNS 服务器,通过某些手段取得某域名的解析记录控制权,进而修改此域名的解析结果,导致对该域名的访问由原IP地址转入到修改后的指定 IP,其结果就是对特定的网址不能访问或访问的是假网址,从而实现窃取资料或者破坏原有正常服务的目的。
16. 数据链路层和网络层分别通过什么寻址
- 数据链路层:通过
MAC
地址进行寻址,这是嵌入在网络接口卡(NIC)中的唯一标识符; - 网络层:通过
IP
地址进行寻址,它帮助在互联网上不同网络间路由数据包到达正确的目的地.
17. 客户端信息发给服务端,需要经过哪些协议?
- **应用层协议:**如 HTTP(用于网页访问)、HTTPS(HTTP的安全版本)、FTP(用于文件传输)、SMTP(用于发送邮件)等;
-
**传输层协议:**主要有 TCP 和 UDP;
-
**网络层协议:**主要是 IP 协议(负责将数据包从源主机发送到目标主机),可能还包括 ICMP(用于发送控制消息)和IGMP(用于多播流量管理)等。
-
**数据链路层协议:**如以太网协议(Ethernet),负责在同一网络段内的设备之间传输数据帧。
-
**物理层:**涉及实际的传输介质和硬件设备,如双绞线、光纤、无线电波等,以及相应的物理层协议标准。
18. 什么是网关,他的作用是什么?
- 网关是实现不同网络之间通信和数据传输的关键节点,通常用于将不同协议、不同数据格式或不同架构的网络连接起来,确保信息能够正确无误地在网络之间传递。
19. 为什么要三次握手,两次行不行?
- 为了实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤
- 如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认
20. 访问一个域名会发生什么事
- DNS解析:找到服务器 IP;
- 建立 TCP 连接;
- 发送 HTTP 请求;
- 服务器处理客户端 HTTP 请求;
- 渲染客户端浏览器界面
21. HTTP的长连接如何实现的
-
原理:
它允许在同一个 TCP 连接上发送和接收多个 HTTP 请求/响应,而不需要为每一个新的请求开启一个新的连接;
-
实现:
在 HTTP 头部信息中使用
Connection
字段。在 HTTP/1.1 中,默认行为是使用长连接,但是可以通过设置Connection: close
来告诉服务器,客户端或服务器在完成本次响应后关闭连接。如果希望明确指出使用长连接,可以设置
Connection: keep-alive
。
22. tcp 传输数据时会有粘包问题,为何会发生,如何解决?
-
原因:
TCP粘包问题发生是因为TCP是一个面向流的协议,数据的发送和接收是连续的字节流而没有固定边界
-
解决:
应用层引入消息边界,例如使用特殊的分隔符、在消息前添加长度字段或实现一个包协议等,以确保数据的正确分割和处理。
23. UDP 如何保证可靠传输的,怎么实现
- UDP并不保证可靠传输。可以在应用层实现一些额外的机制来增加UDP传输的可靠性
- **确认和重传:**发送方在发送数据后等待接收方的确认(ACK)。如果在预定的超时时间内未收到确认,则重传数据。接收方收到数据后发送 ACK 表示确认收到;
- **序列号:**为每个数据包分配一个序列号。接收方可以用序列号检查数据包的顺序,以确保数据的顺序正确,并且可以检测到丢失的数据包;
- **校验和:**在数据包中添加校验和(checksum)来检测数据在传输过程中是否发生了错误。如果接收方检测到错误,可以请求发送方重传;
- 流量控制、拥塞控制、超时重传 …
数据库
声明:部分答案参考 知识星球
1. mysql 事务?常见的隔离级别,可能出现的问题
-
概念:
事务是逻辑上的一组操作,要么都执行,要么都不执行。在事务中可以包含SQL语句(如
INSERT
、UPDATE
、DELETE
等)和控制语句(如START TRANSACTION
、COMMIT
、ROLLBACK
等)。START TRANSACTION; -- 开始事务 -- 检查学生当前的学分是否少于20 SELECT @current_credits := credits FROM students WHERE id = 1; -- 如果学分少于20,则增加3个学分 IF @current_credits < 20 THEN UPDATE students SET credits = credits + 3 WHERE id = 1; COMMIT; -- 提交事务,确认更改 ELSE ROLLBACK; -- 回滚事务,取消更改 END IF;
-
隔离级别:
- 读取未提交(READ-UNCOMMITTED) :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- 读取已提交(READ-COMMITTED) :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- 可重复读(REPEATABLE-READ) :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- 串行化(SERIALIZABLE) :最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
-
问题:
- **脏读:**事务A执行时,读取了事务B修改的数据,此时AB都在执行期间,后来B出现问题回滚,A读取了B误修改的值;
- **不可重复读:**事务A执行期间,事务B修改了某个数据,事务B结束后,事务A读取了B所修改的值。此时事务A继续执行,事务B再次执行修改了这个值,事务B结束后A再去读,发现所读数据不一致;
- **幻读:**事务A执行期间读取了某些值,接着事务B增加或删除了这部分值,此时A再去读,发现数据多了或少了某些部分。
2. MVCC 说一下
-
概念:
MVCC 是一种并发控制机制,用于在多个并发事务同时读写数据库时保持数据的一致性和隔离性。它是通过在每个数据行上维护多个版本的数据来实现的。当一个事务要对数据库中的数据进行修改时,MVCC 会为该事务创建一个数据快照,而不是直接修改实际的数据行。
-
操作:
- **读操作:**当一个事务执行读操作时,它会使用快照(不晚于其开始时间的最新版本)读取。快照读取是基于事务开始时数据库中的状态创建的,因此事务不会读取其他事务尚未提交的修改;
- **写操作:**事务会为要修改的数据行创建一个新的版本,并将修改后的数据写入新版本。新版本的数据会带有当前事务的版本号,以便其他事务能够正确读取相应版本的数据。原始版本的数据仍然存在,供其他事务使用快照读取,这保证了其他事务不受当前事务的写操作影响。
- **事务提交和回滚:**当一个事务提交时,它所做的修改将成为数据库的最新版本,并且对其他事务可见。当一个事务回滚时,它所做的修改将被撤销,对其他事务不可见。
- **版本的回收:**为了防止数据库中的版本无限增长,MVCC 会定期进行版本的回收。回收机制会删除已经不再需要的旧版本数据,从而释放空间。
3. Redis 如何持久化?
-
**RDB:**以创建快照来获得存储在内存里面的数据在 某个时间点 上的副本。快照持久化是 Redis 默认采用的持久化方式。
-
在
redis.conf
配置文件中设置:save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发bgsave命令创建快照。 save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发bgsave命令创建快照。
-
命令触发:
save
: 同步保存操作,会阻塞 Redis 主线程;bgsave
: fork 出一个子进程,子进程执行,不会阻塞 Redis 主线程,默认选项。
-
-
AOF:开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区
server.aof_buf
中,然后再写入到 AOF 文件中(此时还在系统内核缓存区未同步到磁盘),最后再根据持久化方式(fsync
策略)的配置来决定何时将系统内核缓存区的数据同步到硬盘中的。Redis 6.0 后默认开启 AOF,也可在 redis.conf 配置文件中设置:
appendonly yes
-
RDB + AOF 混合
- RDB 性能较好,AOF 丢失数据的风险更小,所以实际开发都用 RDB + AOF,这样可以在大部分情况下通过 RDB 快速恢复,并利用 AOF 日志来最小化数据丢失。
4. 了解 LevelDB 吗
- LevelDB是一个开源的、高性能的键值存储库。支持任意大小的键和值,能够自动压缩数据以节省空间,并且提供了原子性的读写和批量操作功能。它的设计目标是能够处理大量的数据写入操作,同时保持高效的读取性能。
5. innodb 索引的结构为什么采用 B+ 树,不用其他结构
-
Hash 表:
查询速度极快,但是不支持顺序和范围查询,而且每次 IO 只能取一个结果,例如:
SELECT * FROM tb1 WHERE id < 500;
我们可能需要几百次 IO,如此就非常低效。另外还可能有哈希冲突问题。
-
二叉查找树:
基于二叉树实现,特点是左子树所有节点值小于根节点值小于右子树所有节点值。
当二叉查找树高度平衡时,具有较高效率,反之最坏情况下可能退化为链表。此时增删改查效率会将为O(n)。
-
AVL 树:
严格自平衡的二叉查找树。由于 AVL 树需要频繁地进行旋转操作来保持平衡,因此会有较大的计算开销进而降低了查询性能。并且, 在使用 AVL 树时,每个树节点仅存储一个数据,而每次进行磁盘 IO 时只能读取一个节点的数据,如果需要查询的数据分布在多个节点上,那么就需要进行多次磁盘 IO。 磁盘 IO 是一项耗时的操作,在设计数据库索引时,我们需要优先考虑如何最大限度地减少磁盘 IO 操作的次数。
-
红黑树:
自平衡的二叉查找树,对于平衡的要求不像 AVL 树那么严,所以红黑树查找效率略低,这可能会导致一些数据需要进行多次磁盘 IO 操作才能查询到,这也是 MySQL 没有选择红黑树的主要原因。且同样每个节点上进存储一个数据,多数据查找需要多长磁盘 IO。
-
B 树 和 B+ 树
- B 树的所有节点既存放键(key) 也存放数据(data),而 B+树只有叶子节点存放 key 和 data,其他内节点只存放 key。
- B 树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
- B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
- 在 B 树中进行范围查询时,首先找到要查找的下限,然后对 B 树进行中序遍历,直到找到查找的上限;而 B+树的范围查询,只需要对链表进行遍历即可。
综上,B+ 树与 B 树相比,具备更少的 IO 次数、更稳定的查询效率和更适于范围查询这些优势。
6. Redis 缓存穿透、缓存击穿、缓存雪崩?
-
缓存穿透:
-
概念:
大量请求的 key 是不合理的,根本不存在于缓存中,也不存在于数据库中 。这就导致这些请求直接到了数据库上,根本没有经过缓存这一层,对数据库造成了巨大的压力。(比如无效 key 攻击)
-
解决:
- 合法 key 检查(比如 age > 0);
- 接口限流,异常 IP 拉黑;
- 将无效 key 的过期时间设置地较短。
-
-
缓存击穿
-
概念:
请求的 key 对应的是 热点数据 ,该数据 存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期) 。这就可能会导致瞬时大量的请求直接打到了数据库上,对数据库造成了巨大的压力。
-
解决:
- 设置热点数据永不过期或者过期时间比较长。
- 针对热点数据提前预热,将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。
- 请求数据库写数据到缓存之前,先获取互斥锁,保证只有一个请求会落到数据库上,减少数据库的压力。
-
-
缓存雪崩
-
概念:
缓存在同一时间大面积的失效,导致大量的请求都直接落到了数据库上,对数据库造成了巨大的压力。
-
解决:
- 集群、限流、多级缓存;
- 热键过期时间增长;
- 缓存预热:程序启动后主动将热点数据加入到缓存;
-
7. SQL 注入?
- SQL注入是一种常见的网络攻击技术,它可以让攻击者通过在 SQL 查询中插入恶意SQL代码,从而绕过应用程序的安全措施,直接对数据库进行操作。当应用程序的代码不够严谨,没有对用户输入进行适当的检查和过滤时,就可能发生SQL注入攻击。
8. mysql 优化查询的方式
- 优化索引;
- 优化查询语句:①避免
SELECT *
;②使用Join
而不是子查询;③避免不必要的数据扫描; - 优化数据库,例如使用合适的
VARCHAR
,使用分表来管理大型数据; - 可以通过
EXPLAIN
关键字查看用了那个索引,是否可以优化; - 减少锁的竞争,合理使用事务;
- 可在应用层优化,例如
Redis
缓存; - 硬件优化(最直接)。
9. join,group by,order by,limit 的优先级排序
- JOIN > WHERE > GROUP BY > HAVING > SELECT > DISTINCT > ORDER BY > LIMIT
10. 说说三大日志
- 这个就很多了,建议参考 知识星球
11. 索引失效的场景
- 例:表
user
,属性有id、age、name
-
不满足最左匹配原则
如聚合索引
unoin_idx
:id, age, name
有效: 1. select * from user where id = '1'; # 有最左侧字段 id 2. select * from user where id = '1' and name = 'WF'; # 有最左侧字段 id 无效: 1. select * from user where name = 'WF'; # 未用到最左侧字段 id
综上:最左匹配原则是值使用聚合索引的最左侧字段
-
使用了
select *
如聚合索引
unoin_idx
:id, age, name
无效: select * from user where name = 'WF'; 有效: select name from user where name = 'WF'; # 走 union_idx 索引
综上:最左匹配时针对
select *
而言的,当指定了select
的对象,则无需考虑最左匹配问题 -
索引列上有计算
无效: select * from user where id+1=2;
-
索引列用了函数
无效: select * from user where SUBSTR(name, 1, 2) = 'w';
-
字段类型不同
例:
user
表中id
字段为varchar
,且id
为主键索引有效: select * from user where id = "101"; 无效: select * from user where id = 101; # 此处用 varchar 和 int 对比
-
like
左边包含%
有效: select * from user where id like '1%'; 无效: select * from user where id like '%1';
-
列对比
如
age
是唯一索引无效: select * from user where age = id;
-
使用了
or
关键字无效: select * from user where age = 18 OR id = 1;
-
in / exists
in / exists
的取值范围较大时会导致索引失效,走全表扫描;
-
order by
- 失效场景
order by
后加有where
或limit
;- 对于不同索引
order by
; - 联合索引各字段排序方式不同;
- 不满足最左匹配原则
- 失效场景
场景题
1. 进程A写100个数据和进程B写100个数据,文件会是什么样?
- 无锁定机制:如果没有使用文件锁或者其他同步机制,那么两个进程的写入可能会互相覆盖,最终文件中的数据可能是不完整的或者损坏的。比如,进程A写入了一部分数据,此时进程B也开始写入,可能会覆盖掉进程A已经写入的数据。
- 有锁定机制:如果使用了文件锁或者其他同步手段(比如互斥量、信号量等),那么在任何时刻只有一个进程能够写入数据。例如,如果进程A首先获得锁并写入100个数据,然后释放锁,随后进程B获得锁并写入另外100个数据,文件最终会安全地包含这200个数据。
2. 文件open的时候,另一个进程删除它会怎么样
- Linux系统:在这些系统中,如果一个文件被进程打开,即使它被删除,文件描述符仍然有效,进程仍可以继续读写文件。文件实际上不会从磁盘上删除,直到所有打开它的文件描述符都被关闭。这意味着进程A可以继续操作文件,不受删除操作的影响。
- Windows系统:在Windows中,通常不允许删除打开状态的文件。尝试这样做通常会导致“访问被拒绝”错误。但是,如果文件是以允许删除标志打开的(比如使用
DeleteFile
函数),那么它可以被标记为删除,在所有的句柄关闭后,文件会被删除。
3. 如果现在有一个数据量较大,会放到哪里
- 一个数据量比较大的对象通常会存储在堆内存区域。这是因为堆内存区域相对较大,能够动态分配大块内存,而栈(stack)内存区域相对较小,主要用于存储函数的局部变量和函数调用的上下文信息。大的局部变量也可以放在堆区,防止栈溢出。