腾讯云智一面面经,洒洒水喽~
公众号:阿Q技术站
来源:https://www.nowcoder.com/discuss/620573809690591232
1、说一下堆区和栈区,为什么要划分出这两个区?
堆区(Heap)
特点:
- 动态内存分配:堆区用于动态分配内存,程序在运行时可以使用
malloc
、calloc
、realloc
(在C语言中)或new
(在C++中)等函数从堆区申请内存。 - 手动管理:程序员必须手动管理堆内存的分配和释放。例如,在C语言中,需要使用
free
函数来释放内存;在C++中,则使用delete
。 - 内存大小:堆区的内存大小较大,但由于需要手动管理,容易导致内存泄漏(忘记释放已分配的内存)和内存碎片(频繁分配和释放内存后,空闲内存不连续)。
- 访问速度较慢:堆内存的访问速度比栈内存慢,因为堆区需要维护复杂的数据结构以跟踪已分配和未分配的内存块。
栈区(Stack)
特点:
- 自动内存分配:栈区用于存储函数调用时的局部变量和函数参数。内存的分配和释放由编译器自动管理。
- LIFO(后进先出)结构:栈是一种后进先出的数据结构,遵循“最后分配的内存最先释放”的原则。函数调用时,局部变量在栈上分配;函数返回时,这些变量自动释放。
- 内存大小有限:栈的大小通常比堆小,但足以满足大多数函数调用的需求。栈内存的大小通常由操作系统和编译器设定。
- 访问速度快:栈内存的访问速度比堆快,因为栈的结构简单,并且局部变量通常存储在CPU的高速缓存中。
划分堆区和栈区的原因
- 内存管理的灵活性:堆区允许动态内存分配,使程序可以根据需要动态调整内存使用。这对于处理不确定大小的数据结构(如链表、树等)非常重要。
- 内存管理的效率:栈区提供了快速的内存分配和释放机制,适用于生命周期较短的局部变量和函数调用。栈区内存的分配和释放由编译器自动处理,减少了程序员的负担。
- 内存使用的安全性:栈区由于自动管理内存,可以减少内存泄漏的风险。而堆区虽然灵活,但需要程序员小心管理,以避免内存泄漏和碎片问题。
- 资源的合理分配:操作系统可以根据不同的需求和程序的特点,将内存资源合理划分给堆区和栈区,以提高系统的整体性能和稳定性。
2、函数是如何调用的?
1. 函数调用前准备
当一个函数被调用时,首先需要进行一些准备工作:
传递参数:
- 调用者(caller)需要将函数参数传递给被调用函数(callee)。这通常通过栈或寄存器完成,具体方式取决于调用约定(calling convention)。
- 在多数调用约定中,参数从右到左推入栈中,或者通过寄存器传递。
保存状态:
- 调用者可能需要保存当前的CPU寄存器状态,以便在函数返回后能恢复执行状态。
2. 函数调用过程
压栈:
- 将参数压入栈中(如果采用栈传递参数)。
- 将返回地址压入栈中,这个地址是函数执行完毕后程序需要返回的位置。
跳转到函数:
- 执行跳转指令(如x86架构中的
CALL
指令)跳转到函数的入口地址。
3. 函数执行
进入函数后,需要执行一些初始设置:
设置栈帧(Stack Frame):
- 为了管理局部变量和参数,函数通常会设置一个新的栈帧。栈帧由帧指针(Frame Pointer,FP,也称作基址指针,BP)和栈指针(Stack Pointer,SP)管理。
- 保存调用者的帧指针,更新当前的帧指针到栈顶位置。
- 为局部变量预留空间,更新栈指针。
执行函数体:
- 函数执行其内部代码,包括对局部变量的操作、调用其他函数等。
4. 函数返回
当函数执行完毕,需要返回调用者,并可能返回一个值:
清理栈帧:
- 恢复原来的帧指针,释放局部变量占用的栈空间。
- 如果使用栈传递参数,需要清理参数。
返回值:
- 如果函数有返回值,通常通过寄存器传递,例如x86架构中使用EAX寄存器。
跳转回调用点:
- 从栈中弹出返回地址,并跳转到该地址继续执行调用者的代码。
具体示例
以x86架构为例,假设我们有如下函数:
int add(int a, int b) {
return a + b;
}
调用add(2, 3)
时,典型的汇编指令流程如下:
-
压栈参数:
push 3 ; 将参数b压入栈 push 2 ; 将参数a压入栈
-
调用函数:
call add ; 调用add函数,压入返回地址并跳转到add函数入口
-
设置栈帧(在add函数内部):
add: push ebp ; 保存调用者的帧指针 mov ebp, esp ; 设置当前函数的帧指针 sub esp, 4 ; 为局部变量预留空间(假设需要4字节)
-
执行函数体:
mov eax, [ebp+8] ; 取参数a mov ecx, [ebp+12] ; 取参数b add eax, ecx ; 计算a + b
-
清理栈帧并返回:
mov esp, ebp ; 恢复栈指针 pop ebp ; 恢复调用者的帧指针 ret ; 从栈中弹出返回地址并跳转
调用约定(Calling Conventions)
不同的调用约定对函数调用的具体细节有不同的规定。常见的调用约定包括:
- CDECL:参数从右到左压栈,调用者清理栈。
- STDCALL:参数从右到左压栈,被调用者清理栈。
- FASTCALL:部分参数通过寄存器传递,剩余参数从右到左压栈。
- THISCALL:用于C++成员函数调用,
this
指针通过寄存器传递,其余参数根据具体约定。
3、介绍一下指针和引用,它们在作为参数类型时,如何传递参数?
指针
定义和特点:
- 指针是一个变量,用于存储另一个变量的内存地址。
- 使用
*
运算符来定义指针,使用&
运算符来获取变量的地址。
声明和使用:
int a = 10;
int *p = &a; // p是指向a的指针
访问指针指向的值:
int value = *p; // 通过解引用访问指针p指向的值
引用
定义和特点:
- 引用是一个别名,它是对另一个变量的引用。
- 使用
&
运算符来定义引用,引用一旦绑定到一个变量,就不能再绑定到其他变量。
声明和使用:
int a = 10;
int &ref = a; // ref是a的引用
访问引用指向的值:
int value = ref; // 直接使用ref就可以访问a的值
指针和引用作为参数类型
指针作为参数
当指针作为函数参数时,函数接收的是参数变量的地址,可以直接修改参数变量的值。
函数定义和调用:
void increment(int *p) {
(*p)++; // 解引用指针并修改值
}
int main() {
int a = 10;
increment(&a); // 传递a的地址
// a的值变为11
return 0;
}
引用作为参数
当引用作为函数参数时,函数接收的是参数变量的引用,可以直接修改参数变量的值。引用参数在函数调用时更加直观,因为不需要显式传递地址。
函数定义和调用:
void increment(int &ref) {
ref++; // 直接修改引用的值
}
int main() {
int a = 10;
increment(a); // 直接传递a
// a的值变为11
return 0;
}
对比指针和引用
- 语法简洁:引用语法更加简洁、直观,不需要使用解引用运算符
*
。 - 不可为空:引用必须绑定到有效的变量,不能为NULL。指针可以为空(NULL),需要检查是否为空以避免空指针访问错误。
- 不可重绑定:引用在初始化后不能绑定到另一个变量。指针可以在任何时候指向不同的变量。
- 更安全:引用更安全,减少了指针操作中的潜在错误(如空指针、悬空指针)。
4、说一下常用的索引?
B树索引
特点:
- B树(B-tree)或其变体B+树(B+tree)是最常见的索引类型。
- 适用于范围查询、大量数据的排序操作。
- 平衡树结构,所有叶子节点在同一层。
- 支持等值查询和范围查询,如
=
,<
,>
,BETWEEN
,LIKE 'abc%'
。
优点:
- 维护数据的有序性。
- 查询效率高,尤其是范围查询。
- 插入、删除操作性能较好。
缺点:
- 占用一定的存储空间。
- 在频繁的写操作下,维护树结构的代价较高。
使用场景:
- 主键、唯一键等经常用于排序和搜索的字段。
- 需要支持范围查询的字段。
2. 哈希索引
特点:
- 使用哈希表实现,键值通过哈希函数计算位置。
- 仅支持等值查询,如
=
、IN
。 - 查询速度非常快,但不支持范围查询。
优点:
- 等值查询效率极高。
- 插入和删除操作性能优秀。
缺点:
- 不支持范围查询。
- 哈希冲突可能导致性能下降。
- 无法维护数据的有序性。
使用场景:
- 频繁进行等值查询的字段。
- 不需要进行范围查询的场景。
3. 位图索引
特点:
- 使用位图(bitmaps)来表示字段的值。
- 每个不同值对应一个位图,每个位表示该值是否在对应的记录中出现。
- 非常适合低基数(low cardinality)的字段,即字段值种类较少。
优点:
- 在低基数、高查询密度的场景下,查询效率高。
- 存储空间利用率高。
缺点:
- 更新操作成本高,尤其是在高并发写入的环境下。
- 适用于静态或变化较少的数据。
使用场景:
- 数据仓库中,用于分析和统计的字段。
- 低基数字段,如性别、状态等。
4. 全文索引
特点:
- 用于对大文本字段进行全文搜索。
- 支持自然语言查询和布尔查询。
- 创建反向索引,将文档中的词映射到文档ID。
优点:
- 可以高效地进行复杂文本搜索。
- 支持多种匹配模式(如前缀匹配、布尔匹配等)。
缺点:
- 占用较多存储空间。
- 创建和维护索引的开销较大。
使用场景:
- 博客、新闻网站等需要全文搜索的场景。
- 大量文本数据的检索和分析。
5. 空间索引
特点:
- 用于存储和查询地理空间数据。
- 常见的数据结构包括R树(R-tree)和四叉树(Quadtree)。
- 支持地理范围查询和空间关系查询(如邻近、包含等)。
优点:
- 能够有效处理地理空间数据的查询。
- 提供了快速的空间查询能力。
缺点:
- 复杂的索引结构,维护成本较高。
- 需要特定的数据库系统支持(如PostGIS, Oracle Spatial等)。
使用场景:
- 地理信息系统(GIS),如地图应用、位置服务等。
- 需要进行空间数据查询的应用。
6. 聚簇索引
特点:
- 数据表中的记录按索引顺序存储。
- 一个表只能有一个聚簇索引,通常为主键。
- 数据行实际存储顺序和索引顺序一致。
优点:
- 读取范围查询速度快,因为数据物理上连续存储。
- 减少了I/O操作,查询效率高。
缺点:
- 插入、删除、更新操作的性能可能较低,因需要维护数据顺序。
- 索引创建和维护的开销较大。
使用场景:
- 需要频繁进行范围查询的表。
- 主键或具有唯一约束的列。
7. 非聚簇索引
特点:
- 索引结构独立于数据存储,索引中存储键值和指向实际数据行的指针。
- 一个表可以有多个非聚簇索引。
优点:
- 允许对不同的列进行多个索引。
- 提供了灵活的查询优化选择。
缺点:
- 查询速度比聚簇索引慢,因为需要额外的I/O操作。
- 插入、删除和更新操作的开销较大。
使用场景:
- 经常用于查找单一值或少量记录的列。
- 需要对多个列进行索引的场景。
5、MySQL引擎有哪些,说一下 InnoDB 和 MyISAM 的区别?
InnoDB 和 MyISAM 的区别
1. 事务支持
InnoDB:
- 支持事务(ACID 属性),即原子性、一致性、隔离性、持久性。
- 提供支持事务的标准语句,如
BEGIN
,COMMIT
,ROLLBACK
。 - 支持自动恢复机制,确保在系统崩溃后数据的一致性。
MyISAM:
- 不支持事务。
- 每个查询独立执行,不能回滚或提交。
2. 外键支持
InnoDB:
- 支持外键约束,能确保数据的完整性和一致性。
- 外键约束可以自动级联更新和删除(CASCADE)。
MyISAM:
- 不支持外键约束,数据完整性需要在应用层手动维护。
3. 锁机制
InnoDB:
- 支持行级锁(Row-level locking),即每次锁定的是表中的一行。
- 行级锁提高了并发性能,特别是高并发读写场景。
MyISAM:
- 支持表级锁(Table-level locking),即每次锁定整个表。
- 适合以读操作为主的应用,因为写操作会锁住整个表,阻塞其他读写操作。
4. 数据存储和索引
InnoDB:
- 将数据和索引存储在一个共享表空间(tablespace)或每个表独立的.ibd文件中。
- 支持聚簇索引(Clustered Index),主键索引和数据存储在一起,提高了主键查询的性能。
MyISAM:
- 将数据存储在.MYD文件中,索引存储在.MYI文件中。
- 使用非聚簇索引,数据和索引分开存储。
5. 性能和使用场景
InnoDB:
- 适合需要高并发读写、事务处理、数据完整性要求高的应用场景。
- 常用于OLTP(在线事务处理)系统。
MyISAM:
- 适合读操作频繁、对事务要求不高的应用场景。
- 常用于OLAP(在线分析处理)系统、数据仓库、数据归档等。
6. 数据恢复和崩溃安全
InnoDB:
- 支持崩溃恢复,通过日志文件(redo log)和检查点(checkpoint)机制恢复未完成的事务,保证数据一致性。
- 自动检查并修复数据文件中的损坏记录。
MyISAM:
- 不支持崩溃恢复,数据文件损坏时需要手动修复。
- 提供
myisamchk
工具用于检查和修复表。
7. 全文索引
InnoDB:
- 从MySQL 5.6版本开始支持全文索引,但性能不如MyISAM。
MyISAM:
- 原生支持全文索引,适合大文本字段的搜索操作。
6、说一下事务?
事务是一组操作的集合,这些操作要么全部成功,要么全部失败。事务的主要目的是保证数据库的一致性和完整性。事务通常具有以下四个特性,称为ACID特性:
- 原子性(Atomicity):事务中的所有操作要么全部完成,要么完全不执行。如果事务中的任何一个操作失败,整个事务会回滚到事务开始时的状态,就像这个事务从未执行过一样。
- 一致性(Consistency):事务在完成时,必须使数据库从一个一致性状态变到另一个一致性状态。换句话说,事务的执行不能破坏数据库的完整性约束。
- 隔离性(Isolation):在事务并发执行时,一个事务的执行不应该影响到其他事务的执行。不同的隔离级别决定了一个事务在多大程度上受到其他事务的影响。
- 持久性(Durability):一旦事务提交,其对数据库的改变是永久的,即使系统崩溃,事务的结果也不会丢失。
事务的生命周期
- 开始事务:事务开始时,所有操作都会被记录在事务日志中。
- 执行操作:事务执行一系列的数据库读写操作。
- 提交事务:如果所有操作成功完成,则提交事务,所有更改持久化到数据库。
- 回滚事务:如果任何操作失败,则回滚事务,撤销所有已执行的操作,恢复到事务开始时的状态。
7、说一下 MVCC 机制?
多版本并发控制(MVCC)是一种用于实现数据库并发控制的机制,特别是为了提高数据库在高并发环境下的性能和一致性。MVCC 通过维护数据的多个版本,使读操作和写操作可以并发执行,从而减少锁竞争,提高系统的并发性能。
使用场景
MVCC 适用于读多写少的场景,如在线事务处理系统(OLTP),这类系统通常具有高并发读写需求。通过 MVCC,可以在保证数据一致性的同时,提高系统的整体性能和响应速度。
8、隔离级别有哪些?MVCC 机制用于哪些隔离级别?
四种隔离级别
- 读未提交(Read Uncommitted)
- 特性:允许事务读取其他未提交事务的变更。
- 优点:并发性最高。
- 缺点:可能导致脏读(Dirty Read)、不可重复读(Non-Repeatable Read)和幻读(Phantom Read)。
- 使用场景:几乎不使用,因为数据一致性风险较高。
- 读已提交(Read Committed)
- 特性:只允许事务读取其他已提交事务的变更。
- 优点:避免了脏读。
- 缺点:可能导致不可重复读和幻读。
- 使用场景:多数数据库系统的默认隔离级别,如 Oracle。
- 可重复读(Repeatable Read)
- 特性:确保在同一事务中多次读取同一数据的结果是一致的。
- 优点:避免了脏读和不可重复读。
- 缺点:可能导致幻读。
- 使用场景:MySQL InnoDB 的默认隔离级别。
- 序列化(Serializable)
- 特性:强制事务顺序执行,确保完全隔离。
- 优点:避免了脏读、不可重复读和幻读。
- 缺点:并发性最低,性能开销大。
- 使用场景:适用于对并发要求非常严格的场景。
MVCC 与隔离级别的关系
多版本并发控制(MVCC)主要用于实现以下两种隔离级别:
- 读已提交(Read Committed)
- 可重复读(Repeatable Read)
在这两个隔离级别中,MVCC 通过维护数据的多个版本,使读操作和写操作可以并发执行,从而减少锁竞争,提高系统的并发性能。
MVCC 在不同隔离级别中的实现
- 读已提交(Read Committed)
- 每次读取数据时,事务会获取一个最新的快照,只能看到其他事务已经提交的变更。
- 不同读操作可能看到不同的版本,容易导致不可重复读。
- 可重复读(Repeatable Read)
- 事务在开始时创建一致性视图,所有读取操作基于这个视图。
- 在整个事务期间,读取结果一致,避免了不可重复读。
- 使用 MVCC 维护的数据版本,确保读操作不会阻塞写操作,写操作也不会阻塞读操作。
实现细节
1. 读已提交(Read Committed)
- Read View:每次读取数据时,创建一个新的 Read View,确保只能读取已提交的变更。
- 可见性判断:根据当前活跃事务的快照判断数据版本的可见性。
2. 可重复读(Repeatable Read)
- Read View:在事务开始时创建一次 Read View,在整个事务期间保持不变。
- 可见性判断:事务读取的数据版本基于事务开始时的快照,保证一致性。
事务隔离级别对比
隔离级别 | 脏读(Dirty Read) | 不可重复读(Non-Repeatable Read) | 幻读(Phantom Read) | 实现复杂度 | 性能开销 |
---|---|---|---|---|---|
读未提交 | 可能 | 可能 | 可能 | 最简单 | 最低 |
读已提交 | 不可能 | 可能 | 可能 | 较简单 | 较低 |
可重复读 | 不可能 | 不可能 | 可能 | 中等 | 中等 |
序列化 | 不可能 | 不可能 | 不可能 | 最复杂 | 最高 |
9、说一下 MVCC 的实现原理
MVCC 的基本原理
MVCC 主要通过以下几种方式来管理数据的多个版本:
- 数据版本化:每个数据行不仅存储当前版本,还存储其历史版本。通常,版本信息会包含事务ID和时间戳等元数据,用于标识和区分不同版本的数据。
- 快照读(Snapshot Read):读操作不会阻塞写操作。每次读取数据时,系统会生成一个一致性快照,读操作基于该快照读取数据。这保证了读操作在事务开始时看到的视图是一致的,即使其他事务进行了修改。
- 版本链:每个数据行维护一个版本链,链表中每个节点代表数据行的一个版本。最新的版本在链表的头部,历史版本按时间顺序排列。
MVCC 的实现细节
以 InnoDB 为例,InnoDB 是 MySQL 中默认的存储引擎,并且使用 MVCC 来实现事务隔离。以下是 InnoDB 中 MVCC 的实现细节:
1. 隐藏列
InnoDB 为每行数据添加了两个隐藏列:
DB_TRX_ID
:记录最后修改该行的事务ID。DB_ROLL_PTR
:指向回滚段,用于存储修改前的旧版本数据。
当事务修改一行数据时,会创建一个新的版本,同时更新这两个隐藏列。
2. Undo Log
Undo Log 用于记录数据修改前的旧版本。当事务进行回滚时,可以通过 Undo Log 恢复到旧版本。Undo Log 还用于读取历史版本的数据。
3. Read View
每个事务在开始时,会创建一个一致性视图(Read View),该视图包含当前活跃事务的快照。Read View 用于决定一个数据版本是否对当前事务可见。
4. 可见性判断
当事务读取数据时,根据 Read View 判断数据版本的可见性:
- 如果数据行的
DB_TRX_ID
小于 Read View 中的最小活跃事务ID,则该版本对当前事务可见。 - 如果数据行的
DB_TRX_ID
大于 Read View 中的最大活跃事务ID,则该版本对当前事务不可见。 - 如果数据行的
DB_TRX_ID
在 Read View 的活跃事务ID列表中,则该版本对当前事务不可见。
MVCC 的优点
- 高并发性能:读操作不会阻塞写操作,写操作也不会阻塞读操作,极大地提高了系统的并发性能。
- 一致性视图:每个事务在开始时获取一致性视图,确保事务在读取数据时看到的视图是一致的,即使其他事务进行了修改。
- 减少锁竞争:通过多版本管理,减少了读写操作之间的锁竞争,避免了许多因锁争用而导致的性能问题。
MVCC 的缺点
- 存储开销:由于需要维护多个版本的数据,MVCC 需要更多的存储空间。数据行的历史版本和 Undo Log 都会占用额外的存储空间。
- 版本清理:需要定期清理无用的历史版本,以防止存储空间的无限增长。这通常通过垃圾回收机制实现。
10、MVCC 解决了哪些问题?
- 读-写冲突:在传统的并发控制机制中,读操作和写操作之间存在冲突,读操作会阻塞写操作,写操作会阻塞读操作,降低了系统的并发性能。MVCC 通过维护数据的多个版本,使得读操作不会被写操作阻塞,读操作可以读取到一个一致性的快照,提高了系统的并发性能。
- 写-写冲突:在传统的并发控制机制中,两个事务同时对同一数据进行写操作时会发生冲突,其中一个事务的写操作会被阻塞,直到另一个事务完成。MVCC 通过维护数据的多个版本,每个事务可以看到自己修改的数据版本,不会被其他事务的写操作阻塞,提高了系统的并发性能。
- 不可重复读:不可重复读是指在一个事务中,同一查询在不同时间点多次执行,返回的结果不一致。MVCC 通过事务开始时创建一致性快照(Read View),保证了在同一事务中多次读取同一数据的结果是一致的,解决了不可重复读的问题。
- 幻读:幻读是指在一个事务中,同一查询在不同时间点多次执行,返回的结果集不一致,可能包含了其他事务插入或删除的数据。MVCC 通过维护数据的多个版本,保证了在同一事务中多次执行相同查询返回的结果是一致的,解决了幻读的问题。
- 并发性能:MVCC 提高了系统的并发性能,通过允许读操作不会被写操作阻塞,写操作也不会被读操作阻塞,减少了锁的竞争,提高了系统的并发处理能力。
11、MVCC 机制如何解决幻读?介绍一下MySQL中的锁机制,说一下间隙锁的使用及原理
MVCC(多版本并发控制)机制通过维护数据的多个版本来解决幻读问题。在MVCC中,每个事务在开始时会创建一个一致性视图(Read View),该视图包含了事务开始时数据库中所有数据的一个快照。在事务执行期间,所有的读操作都基于这个一致性视图,从而保证了事务读取数据的一致性。
对于幻读问题,MVCC主要通过在读操作中使用快照读(Snapshot Read)来解决。快照读意味着在事务开始时生成一个一致性快照,事务中的所有读操作都基于这个快照进行,而不受其他事务并发修改的影响。因此,即使其他事务在事务执行期间插入或删除数据,当前事务也不会受到影响,从而避免了幻读问题。
在MySQL中,除了MVCC机制外,还有锁机制用于并发控制。MySQL中的锁主要分为两种:行级锁和表级锁。行级锁用于控制对数据行的访问,而表级锁用于控制对整个表的访问。
行级锁
行级锁是在数据行级别上加锁,可以避免多个事务同时修改同一行数据而导致的冲突。MySQL中的InnoDB存储引擎默认使用行级锁来实现MVCC机制。行级锁包括如下几种类型:
- 共享锁(Shared Lock):允许事务读取一行数据,但不允许对其进行修改。多个事务可以同时持有共享锁,但不允许有其他事务持有排他锁。
- 排他锁(Exclusive Lock):允许事务对一行数据进行读取和修改。排他锁不能与其他锁共存,即排他锁会阻止其他事务获取任何类型的锁。
表级锁
表级锁是对整个表加锁,可以控制对整个表的访问。MySQL中的表级锁包括如下几种类型:
- 表共享锁(Table Read Lock):多个事务可以同时获取表共享锁,用于阻止其他事务获取排他锁,但不阻止其他事务获取表共享锁。
- 表排他锁(Table Write Lock):只允许一个事务获取表排他锁,其他事务不能同时获取任何类型的锁。
间隙锁(Gap Lock)
间隙锁是InnoDB存储引擎特有的一种锁机制,用于解决幻读问题。间隙锁是在索引记录之间的间隙上设置的锁,用于防止其他事务在这个间隙内插入新记录,从而避免了幻读问题。
间隙锁的使用和原理如下:
- 使用:当一个事务执行范围查询(例如:SELECT … WHERE id BETWEEN 10 AND 20)时,InnoDB会在查询的索引范围内的间隙上设置间隙锁,阻止其他事务在这个间隙内插入新记录。
- 原理:间隙锁是排他锁的一种,它的目的是为了防止其他事务在查询的索引范围内插入新记录,从而保证了范围查询的结果是一致的,避免了幻读问题。
12、索引如何优化?
1. 选择合适的索引类型
单列索引
单列索引是最基本的索引类型,每个索引只包含一列。适用于对单个字段的查询。
多列索引
多列索引(复合索引)包含多个列,适用于经常在多个字段上进行查询的情况。索引的列顺序很重要,应该将查询中最常用的列放在前面。
唯一索引
唯一索引确保索引列中的值唯一,适用于需要保证数据唯一性的场景。
全文索引
全文索引用于对大文本字段进行搜索,如MySQL中的FULLTEXT
索引。
覆盖索引
覆盖索引包含了查询需要的所有列,避免回表查询。例如:SELECT col1, col2 FROM table WHERE col1 = ?
,如果有索引(col1, col2)
,则查询可以完全在索引中完成。
2. 创建高效的索引
分析查询模式
查看系统中的查询日志,找出最常用的查询和最慢的查询,分析这些查询涉及的表和字段。
索引选择
针对高频查询创建合适的索引。对于WHERE
条件和JOIN
操作中经常使用的列,应该优先考虑创建索引。
覆盖索引
尽量创建覆盖索引,以减少回表查询的次数,提高查询性能。
索引顺序
对于复合索引,考虑索引列的顺序。一般来说,将选择性高的列放在前面。选择性指的是列中不同值的数量占总行数的比例。
3. 避免冗余和低效的索引
删除冗余索引
定期检查数据库中的索引,删除重复或几乎不使用的索引,以减少维护开销和磁盘空间占用。
避免过多索引
每个索引都会增加数据插入、更新、删除的开销,因为每次修改数据时,索引也需要更新。需要平衡查询性能和写操作性能。
4. 维护索引
定期重建索引
特别是对经常更新的表,定期重建索引可以防止索引碎片化,提高查询性能。
更新统计信息
数据库管理系统使用统计信息来选择最优的查询执行计划,定期更新统计信息可以确保查询优化器有最新的信息来选择最优的执行计划。
5. 使用数据库自带工具
查询优化工具
大多数数据库管理系统都有查询优化工具,如MySQL的EXPLAIN
命令,可以分析查询计划,找出性能瓶颈。
自动索引建议
一些数据库,如SQL Server,提供自动索引建议功能,可以根据查询模式自动推荐索引。
实例:优化MySQL中的索引
假设我们有一个用户表users
,包含字段id
、name
、email
、created_at
,以及以下查询:
SELECT name, email FROM users WHERE created_at >= '2024-05-19';
分析:
- 这个查询在
created_at
列上有一个条件。 - 返回的列是
name
和email
。
创建覆盖索引:
CREATE INDEX idx_created_at_name_email ON users (created_at, name, email);
这个索引可以覆盖查询中的所有字段,查询时无需回表,直接从索引中获取数据,极大提高查询性能。
13、说一下 select、poll、epoll?
特性 | select | poll | epoll |
---|---|---|---|
系统调用接口 | fd_set | pollfd 数组 | 文件描述符 + 回调 |
最大文件描述符限制 | 有,通常为 1024(可通过编译选项增加) | 无限制 | 无限制 |
监控文件描述符数量 | 有上限,通常为 1024 | 无上限 | 无上限 |
内核处理方式 | 每次调用都需要重建描述符集 | 每次调用都需要重建描述符集 | 只需一次注册,事件触发时通知 |
性能 | 随着文件描述符数量增加性能显著下降 | 随着文件描述符数量增加性能显著下降 | 在大量文件描述符下性能优越 |
使用场景 | 少量文件描述符(< 1024)的情况 | 中等数量文件描述符的情况 | 大量文件描述符的高并发场景 |
触发机制 | 水平触发(level-triggered) | 水平触发(level-triggered) | 支持水平触发和边缘触发(edge-triggered) |
消息传递方式 | 每次调用都需传递完整的文件描述符集 | 每次调用都需传递完整的文件描述符数组 | 事件驱动,内核和用户空间通过事件通知传递消息 |
复杂度 | 简单,易于使用 | 较简单,易于使用 | 较复杂,需要更多编程技巧 |
内存开销 | 随文件描述符数量增加而增加 | 随文件描述符数量增加而增加 | 低,因为只需注册一次,后续通过事件通知 |
是否阻塞 | 支持阻塞和非阻塞 | 支持阻塞和非阻塞 | 支持阻塞和非阻塞 |
文件描述符集或数组大小 | 固定大小 | 动态调整 | 动态调整 |
1. select
select
是最早期的 I/O 多路复用机制。其主要特点和缺点如下:
特点
select
可以监控多个文件描述符,使用一个固定大小的数组来存储这些文件描述符。- 每次调用
select
时,必须重新初始化该数组,并将所有的文件描述符复制到内核态进行检查。 - 超时参数(timeout)可以设定为阻塞、非阻塞或定时阻塞模式。
缺点
- 文件描述符数量有限制,通常为1024(可以通过修改宏定义
FD_SETSIZE
来改变,但需要重新编译内核)。 - 每次调用
select
都需要将文件描述符集从用户态复制到内核态,这对于大数量文件描述符会导致性能下降。 - 在文件描述符数量很大的情况下,性能较差,因为内核需要遍历所有的文件描述符。
2. poll
poll
是 select
的改进版,解决了一些 select
的局限性。
特点
poll
使用一个结构体数组来存储文件描述符及其事件,支持更多的文件描述符。- 没有文件描述符数量的硬性限制(取决于系统的资源和配置)。
poll
不需要像select
一样每次重新初始化文件描述符集,但仍需要每次调用都复制结构体数组到内核态。
缺点
- 同样需要每次调用都将文件描述符数组从用户态复制到内核态,带来一定的开销。
- 在大量文件描述符的情况下,性能仍然不够理想,因为内核需要遍历所有文件描述符。
3. epoll
epoll
是 Linux 特有的高效 I/O 多路复用机制,相比 select
和 poll
有显著的性能优势。
特点
epoll
采用基于事件的通知机制,而不是轮询所有文件描述符。只有发生事件的文件描述符会被内核通知用户态,极大地减少了不必要的系统调用。- 支持水平触发(Level Triggered)和边缘触发(Edge Triggered)两种模式。边缘触发模式下,只有状态变化时才会通知,有效减少了系统调用次数。
- 文件描述符被添加到
epoll
监控列表后,不需要每次调用都重新传递,减少了复制开销。 - 适用于大规模并发连接,如高性能服务器和网络应用。
缺点
epoll
是 Linux 特有的,在跨平台应用中需要注意兼容性问题。- 相对复杂的 API,使用起来比
select
和poll
要复杂一些。
14、poll 和 epoll 的使用场景?
poll
的使用场景
poll
适合以下场景:
- 中等数量的文件描述符:
- 当文件描述符的数量在数百个以内时,
poll
能够较好地工作。尽管poll
没有select
的硬性文件描述符数量限制,但其性能在文件描述符数量较多时会有所下降。
- 当文件描述符的数量在数百个以内时,
- 需要跨平台兼容性:
poll
是 POSIX 标准的一部分,适用于大多数 UNIX 系统和类 UNIX 系统(如 Linux、BSD、macOS),因此在跨平台开发时具有良好的兼容性。
- 简单的 I/O 多路复用:
poll
的 API 相对简单,易于理解和使用,适合需要快速实现简单 I/O 多路复用功能的应用程序。
poll
示例代码
#include <poll.h>
#include <unistd.h>
#include <stdio.h>
#define TIMEOUT 5000 // 超时时间,单位为毫秒
int main() {
struct pollfd fds[2];
int ret;
// 监控标准输入(文件描述符 0)和标准输出(文件描述符 1)
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
fds[1].fd = STDOUT_FILENO;
fds[1].events = POLLOUT;
// 调用 poll 函数
ret = poll(fds, 2, TIMEOUT);
if (ret == -1) {
perror("poll");
return 1;
} else if (ret == 0) {
printf("Timeout occurred! No file descriptors were ready.\n");
} else {
if (fds[0].revents & POLLIN) {
printf("stdin is ready for reading\n");
}
if (fds[1].revents & POLLOUT) {
printf("stdout is ready for writing\n");
}
}
return 0;
}
epoll
的使用场景
epoll
适合以下场景:
- 大规模并发连接:
epoll
特别适合需要处理大量并发连接的场景,如高性能服务器、网络代理、消息队列等。由于其高效的事件通知机制,能够显著提高性能。
- 高性能网络应用:
epoll
的事件驱动机制减少了系统调用的开销,特别适合需要高吞吐量和低延迟的网络应用。
- 事件驱动模型:
- 使用
epoll
的边缘触发(Edge Triggered)模式,可以减少不必要的系统调用次数,适合需要频繁处理事件的应用程序。
- 使用
epoll
示例代码
#include <sys/epoll.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define MAX_EVENTS 10
#define READ_SIZE 10
int main() {
int epoll_fd, nfds, n;
struct epoll_event ev, events[MAX_EVENTS];
char buffer[READ_SIZE];
// 创建 epoll 文件描述符
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 添加标准输入到 epoll 监控列表
ev.events = EPOLLIN;
ev.data.fd = STDIN_FILENO;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1) {
perror("epoll_ctl: stdin");
exit(EXIT_FAILURE);
}
while (1) {
// 等待事件发生
nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == STDIN_FILENO) {
int bytes_read = read(STDIN_FILENO, buffer, READ_SIZE);
if (bytes_read == -1) {
perror("read");
exit(EXIT_FAILURE);
}
buffer[bytes_read] = '\0';
printf("Read from stdin: %s", buffer);
}
}
}
close(epoll_fd);
return 0;
}
区别
poll
适用于中等数量的文件描述符、需要跨平台兼容性、以及简单的 I/O 多路复用场景。epoll
适用于大规模并发连接、高性能网络应用和事件驱动模型。
15、输入 URL 后发生的事情?
- 用户输入URL: 用户在浏览器的地址栏中输入URL(统一资源定位符),这是他们希望访问的网页的地址。
- DNS解析: 浏览器首先需要解析URL中的主机名部分,以确定目标服务器的IP地址。这个过程称为DNS解析。如果浏览器有缓存,它可能首先查看本地缓存以获取IP地址。如果不在缓存中,浏览器将向本地DNS服务器发出请求,以获取目标主机的IP地址。本地DNS服务器可以向根DNS服务器、顶级域名服务器和权威DNS服务器逐级查询,最终找到目标主机的IP地址。
- 建立TCP连接: 一旦浏览器获得了目标服务器的IP地址,它将尝试建立到该服务器的TCP连接。这个过程通常涉及三次握手,即浏览器向服务器发送一个连接请求,服务器确认请求并回复,然后浏览器再次确认。一旦建立了TCP连接,数据可以在浏览器和服务器之间进行可靠的双向通信。
- 发起HTTP请求: 一旦TCP连接建立,浏览器会构建一个HTTP请求,该请求包括用户想要获取的特定页面的信息。HTTP请求通常包括请求方法(如GET、POST等)、请求头部和请求主体。
- 服务器处理请求: 服务器接收到浏览器发送的HTTP请求后,会根据请求的内容执行相应的操作。这可能涉及查询数据库、处理业务逻辑、生成动态内容等。
- 服务器发送HTTP响应: 服务器会生成一个HTTP响应,该响应包含请求的内容(通常是HTML页面)、状态码(表示请求是否成功、重定向等)以及响应头部信息。服务器还将此HTTP响应发送回浏览器。
- 接收和渲染页面: 一旦浏览器收到HTTP响应,它会解析响应的内容,并根据HTML、CSS和JavaScript等数据渲染页面。这包括呈现文本、图像、链接和其他媒体,以及执行JavaScript代码。
- 页面展示: 最终,浏览器将渲染后的页面呈现给用户,用户可以与页面进行交互。
- 页面缓存: 浏览器通常会将页面的一些资源(如图像、样式表、脚本等)缓存,以便在用户再次访问相同页面时能够更快地加载。
16、HTTP 基于什么协议实现?
HTTP是用于在 Web 上传输超文本数据的协议,它基于 TCP/IP 协议实现。
TCP/IP 协议栈
HTTP 基于 TCP/IP 协议栈进行数据传输,TCP/IP 协议栈分为四层:
- 应用层
- 包括 HTTP、FTP、SMTP 等协议,负责特定的网络应用。
- HTTP 协议在这一层实现,负责 Web 浏览器和 Web 服务器之间的通信。
- 传输层
- 包括 TCP(传输控制协议)和 UDP(用户数据报协议)。
- HTTP 使用可靠的 TCP 协议来保证数据传输的正确性和顺序。
- TCP 提供端到端的连接,通过三次握手建立连接,并通过四次挥手断开连接。
- 网络层
- 包括 IP(互联网协议),负责数据包的路由和转发。
- IP 协议定义了数据包的地址结构,确保数据包能从源地址到达目的地址。
- 常用的 IP 协议版本包括 IPv4 和 IPv6。
- 链路层
- 包括以太网、Wi-Fi 等协议,负责数据帧的传输。
- 处理物理链路上的数据传输,包括帧的组装与拆解、错误检测与纠正。
17、如何使用 HTTP 协议与服务器连接(TCP三次握手)
使用 HTTP 协议与服务器连接包括以下几个主要步骤:建立连接、发送请求、处理响应和关闭连接。
1. 建立连接
首先,客户端(通常是浏览器或 HTTP 客户端)需要与服务器建立连接。HTTP/1.1 使用的是 TCP 协议来传输数据,因此需要通过 TCP 三次握手来建立连接。
TCP 三次握手步骤:
- SYN:客户端发送一个 SYN 包(同步序列号)请求建立连接。
- SYN-ACK:服务器接收到 SYN 包后,回复一个 SYN-ACK 包(同步序列号 + 确认序列号),表示同意建立连接。
- ACK:客户端接收到 SYN-ACK 包后,发送一个 ACK 包(确认序列号),确认连接建立。
2. 发送 HTTP 请求
连接建立后,客户端可以发送 HTTP 请求。一个典型的 HTTP 请求包含以下部分:
- 请求行:包括请求方法(如 GET、POST)、请求的 URL 和 HTTP 版本。
- 请求头:包括元数据,如主机名、用户代理、接受的文件类型等。
- 请求体:在 POST 请求中包含提交的数据(GET 请求没有请求体)。
3. 服务器处理请求
服务器接收到 HTTP 请求后,会进行处理:
- 解析请求行和请求头:确定请求的资源、方法和其他信息。
- 处理请求:根据请求方法和 URL,执行相应的逻辑,如读取文件、查询数据库等。
- 生成响应:构建 HTTP 响应,包括响应状态码、响应头和响应体。
4. 发送 HTTP 响应
服务器将生成的 HTTP 响应通过 TCP 连接发送回客户端。
5. 解析和渲染内容
客户端接收到 HTTP 响应后,开始解析和渲染内容:
- 解析响应行和响应头:确定响应状态、内容类型等信息。
- 解析响应体:如果内容类型是 HTML,浏览器会解析 HTML 生成 DOM 树,并根据需要加载其他资源(如 CSS、JavaScript、图片等)。
6. 关闭连接
通常情况下,HTTP/1.1 使用 keep-alive
机制保持连接打开,以便复用。客户端和服务器可以通过 Connection: close
头来显式关闭连接。如果不使用 keep-alive
,则需要通过 TCP 四次挥手来关闭连接。
TCP 四次挥手步骤:
- FIN:发起方发送 FIN 包(终止连接),请求关闭连接。
- ACK:接收方回复 ACK 包,确认关闭请求。
- FIN:接收方也发送 FIN 包,表示准备关闭连接。
- ACK:发起方回复 ACK 包,确认连接关闭。
18、HTTP 和 HTTPS 的区别?
特性 | HTTP | HTTPS |
---|---|---|
基础协议 | 基于 TCP/IP 的应用层协议 | 基于 HTTP 和 SSL/TLS 的应用层协议 |
端口 | 80 | 443 |
数据加密 | 无(明文传输) | 有(使用 SSL/TLS 加密) |
数据完整性 | 无数据完整性检查 | 使用消息认证码(MAC)验证数据完整性 |
身份验证 | 无内置身份验证机制 | 使用数字证书验证服务器身份 |
连接过程 | TCP 三次握手 | TCP 三次握手 + TLS 握手 |
性能 | 较快,无加密开销 | 初始连接稍慢,需进行加密解密和 TLS 握手,但现代优化技术已减少性能影响 |
使用场景 | 适用于不涉及敏感数据的公共信息展示场景 | 适用于涉及敏感数据的场景,如在线支付、用户登录、电子邮件通信等 |
安全性 | 低,易受中间人攻击 | 高,防止数据被窃取和篡改 |
浏览器地址栏指示 | 无特殊指示 | 带有锁定标志和“https://”前缀 |
证书需求 | 不需要 | 需要数字证书,由受信任的证书颁发机构(CA)签发 |
SEO 影响 | 无 | 有利于搜索引擎优化(SEO),搜索引擎更倾向于排名 HTTPS 网站 |
HTTP 版本支持 | 支持 HTTP/1.0、HTTP/1.1、HTTP/2、HTTP/3 | 支持 HTTP/1.0、HTTP/1.1、HTTP/2、HTTP/3 |
普及趋势 | 渐渐被 HTTPS 取代 | 越来越多的网站开始默认使用 HTTPS,提高整体网络的安全性 |
1. 基础协议
- HTTP:基于 TCP/IP 协议的应用层协议,用于传输超文本数据(如 HTML 文件)。HTTP 默认使用 80 端口。
- HTTPS:基于 HTTP,但增加了 SSL/TLS(Secure Sockets Layer / Transport Layer Security)加密层,以确保数据传输的安全性。HTTPS 默认使用 443 端口。
2. 数据加密
- HTTP:数据以明文形式传输,未加密。任何中间人(如网络管理员、黑客等)都可以拦截并读取传输的数据。
- HTTPS:数据通过 SSL/TLS 加密传输,确保数据在客户端和服务器之间的传输过程中无法被拦截和读取。
3. 数据完整性
- HTTP:没有数据完整性检查,数据在传输过程中可能被篡改而不被发现。
- HTTPS:使用消息认证码(MAC)来验证数据的完整性,确保数据在传输过程中未被篡改。
4. 身份验证
- HTTP:没有内置的身份验证机制,无法确认通信对方的身份。
- HTTPS:使用数字证书来验证服务器的身份,确保客户端与合法的服务器通信。证书由受信任的证书颁发机构(CA)签发。
5. 连接过程
HTTP 连接过程:
- DNS 解析:将域名解析为 IP 地址。
- TCP 连接:通过 TCP 三次握手建立连接。
- 发送 HTTP 请求:客户端发送 HTTP 请求到服务器。
- 接收 HTTP 响应:服务器处理请求并返回响应。
- 关闭连接:通过 TCP 四次挥手关闭连接(如果未使用 keep-alive)。
HTTPS 连接过程:
- DNS 解析:将域名解析为 IP 地址。
- TCP 连接:通过 TCP 三次握手建立连接。
- TLS 握手:
- 客户端问候:客户端发送支持的加密算法和随机数。
- 服务器问候:服务器选择加密算法,返回数字证书和随机数。
- 证书验证:客户端验证服务器证书的有效性。
- 密钥交换:客户端生成并发送预主密钥,服务器使用私钥解密。
- 对称密钥生成:客户端和服务器使用预主密钥和随机数生成对称密钥。
- 握手完成:双方使用对称密钥加密通信。
- 发送 HTTPS 请求:客户端发送加密的 HTTP 请求到服务器。
- 接收 HTTPS 响应:服务器处理请求并返回加密的响应。
- 关闭连接:通过 TCP 四次挥手关闭连接(如果未使用 keep-alive)。
6. 性能
- HTTP:由于没有加密开销,HTTP 请求和响应速度通常比 HTTPS 更快。
- HTTPS:由于需要进行加密和解密操作,以及 TLS 握手过程,HTTPS 通信在初始连接时会有额外的开销,但现代硬件和优化技术(如 TLS 会话重用、HTTP/2)已大大减少了性能影响。
7. 使用场景
- HTTP:适用于不涉及敏感数据的场景,如公共信息展示(新闻网站、博客等)。
- HTTPS:适用于需要保护敏感数据的场景,如在线支付、用户登录、电子邮件通信等。当前趋势是所有网站都使用 HTTPS,以提高安全性和用户信任度。
19、HTTPS 如何确保安全性?
HTTPS通过使用 SSL/TLS 协议来确保通信的安全性。SSL/TLS 协议提供了以下几种安全性保证:
1. 数据加密
- 对称加密:客户端和服务器之间的通信会使用对称加密算法来加密传输的数据。在 SSL/TLS 握手阶段,客户端和服务器会协商使用的加密算法和密钥,然后使用该密钥来加密数据。常见的对称加密算法有 AES、DES、3DES 等。
- 非对称加密:SSL/TLS 握手阶段还会使用非对称加密算法来交换对称加密所需的密钥。客户端会向服务器发送一个公钥,服务器使用该公钥加密对称密钥并发送回客户端,客户端再使用自己的私钥解密获取对称密钥。常见的非对称加密算法有 RSA、DSA、ECC 等。
2. 数据完整性
- 消息认证码(MAC):SSL/TLS 使用 HMAC(Hash-based Message Authentication Code)算法来计算消息的 MAC 值,用于验证数据的完整性。MAC 值会与数据一起传输,接收方会使用相同的密钥和算法来计算 MAC 值并验证数据的完整性。
3. 身份验证
- 数字证书:服务器会向客户端发送一个包含其公钥和身份信息的数字证书,证书由受信任的证书颁发机构(CA)签发。客户端在收到证书后会验证其有效性,包括检查证书的签名、有效期等信息,以确认服务器的身份。
- 客户端身份验证:在某些情况下,服务器也可以要求客户端进行身份验证,客户端会发送自己的数字证书供服务器验证。
4. 安全协议
- SSL/TLS 协议:SSL/TLS 协议本身具有安全性,通过使用加密、认证和完整性校验等机制,确保通信的安全性。
20、为什么不能只使用非对称加密?
1. 效率低
- 计算复杂度高:非对称加密算法(如 RSA、DSA)的计算复杂度较高,特别是对于较长的密钥长度,加密和解密的时间会更长。
- 密钥长度:为了保证安全性,通常需要使用较长的密钥长度,这会导致加密和解密的速度进一步降低。
- 限制传输数据量:非对称加密算法能够加密的数据量有限,一般情况下只能用于加密对称密钥等较小数据,无法直接应用于加密大量数据的通信。
2. 安全性难以保证
- 密钥管理:非对称加密需要管理公钥和私钥,公钥可以公开,但私钥必须严格保密。在实际应用中,密钥的生成、存储和分发等环节都需要严格控制,否则容易被攻击者获取私钥。
- 中间人攻击:如果攻击者能够截获通信双方的公钥,并将自己的公钥伪装成对方的公钥,就能够实施中间人攻击,窃取通信内容。
- 完整性保证:非对称加密本身并不能保证数据的完整性,需要结合消息认证码(MAC)等机制才能确保数据的完整性。
综合考虑效率和安全性,通常会将非对称加密与对称加密结合使用:
- 使用非对称加密算法交换对称密钥:通信双方使用非对称加密算法交换对称密钥,然后使用对称加密算法加密通信数据,提高加密效率。
- 结合消息认证码(MAC):在加密通信数据的同时,使用 MAC 算法计算消息的完整性,确保通信数据没有被篡改。
21、说一下 CA 证书?
CA证书(Certificate Authority certificate),又称为根证书或信任锚点,是由受信任的证书颁发机构(CA)签发的数字证书,用于验证其他数字证书的有效性。CA证书本身是一种特殊的数字证书,其作用是构建信任链,确保通信的安全性。
- 证书结构:CA 证书通常采用 X.509 标准,包含了多项信息,如颁发者信息、持有者信息、公钥、有效期等。其中最重要的是证书的签名,用于验证证书的完整性和真实性。
- 作用:
- 信任链:CA 证书构成了数字证书的信任链。如果客户端信任某个 CA 证书,就会信任由该 CA 颁发的所有数字证书。
- 验证其他证书:通过验证 CA 证书,可以验证其他数字证书的有效性。如果某个数字证书的签发者是一个受信任的 CA,那么这个数字证书就可以被信任。
- 获取和验证:
- 获取:CA 证书可以通过 CA 的网站或其他途径获取。
- 验证:验证 CA 证书的有效性,通常需要检查证书的签名是否有效、证书是否在有效期内、证书是否被吊销等。
- 传输:CA 证书通常通过网络传输,特别是在 HTTPS 等安全通信协议中,服务器会将自己的 CA 证书发送给客户端,以便客户端验证服务器的身份。
- 信任管理:CA 证书的管理和保护非常重要。私钥泄露或证书被伪造都可能导致通信安全问题。因此,CA 机构会采取严格的安全措施来保护其 CA 证书。
22、CA证书是由谁传输的?
CA 证书通常是通过网络进行传输的。在使用 HTTPS 等安全通信协议时,服务器会将自己的 CA 证书发送给客户端,以便客户端验证服务器的身份。这个过程通常是在握手阶段完成的,服务器在握手过程中会将自己的 CA 证书发送给客户端,客户端收到后会验证证书的有效性。如果验证通过,客户端就会信任服务器,并继续安全通信。
23、如何使用 CA 证书(CA 证书是如何保证安全的)?
- 获取 CA 证书:首先,需要获取受信任的 CA 机构颁发的 CA 证书。这通常是通过操作系统或浏览器内置的信任 CA 列表来获取的,这些列表中包含了一系列受信任的 CA 机构的根证书。
- 验证 CA 证书:在建立安全通信时,服务器会将自己的数字证书(包括 CA 证书)发送给客户端。客户端接收到数字证书后,会首先验证 CA 证书的有效性。这个过程包括检查证书的签名是否有效、证书是否在有效期内、证书是否被吊销等。
- 验证服务器证书:如果 CA 证书验证通过,客户端会继续验证服务器证书(即服务器的数字证书)。客户端会检查服务器证书的签名是否有效、证书是否在有效期内、证书是否被吊销等。如果服务器证书验证通过,客户端就会信任服务器,并继续安全通信。
- 建立安全通信:如果服务器证书验证通过,客户端和服务器就会建立安全通信,使用对称加密算法加密通信数据,并使用 MAC 算法验证数据的完整性。
- 定期更新 CA 证书:由于 CA 证书有一定的有效期限,因此需要定期更新 CA 证书,以确保通信的安全性。
24、如果访问两个不同的网站,如何获取 CA 的公钥?
- 建立安全连接:当你访问一个使用 HTTPS 协议的网站时,浏览器会向服务器发起连接请求。
- 服务器发送证书:服务器会将其数字证书(包括 CA 证书和服务器证书)发送给浏览器。
- 验证证书:浏览器会首先验证 CA 证书的有效性。如果 CA 证书验证通过,浏览器会继续验证服务器证书。
- 获取 CA 的公钥:如果服务器证书验证通过,浏览器会从 CA 证书中获取 CA 的公钥。这个过程是自动完成的,用户无需手动获取 CA 的公钥。
- 建立安全通信:浏览器和服务器会使用 CA 的公钥和服务器的公钥来建立安全通信,保证通信的安全性。
25、手撕算法:无重复字符的最长子串
思路
- 使用两个指针
left
和right
来表示滑动窗口的左右边界,初始时都指向字符串的开头。 - 使用哈希表(unordered_map)来记录当前窗口中每个字符出现的次数。
- 不断移动右指针
right
,直到遇到重复字符为止。在移动过程中,更新哈希表,并记录当前窗口中不含重复字符的最长子串长度。 - 一旦遇到重复字符,移动左指针
left
,直到移除重复字符,然后继续移动右指针right
。 - 不断重复步骤 3 和 4,直到右指针到达字符串末尾。
参考代码
C++
#include <iostream>
#include <unordered_map>
#include <algorithm>
using namespace std;
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> charIndexMap; // 用于记录字符最近一次出现的位置
int left = 0; // 滑动窗口左边界
int maxLength = 0; // 最长不含重复字符的子串长度
for (int right = 0; right < s.length(); right++) {
if (charIndexMap.find(s[right]) != charIndexMap.end() && charIndexMap[s[right]] >= left) {
// 如果字符已经在窗口内出现过,并且最近一次出现的位置在当前窗口内
left = charIndexMap[s[right]] + 1; // 移动左边界到重复字符的下一个位置
}
charIndexMap[s[right]] = right; // 更新字符最近一次出现的位置
maxLength = max(maxLength, right - left + 1); // 更新最长子串长度
}
return maxLength;
}
int main() {
string s = "abcabcbb"; // 示例输入
int result = lengthOfLongestSubstring(s);
cout << "最长不含重复字符的子串长度为:" << result << endl; // 输出结果
return 0;
}