二叉树总结:
动态规划总结:
回溯算法:
dfs框架:
数组和指针的关系:arr[0] <=> *(p+0)
指针的偏移量,偏移量为0,指向第一个元素。
大小端模式,验证方法,一级指针验证,联合体
内存换分的基本单位:按照字节划分
大小端模式讲解
程序测试
#include<stdio.h>
//地址 %p 输出默认16进制格式
int main() {
int a = 0x64; //以十六进制存储100,就是64,系统默认
int b = 100;//100存储在内存里,就是64,十六进制
int c = 0x12345678;
//判断大小端模式
char* p = (char*)&c;//需要强制转换类型
if (*p == 0x78)printf("是小端模式\n");
else printf("是大端模式\n");
//内部存储测试
printf("%x\n", *p);
printf("%x\n", *(p+1));//*p解引用的能力只有一个字节,0x56
int* q =&c;
printf("%x\n", *q);//解引用能力是4个字节
return 0;
}
运行结果
测试及讲解
变量a的地址是0x00B6FE20 变量a是整型,有四个字节,所以根据进制装换,64就是0110 0100,一个字节,根据小端存储,低位存储在低内存,就是64 00 00 00
变量c的地址是0x00B6FE08 变量c是整型,有四个字节,根据小端存储,低位存储在低内存,就是78 56 34 12
联合体验证大小端
union name {
int a;
char c;
};
int main()
{
name s;
s.a= 0x12345678;
if (s.c == 0x78) {
printf("小端模式");
}
else { printf("大端模式"); }
return 0;
}//输出结果是小端模式
atoi函数的实现
//atoi()函数的实现
int myatoi(const char* str) {
int flag = 1,i=0,num=0;//用flag来标记正负数
while (str[i] == ' ')i++;
//下面这句可以去掉
//if (str[i] != '+' && str[i] != '-' && !isdigit(str[i]))return 0;
if (str[i] == '+') {
flag = 1;
i++;
if (str[i] == '+' || str[i] == '-')return 0;
}
else if (str[i] == '-') {
flag = -1;
i++;
if (str[i] == '+' || str[i] == '-')return 0;
}
while (isdigit(str[i])) {
num = num * 10 + str[i] - '0';
i++;
}
return flag * num;
}
将itoa函数实现 用到的知识
辗转相除法
字符数组翻转
查表法
将整型转换成无符号类型的整型
注意一个负数转换为十进制数,比如 整数 -1 用十进制转化为字符串,就是 “-1”
char* myitoa(int value, char* buff, int radix) {
if (buff == NULL)return 0;
char alpha[] = "0123456789abcdefghijklmnopqrstuvwsyz";
int flag = 0;
if (value < 0 && radix == 10) {
buff[0] = '-';
value *= -1;
flag = 1;
}
unsigned int val = (unsigned int)value;//将有符号的数转换成无符号的数
//-1 二进制内部存储为1111 1111 和无符号类型的1111 1111 即255 内部存储相同
int i = flag;
while (val != 0) {
buff[i++] = alpha[val % radix];
val /= radix;
}
buff[i] = '\0';
for (int j = 0; j < =(i - 1) / 2; j++) {//buff中有4个,交换2次,有3个也交换2次,第二次自己和自己交换
int temp = buff[j + flag];//flag目的是看是否是负数并且是十进制
buff[j + flag] = buff[i - 1 - j];
buff[i - 1 - j] = temp;
}
return buff;
}
测试结果
int main() {
char a[128] = { 0 };
char b[128] = { 0 };
char c[128] = { 0 };
char d[128] = { 0 };
myitoa(-1, a, 10);
myitoa(10, b, 2);
myitoa(-1, c, 2);
myitoa(10, d, 16);
printf("%s %s %s %s\n", a,b,c,d);
}
//-1 1010 11111111111111111111111111111111 a
1.内存划分基本单位是按字节划分,CPU内存非单字节进行,2 4 8的倍数
2.不同平台上的对齐方式不同,都有指定的对齐方式。windows:8 linux:4
#pragma pack(8) 开始按照8字节对齐
//
//
#pragma pack() 结束//在此区间是按照8字节对齐
结构体大小满足的规律:
1.结构体变量的首地址,必须是min[最大基本数据类型占字节数,指定对齐方式]
2.结构体每个成员相对于结构体首地址偏移量,是min[该成员数据类型,指定对齐方式]
3.结构体总大小:min[最宽基本数据类型,指定对齐方式]
struct A
{
char c;//1+1
short b;//2
char e;//1+3
int i;//4
};//总大小为 12
struct A
{
double c;//8
float b;//4
char e;//1
char i;//1+2
int a;//4 最终的大小为8的整数倍
};//最终大小为 24
struct B
{
int c;//4
char d;//1
};//最终大小为8的整数倍,所以为8
struct A
{
char c;//1+1
short b;//2 不加4,前面是基本数据类型的整数倍
B e;//8
int a;//4 最终的大小为4的整数倍
};//最终大小为 16
struct A
{
char name[10];//10+2
int a;//4
char* sex;//4 x86指针大小是4个,x64是8个
};//最终大小为 20
#pragma pack(2)
struct B
{
double t;//8
int c;//4
};//最终大小为2的整数倍,所以为12
#pragma pack()
#pragma pack(4)
struct A//A中最大的数据类型是结构体B中的double 8
{
int b;//4
char a;//1
B e;//12//不是基本数据类型,不用满足是前面的整数倍
char d;//1+2
};//最终大小为 20
#pragma pack()
不是基本数据类型,不用满足是前面的整数倍
#pragma pack(2)
struct A{
int a;
char b;
}//6
#pragma pack()
#pragma pack(4)
struct B{
char c;//1
char d;//1
A a;//6
int e;//4
}//12
#pragma pack()
//交换a的顺序
#pragma pack(4)
struct B{
char c;//1
char d;//1+2
int e;//4
A a;//6
}//16
#pragma pack()
这篇博客讲的是查找重复数字的最左边问题:link
在这个实现中,当找到某个目标值时,按照原二分法中的大于处理,因为我们想找到的是最左目标值
这篇是二分查找算法的一个汇总:link:
//饥汉模式
class Object {
private:
//单例模式,构造函数为私有
int value;
Object(int x=0):value(x){}
static Object obj;
//不存在拷贝构造和赋值重载
Object(const Object&) = delete;
Object& operator=(const Object&) = delete;
public:
~Object() {}
static Object* GetObject(int x) {
static Object obj(x);
return &obj;
}
void Print() const { cout << "value" << value << endl; }
};
Object Object::obj(45);
//懒汉模式
class Object {
private:
//单例模式,构造函数为私有
int value;
Object(int x = 0) :value(x) {}
static Object* obj;
//不存在拷贝构造和赋值重载
Object(const Object&) = delete;
Object& operator=(const Object&) = delete;
public:
~Object() {}
static Object* GetObject(int x) {
//需要对象构建,不需要就不构建
if (obj == nullptr) {
obj = new Object(x);
}
return obj;
}
void Print() const { cout << "value" << value << endl; }
};
Object* Object::obj = nullptr;
异步描述的是IO的状态,一个典型的IO包含两个阶段 数据就绪 和数据读写
数据就绪 recv(fd,buff,64,0),当IO 工作在阻塞模式下,数据没有就绪 ,recv就会阻塞在当前的线程,当sockfd 工作在非阻塞模式下,数据没有就绪 ,recv就会立即返回,返回值为-1 连接出现问题 返回值为0 或者为EAGAIN 表示正常的一个非阻塞IO返回的数据,如果返回值大于0 就需要花费用户自己的时间 将数据从内核tcp接收缓冲区拷贝到recv的参数buff
拷贝完成后,recv才会返回
如果是异步,需要通过一步IO接口aio_read aio_write 传入sockfd buff 通知方式,可以通过回调 信号告诉异步IO,如果有消息 ,你帮我将内核tcp缓冲区的数据搬到buff,在通过实现约定好的通知方式,通知我应用程序 ,我自己处理自己的业务
业务层面:同步 A操作调用B操作的API接口时,A操作一直在等待B,等待B操作做完事情,得到返回值,继续处理
异步:A操作告诉B,我对什么时间感兴趣,告诉通知我的方式,A操作继续执行自己的业务逻辑;当B操作监听这个时间发生后,用实现约定的通知方式,通知A,让A处理
索引的文件存储形式与存储引擎有关
innoDB 当前使用
InnoDB是 MySQL的默认事务型引擎,也是最重要、使用最广泛的存储引擎。它被设计用来处理大量的短期(short-lived)事务,短期事务大部分情况是正常提交的,很少会被回滚。InnoDB的性能和自动崩溃恢复特性,使得它在非事务型存储的需求中也很流
MyISAM早期默认
在MySQL 5.1及之前的版本,MyISAM是默认的存储引擎。MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM 不支持事务和行级锁
由存储引擎决定:
innodb支持事物,早期myisam不支持
innodb用的是B+树,其他不知道
用B+树是因为B+树层数少,适合作为索引
不好,一个索引就要建立一个B+树,查找速度变快,但是增加删除变慢,将查询频率最高的加上索引。
看范式:
目前的隔离级别是 可重复读 即 幻读
如果只有一条语句,其实先 begin 执行完后再 commit。
对同一个数据库的表
有两个用户都开启了事物
a修改了表中的数据 提交与否
b都看不见修改后的数据 可重复读
只有a提交后,b回滚或者提交,从新开启事物,打开表,才能看到修改
可提交读
对同一个数据库的表
有两个用户都开启了事物
a修改了表中的数据 提交后
b在该事物中可以直接看到修改后的数据
未提交读
对同一个数据库的表
有两个用户都开启了事物
a修改了表中的数据 都不用提交,只要一修改
b在该事物中可以直接看到修改后的数据
int main() {
int a = 10, b = 20;
int len = 0;
len = printf("a=%d b=%d \n", a, b);
len = fprintf(stdout, "a=%d b=%d \n", a, b);
cout << len << endl;//len=11 是字节数
return 0;
}
//… 代表可变参 可变参在内存的分布情况
小于等于4字节,分配都是4字节,
单精度还是双精度都是8字节。
字符串存储的是一个指针。
//最后一次 解包啥也不干 "递归"
void func() {
}
//可变参模板
template<class T,class ... Args>
void func(T t,Args ... args) {
cout << sizeof...(args)<<" ";
cout << t << " " << endl;
func(args...);
}
//引用型别未定义 的变参模板 完美转发
void print(const int&,int a) {}
void print(int&&a,double b) {}
void print(int&t,const char*a) {}
template<class... Args>
void test(Args&&... t) {
print(forward<Args>(t));
}
int main() {
func(1,12, 'a',"abcde");
return 0;
}
memcmp不能用来比较结构体变量,结构体变量中空间有填充,有内存对齐的问题
手撕void*memcpy(…)
memcpy和memmove的区别和实现
my_atoi
my_itoa
TCP和udp的区别
6+tcp通讯是全双工通讯+使用场景
三次握手,不能连接的情况
滑动窗口,处理什么问题,有很大的文件,如何传送,
粘包问题,数据如何保证一致的,数据的正确性。用MD5对数据进行加密,计算哈希值,传输的时候连从报文一起传送,再通过MD5计算哈希值,看这两次计算出的哈希值是否一致
网络攻击,大规模的连接,如何处理:listen队列充满,如何解决该问题。
网络攻击有哪些方式,如何对付这些攻击。
看哪些申请连接是从同一个ip地址发送过来的,直接记录删除这些连接
监听函数内部的实现:
多考虑不正常的情况。
如何保证客户端是否存活:心跳机制,实现方式。
定时器如何实现:定时检测。
拥塞控制:慢启动,和快启动。还得想想,更加明确一些。有应用的场景
Redis常用的数据结构:哈希表和STL哈希表的区别,谁的更加平滑:stl断崖式的,一次性倒换,redis是慢慢的倒换。
介绍一下负载均衡:
如何实现Nginx的负载均衡算法:和一致性哈希算法
正向代理和反向代理服务器
集群和分布式的区别
为什么构造函数不写为虚函数?
从存储空间角度:虚函数对应一个vtable,可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到vtable,所以构造函数不能是虚函数。
从使用角度:虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。
extern的作用?3种
const作用?
不考虑类的情况
const常量在定义时必须初始化,之后无法更改
const形参可以接收const和非const类型的实参,例如// i 可以是 int 型或者 const int 型void fun(const int& i){ //…}
考虑类的情况
const成员变量:不能在类定义外部初始化,只能通过构造函数初始化列表进行初始化,并且必须有构造函数;不同类对其const数据成员的值可以不同,所以不能在类中声明时初始化。
const成员函数:const对象不可以调用非const成员函数;非const对象都可以调用;不可以改变非mutable(用该关键字声明的变量可以在const成员函数中被修改)数据的值。
C++11新特性C++11三种类型,十几种新特性
内存泄漏 堆资源+系统资源+基类析构函数+浅拷贝+指针循环引用 内存泄漏 分5种,如何避免
迭代器失效问题 迭代器失效问题
迭代器实现 迭代器原理
volatile 详解volatile
概述volatile
override final override final详解
消息队列 线程中消息队列的简单实现
回调函数是,回到本质回调函数的本质
快排 和 归并 堆排
最长无重复子串
最大子数组和
最长公共子序列
最长递增子序列
最长回文字符串
回文字符串
不同子序列
股票问题最佳时间可多次买入
随机子集
迷宫
接雨水
青蛙跳台阶
链表是否有环 入口
K个升序链表的合并
反转链表 递归和头插
LRU缓存 两种一个是不用list,一个是 用list
去除链表的重复节点
用栈实现队列,用队列实现栈
中序遍历 迭代+递归
后序遍历非递归
序列化和反序列化二叉树
约瑟夫环
两数之和
三数之和
逆序数组
合并两个有序数组 n个数组
最大相同前缀
第k大的元素
一堆单词中找频率前三的单词
实现一个加法函数不用+
合并区间
字符串转浮点数
平方根
实现环形队列
手写单例 饿汉和饥汉
说一下分库分表?
当数据量过大造成事务执行缓慢时,就要考虑分表,因为减少每次查询数据总量是解决数据查询缓慢的主要原因。
为了应对高并发,一个数据库实例撑不住,即单库的性能无法满足高并发的要求,就把并发请求分散到多个实例中去,这种就是分库。
总的来说,分库分表使用的场景不一样: 分表是因为数据量比较大,导致事务执行缓慢;分库是因为单库的性能无法满足要求。
说一下binlog?两种功能+3中模式
binlog日志是MySQL数据库的一种日志记录机制,用于记录数据库的修改操作(如插入、更新、删除等)
binlog日志的实现以下功能:
数据恢复:binlog日志可以用于回滚到之前的某个时间点,从而恢复数据。
数据复制:binlog日志可以用于在主从数据库之间复制数据,从而实现数据的高可用和负载均衡等功能。
MySQL的binlog日志有三种格式,分别是Statement格式、Row格式和Mixed格式。它们之间的区别如下:
STATEMENT:每一条修改数据的 SQL 都会被记录到 binlog 中(可以称为逻辑日志),主从复制中 slave 端再根据 SQL 语句重现。但 STATEMENT 有动态函数的问题,比如你用了 uuid 或者 now 这些函数,你在主库上执行的结果并不是你在从库执行的结果,这种随时在变的函数会导致复制的数据不一致;
ROW:记录行数据最终被修改成什么样了(这种格式的日志,就不能称为逻辑日志了),不会出现 STATEMENT 下动态函数的问题。但 ROW 的缺点是每行数据的变化结果都会被记录,使 binlog 文件过大,而STATEMENT 格式下只会记录一个 update 语句而已;
MIXED:包含了 STATEMENT 和 ROW 模式,它会根据不同的情况自动使用 ROW 模式和 STATEMENT 模式;
char和varchar的区别?最大长度255+是否填充0+读取速度+存储空间大小
CHAR类型用于存储固定长度的字符串,且最大长度为255个字符。当存储的字符串长度小于定义的长度时,MySQL会在其后面补充空格使其长度达到定义的长度。由于存储的长度是固定的,因此CHAR类型的读取速度比VARCHAR类型更快。
VARCHAR类型则用于存储可变长度的字符串,但最大长度也为255个字符。当存储的字符串长度小于定义的长度时,MySQL不会在其后面补充空格。由于存储的长度是可变的,因此VARCHAR类型的存储空间相对更小,但读取速度比CHAR类型稍微慢一些。
那与varchar相比,char字段是不是一无是处呢?
大部分情况,是的,最好使用varchar。不过考虑一个极端的场景:某个字段的最大长度是100字节,但是会频繁修改。如果使用char(100),则插入记录后就分配了100个字节,后续修改不会造成页分裂、页空隙等问题,而varchar(100)由于没有提前分配存储空间,后续修改时可能出现页分裂,进而导致性能下降。
说一下外键约束?
外键约束的作用是维护表与表之间的关系,确保数据的完整性和一致性。让我们举一个简单的例子:
假设你有两个表,一个是学生表,另一个是课程表,这两个表之间有一个关系,即一个学生可以选修多门课程,而一门课程也可以被多个学生选修。在这种情况下,我们可以在学生表中定义一个指向课程表的外键,这里,students表中的course_id字段是一个外键,它指向courses表中的id字段。这个外键约束确保了每个学生所选的课程在courses表中都存在,从而维护了数据的完整性和一致性。这里,students表中的course_id字段是一个外键,它指向courses表中的id字段。这个外键约束确保了每个学生所选的课程在courses表中都存在,从而维护了数据的完整性和一致性。
MySQL
数据库中的锁
NoSQL和关系型数据库的区别?
数据模型 数据结构 可扩展性 ACID特性一个支持一个不支持 事物不同 查询语言
搜索引擎
主键索引和普通索引的区别
主键索引和普通索引的区别在于,主键索引必须是唯一的,而普通索引可以包含重复的值。主键索引可以加速数据的查找和排序,并且可以保证数据的唯一性。普通索引则是一种常规的索引,用于加速数据的查找和排序,但不保证数据的唯一性 。
脏读 幻读
隔离级别如何实现
持久性是通过 redo log (重做日志)来保证的;
原子性是通过 undo log(回滚日志) 来保证的;
隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的;
一致性则是通过持久性+原子性+隔离性来保证;
MySQL-原子性怎么实现的?事务的原子性是通过 undo log 实现的。在发生回滚时,就读取 undo log 里的数据,然后做原先相反操作
MySQL-持久性是怎么实现的?
务的持久性是通过 redo log 实现的。
我们修改某条记录,其实该记录并不是马上刷入磁盘的,而是将 Innodb 的 Buffer Pool 标记为脏页,等待后续的异步刷盘。
为了防止断电导致数据丢失的问题,对这个页的修改以 redo log 的形式记录下来,这个时候更新就算完成了。
WAL 技术指的是, MySQL 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。
操作系统-pageCache是什么?
为了提升对文件的读写效率,Linux 内核会以页大小(4KB)为单位,将文件划分为多数据块。当用户对文件中的某个数据块进行读写操作时,内核首先会申请一个内存页(称为 页缓存)
操作系统-怎么避免死锁?
kill+指定规则+银行家算法
mysql主从复制
MySQL主从复制是一种数据复制技术,它允许将来自一个MySQL数据库服务器(主服务器)的数据复制到一个或多个MySQL数据库服务器(从服务器)。主服务器负责写,从服务器负责读。
在MySQL主从复制中,主服务器上的所有更改都会记录到二进制日志(binlog)中,然后从服务器可以从这些日志中读取这些更改并将其应用于自己的数据库。这种技术可以提高数据的可用性和可靠性,并允许从服务器执行备份和恢复操作。
redo log undo log
mvcc?
MVCC是多版本并发控制(Multi-Version Concurrency Control)的缩写,是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问。在Mysql的InnoDB引擎中就是指在已提交读 (READ COMMITTD)和可重复读 (REPEATABLE READ)这两种隔离级别下的事务对于SELECT操作会访问版本链中的记录的过程。这就使得别的事务可以修改这条记录,反正每次修改都会在版本链中记录。SELECT可以去版本链中拿记录,这就实现了读-写,写-读的并发执行,提升了系统的性能 。
事物的底层原理
数据库事务的底层原理是指数据库在执行事务时,为了保证事务的原子性、一致性、隔离性和持久性,所采取的一系列技术手段。其中,原子性指事务是一个不可分割的工作单位,事务中的操作要么全部完成,要么全部不完成;一致性指事务执行前后,数据库从一个一致性状态变为另一个一致性状态;隔离性指并发执行的事务之间不能互相干扰;持久性指事务提交后,对数据库的修改是永久性的 。
这个读可重复读和那个读提交这两个有什么区别?
「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View。
可重复读下,快照是在什么时候生成的,是事务启动时,还是语句执行前?
执行了 begin/start transaction 命令后,并不会创建 read view,只有在第一次执行 select 语句后, 才会创建 read view。
执行了 start transaction with consistent snapshot 命令,就会马上创建 read view。
聚簇索引和非聚簇索引
聚簇索引和非聚簇索引的区别3点
索引的分类 索引失效等一系列问题
回表 索引覆盖 索引下推
最左端匹配原则
两表联合查询
如何建立联合索引
不要对太多的列创建联合索引,否则会影响插入、更新和删除操作的性能。
应该选择经常用于查询条件的列来创建联合索引。
联合索引的顺序很重要,应该将最常用作查询条件的列放在联合索引的最左边。
分布式锁的实现和四个特性?
互斥,安全,对称,可靠。
分布式锁的实现?
Redis
缓冲击穿基本数据结构 持久化 主从复制 缓存穿透和雪崩
redis持久化机制
redis淘汰机制
Redis常用数据类型和底层数据结构
Redis常用数据结构底层实现
redis多路复用redis多路复用
redis数据结构,三种缓存问题
定时删除和定期删除的区别
redis为什么快?
内存完成;单线程避免多线程竞争;io复用机制,一个线程处理多个io流
map怎么扩容,扩容时会影响缓存吗?
随着数据逐步增多,触发了 rehash 操作,这个过程分为三步:
给「哈希表 2」 分配空间,一般会比「哈希表 1」 大 2 倍;
将「哈希表 1 」的数据迁移到「哈希表 2」 中;
迁移完成后,「哈希表 1 」的空间会被释放,并把「哈希表 2」 设置为「哈希表 1」,然后在「哈希表 2」 新创建一个空白的哈希表,为下次 rehash 做准备。
渐进式 rehash 步骤如下:
给「哈希表 2」 分配空间;
在 rehash 进行期间,每次哈希表元素进行新增、删除、查找或者更新操作时,Redis 除了会执行对应的操作之外,还会顺序将「哈希表 1 」中索引位置上的所有 key-value 迁移到「哈希表 2」 上;
随着处理客户端发起的哈希表操作请求数量越多,最终在某个时间点会把「哈希表 1 」的所有 key-value 迁移到「哈希表 2」,从而完成 rehash 操作。
这样就巧妙地把一次性大量数据迁移工作的开销,分摊到了多次处理请求的过程中,避免了一次性 rehash 的耗时操作。
binlog和redolog做数据恢复的区别?
binlog是MySQL的二进制日志,它记录了所有对MySQL数据库的修改操作,包括插入、更新和删除等。binlog可以用于恢复MySQL数据库到指定的时间点或者指定的事务。具体来说,可以使用mysqlbinlog命令将binlog文件解析成SQL语句,然后再执行这些SQL语句,从而恢复MySQL数据库的状态。
redolog是MySQL的重做日志,它记录了所有对MySQL数据库的修改操作,但是只记录了物理操作,比如页的修改。redolog可以用于恢复MySQL数据库的崩溃恢复,即在MySQL崩溃后,通过重做日志,将数据库恢复到最近一次提交的状态。具体来说,可以使用innodb_recovery命令来进行崩溃恢复,该命令会根据重做日志来恢复数据库。
因此,binlog和redolog都可以用于数据恢复,但是它们的使用场景和恢复方法有所不同。binlog主要用于数据恢复到指定时间点或者指定事务,而redolog主要用于MySQL的崩溃恢复。
http协议的报文的格式有了解吗?
HTTP和HTTPS有什么区别?
1.HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
2.HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
3.两者的默认端口不一样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443。
4.HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。
HTTP1.0和2.0的区别,或者2.0和3.0的区别?
1.头部压缩HTTP/2 会压缩头(Header)如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的部分。
2.HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式,头信息和数据体都是二进制,并且统称为帧(frame)加了数据传输的效率。
3. 并发传输
我们都知道 HTTP/1.1 的实现是基于请求-响应模型的。同一个连接中,HTTP 完成一个事务(请求与响应),才能处理下一个事务,引出了 Stream 概念,多个 Stream 复用在一条 TCP 连接
4、服务器推送
HTTP/2 还在一定程度上改善了传统的「请求 - 应答」工作模式,服务端不再是被动地响应,可以主动向客户端发送消息。
客户端和服务器双方都可以建立 Stream, Stream ID 也是有区别的,客户端建立的 Stream 必须是奇数号,而服务器建立的 Stream 必须是偶数号
HTTP/2 有什么缺陷?
HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。
什么是零拷贝零拷贝 MDA技术,sendfile系统调用,2次上下文切换和拷贝数据,nginx kafka
URL解析过程
tcp 头部结构 ipv4报文头部结构
mmap
对称加密与非对称加密
DNS解析过程 证书里面啥数据
HTTPS加密方式
用到过哪些加密算法?
对称加密算法:如DES、3DES、AES等,使用相同的秘钥加密和解密数据,加解密速度快,但秘钥管理困难。
非对称加密算法:如RSA、ECC等,使用公钥加密数据,私钥解密数据,安全性高,但加解密速度较慢。
哈希算法:如MD5、SHA-1、SHA-256等,将任意长度的数据映射成固定长度的哈希值,不可逆、不可篡改,主要用于数据完整性校验。
HTTPS 采用的是对称加密和非对称加密结合的「混合加密」方式:
在通信建立前采用非对称加密的方式交换「会话秘钥」,后续就不再使用非对称加密。
在通信过程中全部使用对称加密的「会话秘钥」的方式加密明文数据。
udp和tcp的区别 8点
select和poll epoll的区别 2次拷贝2次遍历,bitsmap 链表,红黑树 +链表
TCP是如何保证可靠的?
tcp的序列号可以避免乱序的问题,保证收到的tcp报文都是有序的。
在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。
TCP 针对数据包丢失的情况,会用重传机制解决。
用快重传解决个别报文段的丢失问题。
使用滑动窗口实现流量控制。使用接收方确认报文中的窗口字段来控制发送方发送窗口大小,进而控制发送方的发送速率,使得接收方来得及接收。
使用基于窗口的拥塞控制,来尽量避免避免网络拥塞。
自旋锁和互斥锁的区别开销+试用场景+休眠或者等待
堆内存和栈内存有什么区别?
1.管理方式不同:栈内存由系统自动分配和释放,而堆内存则需要程序员手动分配和释放。
2.分配方式不同:栈内存是一种连续的内存空间,系统会自动为每个线程分配一定大小的栈空间,函数的参数和局部变量都会在栈上分配内存。而堆内存是不连续的内存空间,程序员需要通过动态分配内存来获得堆内存空间。
3.大小限制不同:栈内存的大小是固定的,并且比堆内存小得多。程序运行时,每个线程的栈空间通常只有几MB到几十MB,而堆内存的大小则取决于系统的剩余内存大小和程序员的动态分配。
4.访问速度不同:栈内存的访问速度比堆内存快得多,因为栈内存是连续的,访问局部变量和函数参数时可以直接读取栈指针的偏移量。而堆内存是不连续的,访问速度较慢。
线程和进程的区别?
定义+共享资源+开销
从 a 文件 copy 到另外一个目录, b 作为一个从 a 目录 copy 到一个 b 目录这样的一个文件,操作过程中间包含了哪些系统调用?这里面执行了多少次拷贝的动作?
你能简单说一下里操作系统保护模式和实模式,区别在哪?
实模式:模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,系统程序和用户程序并没有区别对待,而且每一个指针都是指向实际的物理地址
保护模式:将整个物理内存看成分段的区域,程序代码和数据位于不同区域,系统程序和用户程序并没有区别对待,而且每一个指针都是指向实际的物理地址
6个内存分区:加上一个内存保留区
注意:堆和文件映射段的内存是动态分配
malloc 是一个系统调用么?
方式一:通过 brk() 系统调用从堆分配内存
方式二:通过 mmap() 系统调用在文件映射区域分配内存;
分页好处 内存碎片?
解决内存碎片 段外和端内 内存交换的效率低
虚拟内存如何映射到物理内存:
三步 划分段号 段内偏移 在cpu中的mmu的段表 查找,根据偏移找到具体的物理内存地址。
什么是虚拟内存:
虚拟内存就是说,让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。虚拟内存使用部分加载的技术,让一个进程或者资源的某些页面加载进内存,从而能够加载更多的进程,甚至能加载比内存大的进程,这样看起来好像内存变大了,这部分内存其实包含了磁盘或者硬盘,并且就叫做虚拟内存
为什么要使用虚拟内存?
对于传统的内存存储管理方式的特征有如下两个:
1)一次性
作业必须一次性全部装入内存后,方能开始运行。这会导致两种情况发生:
当作业很大,不能全部被装入内存时,将使该作业无法运行;
当大量作业要求运行时,由于内存不足以容纳所有作业,只能使少数作业先运行,导致多道程序度的下降。
2)驻留性
作业被装入内存后,就一直驻留在内存中,其任何部分都不会被换出,直至作业运行结束。运行中的进程,会因等待I/O而被阻塞,可能处于长期等待状态。
由以上分析可知,许多在程序运行中不用或暂时不用的程序(数据)占据了大量的内存空间,而一些需要运行的作业又无法装入运行,显然浪费了宝贵的内存资源。
页面置换算法 6+6+5
进程调度算法 页面置换算法 磁盘调度 总结 小林code
协程和线程进程 协程和线程进程
协程与我们普通的线程有什么区别?
1.调度方式不同:线程是由操作系统调度的,而协程则是由程序员控制的。当一个线程被调度时,它会被操作系统挂起,等待下一次调度。而协程则是由程序员在代码中主动调用的,可以在不同的任务之间切换,而不需要等待操作系统的调度。
2.系统资源占用不同:线程是操作系统管理的实体,它占用系统资源比较大,包括内存、线程栈、CPU 时间片等。而协程则是在用户空间中实现的,不需要操作系统的支持,因此占用的资源比较少。
3.切换成本不同:线程的切换需要保存和恢复线程上下文,需要耗费一定的时间和资源。而协程的切换只需要保存和恢复栈帧等少量数据,因此切换成本比线程低。
4.编程模型不同:线程是面向操作系统的,而协程是面向任务的。线程需要使用操作系统提供的 API 进行线程间通信和同步,而协程则可以使用语言级别的协程库实现协作式多任务。
协程的的通讯有哪些方式?
1.共享内存:协程通过共享内存来交换数据,这种方式简单直接,但需要考虑同步和互斥问题,否则会出现数据竞争等问题。
2.消息传递:协程通过消息队列等方式来传递数据,这种方式可以避免数据竞争等问题,但需要考虑消息的发送和接收顺序等问题。
3.信号量:协程通过信号量等方式来实现同步和互斥,这种方式需要考虑好信号量的数量和使用顺序,否则会出现死锁等问题。
避免死锁:
按照固定的顺序获取锁:按照固定的顺序获取锁可以避免死锁的发生。例如,如果A线程先获取了锁1,再获取锁2,那么B线程就应该先获取锁2,再获取锁1。
设置超时时间:在获取锁的过程中,可以设置超时时间,如果超过一定时间还没有获取到锁,就放弃获取锁,避免因等待锁而导致的死锁。
排查方式:
使用工具:可以使用一些工具来帮助检测和定位死锁问题,例如jstack和jconsole等。
分析日志:可以分析系统日志和线程日志,查看是否有线程在等待某个锁,从而找出可能导致死锁的原因。
代码检查:可以检查代码中是否存在多个线程竞争同一个锁的情况,是否存在锁的嵌套等问题,从而找出可能导致死锁的原因。
gdb查看线程信息
info threads:打印所有线程的信息。
thread :切换到第n个线程。
bt:打印当前线程的所有堆栈信息
调表和微服务 kafka高性能 SSH命令
单例模式
手写工厂模式 手写三种工厂模式
手写观察者模式 手写观察者模式
观察者模式(Observer Pattern)是一种设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象状态发生变化时,它的所有观察者都会收到通知并自动更新。
在观察者模式中,有两个核心角色:Subject(主题)和Observer(观察者)。主题是被观察的对象,它维护了一个观察者列表,可以动态添加或删除观察者。当主题状态发生变化时,它会通知所有观察者,并调用它们的更新方法。观察者是接收主题通知的对象,它定义了一个更新方法,使主题在状态发生变化时能够及时通知到它。