C++ 面试准备
C++ 面试准备
1. 语法基础
-
编译过程
-
从源⽂件到可执⾏⽂件的过程?hello.cpp
-
预处理-> hello.i 去除注释、展开宏定义,处理预编译指令
-
编译-> hello.s 将预处理的⽂件进⾏词法分析、语法分析、语义分析产⽣汇编代码
-
汇编-> hello.obj 把汇编代码翻译成⽬标机器指令,⽣成⽬标⽂件(包括代码段、数据段)
-
链接->hello.out 或hello.exe
-
静态链接:将汇编⽣成的⽬标⽂件.o与引⽤到的库⼀起链接打包成可执⾏⽂件。将所用到的外部文件链接在一起
-
动态链接:动态库在程序运⾏是才被载⼊。不同的应⽤程序如果调⽤相同的库,那么在内存⾥只需要有⼀份该共享库的实例,规避了空间浪费问题。动态库在程序运⾏是才被载⼊,也解决了静态库对程序的更新、部署和发布带来麻烦。
-
-
-
include头文件的顺序以及双引号””和尖括号<>的区别?
-
系统头文件在前面,然后是第三方库头文件,最后是自己的头文件。
-
但是如果是头文件的cpp文件,先要包含自己的头文件,然后再按照上面的顺序。//??
-
“ ”先在当前目录查找有无该头文件,有则包含该目录下的头文件,没有则到系统指定的目录下找该头文件,而<>直接到系统指定的目录下查找该文件。
-
-
进程地址空间
数据区 代码区 栈区 堆区
/// Person类
class Person
{
//变量
int id;
int age = 20;
Person(int _id, int _age)
{
id = _id;
age = _age;
}
}
- 栈区 存放临时变量,由编译器分配释放
- 堆区 动态存储区 在程序运行时,new分配,在程序结束释放或者手动释放
- 数据区(数据段) 也称全局区和静态区,存放静态/全局变量,or 静态方法???
- 全局静态变量:只能在一个文件中使用,具有全局性
- 全局变量:可以在其他文件中用extern的方式引用,在创建的文件中也具有全局性
- 代码区: 存放程序编译后可以执行代码的地方,比如执行代码时的while/if语句
- static,const 和#define《大部头》
static
静态 静态变量 只有一份 存在全局区
- 修饰普通变量,修改变量的存储区域和生命周期。是变量存储在静态区,在main函数运行就分配了空间,并初始化
- 修饰普通函数,表明函数的作用范围,被修饰的函数只能在被定义的文件内使用。在多人开发项目时,防止明明冲突
- 修饰成员变量,使所有的对象只保存一个变量,并且不生成对象就可以访问
- 修饰成员函数,不生成对象就可以访问该函数,但是函数内不能包括非静态成员
const
- 函数后面➕const. C++函数前后加const的区别 这个博客有点水
- 各种const位置解释
const的四个作用 - 修饰变量,说明变量不可更改
- 修饰指针,指向常量的指针,和常量指针(指针指向不能修改)
- 修饰引用,变量不能被修改
- 修饰成员函数,说明该成员函数内不能修改成员变量
1. const int a; //a的值不能修改
2.
3. const int fun(const int& a) const;
//1. 返回值不能被修改 cosnt修饰返回值
//2. 函数形参a不能被修改 修饰参数
//3. 调⽤函数的对象不能被修改(只有const函数才能调⽤const对象) 修饰函数
// const成员函数内部,不能调用非const成员函数
--问:如果const成员函数想要改变成员变量怎么办?
--答:如果数据成员是指针,const成员函数可以修改指针指向的对象,编译器不会把这种修改检测为错误。
但是不可以修改指针指向什么。
//const 修饰指针 可以修改指向
int a = 10;
int b = 10;
const int * p = &a;
//*p = 100;//no ,because (const int)
p = &b;//yes
int* const p2 = &a; //p2不能修改 int可以
const int* const p3 指针指向和指针指向数据均不可修改
const可以完全替代#define
- const有数据类型,编译器可以进⾏类型安全检查;后者只进⾏字符替换,不计算,不作表达式求解
- const节省空间,避免不必要的内存分配。const给出相应的内存地址,在程序运⾏过程中只有⼀份拷⻉,不会存储多次;define给出⽴即数,可能会有若⼲个拷⻉。
volatile
-
为了改善编译器的优化能力
-
程序代码如果没有对内存单元进行修改,内存单元也会发生变化
-
假如一个指针指向硬件位置,硬件(而不是程序)可能修改其中的内容。
-
不加volatile时,如果编译器发现程序在几条语句中两次使用某个变量的值,则编译器可能将值缓存入寄存器中。这样导致两次读取变量的值相同
-
添加volitile,告诉编译器别优化
-
能不能修饰指针呢,看起来可以
-
volatile int i; //声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等 const int* i; volatile int* i; //修饰由指针指向的对象、数据是 const 或 volatile 的 char* const pchc; char* volatile pchv; //指针自身的值——一个代表地址的整数变量,是 const 或 volatile 的:
mutable
- 可以在const结构(类)变量中使用,让某些成员忽略const
全局变量 局部变量 静态变量
-
全局变量
- 在函数外部定义的变量
- 作用域:整个程序
- static修饰 只能在本身源文件中使用 :静态全局变量
- extern修饰 可以被其他源文件调用
-
局部变量
- 函数体内定义
- 作用域整个函数体
- static修饰 只初始化一次 : 静态局部变量
- 自动变量(即局部变量)分配时存储空间则是于调用栈上分配,只在调用时分配与释放
-
静态变量
-
类上(还不够深入)
- 静态成员变量
- 静态函数(不属于某个对象,可以调用静态成员变量)
-
[列表初始化] (http://c.biancheng.net/view/3737.html)
-
new / delete 和 malloc/free
-
New/Delete Malloc/Free 属性 C++关键字 单元格 参数 new typename
即可申请内存,编译器通过类型信息自行计算malloc(sizeof(int))
要给出具体字节数返回类 型 分配成功:返回对象类型的指针 返回void* 需要强制类型转换 分配失败:抛出bac_alloc异常 返回NULL 自定义类型 New先调用operator new申请足够内存(用malloc实现),调用构造函数,初始化成员变量,然后返回自定义类型指针
delete 先调用析构函数,然后调用operator delete函数释放内存(用free实现)只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。 重载 允许重载new/delete操作符 不允许重载 内存区域 new操作符从自由存储区(free store)上为对象动态分配内存空间 malloc函数从堆上动态分配内存 创建new/delete的原因 因为创建自定义类型对象,需要执行构造和析构函数。后二者不行
-
-
命名空间的作用
-
“名称空间允许定义一个可在其中声明标识符的命名区域。这样做的目的是减少名称冲突”
摘录来自: [美] Stephen Prata. “C++ Primer Plus(第6版)中文版。” Apple Books.
-
-
内联函数
-
inline 必须与函数定义放在⼀起;⼀般放在头⽂件中;
-
编译时,编译器会把函数的代码副本放置在每⼀个调⽤函数的地⽅。对于其他函数,都是运⾏时才被替代。//程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。
-
类结构中,在类内定义的函数都是内联函数
-
优点:
1)inline定义的内联函数,函数代码被放入符号表中,在使用时进行替换(像宏一样展开),效率很高
2)类的内联函数也是函数。编绎器在调用一个内联函数,首先会检查参数问题,保证调用正确,像对待真正函数一样,消除了隐患及局限性。
3)inline可以作为类的成员函数,也可以使用所在类的保护成员及私有成员。
-
缺点:
1)内联函数以复制为代价,活动产函数开销
2)如果函数的代码较长,使用内联将消耗过多内存
3)如果函数体内有循环,那么执行函数代码时间比调用开销大。
-
-
内存管理
- 内存溢出:程序在申请内存时,空闲内存不够用
- 内存泄漏:在申请内存后,光占用不释放内存
sizeof数组,得到整个数组所占空间的大小
sizeof指针,得到指针本身所占空间的大小
32位机器 | 64位机器 | |
---|---|---|
int | 4字节 | 4 |
Short | 2 | 2 |
Long | 4 | 8 |
Char | 1 | 1 |
-
- 区别:
- 1.时期:
静态库,在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。
动态库,动态库把对一些库函数的链接载入推迟到程序运行的时期 - 2.资源
静态库在每次使用时将全部连接进可执行程序,浪费资源。
动态库在使用时访问动态库中函数,节省资源。 - 3.更新升级
静态库更新,则每个使用该静态库的程序都需要更新,不易于更新升级
动态库仅更新自身,易于更新升级 - 4.包含其他库
静态链接库不能再包含其他动态链接库
动态链接库可以包含其他动态链接库
-
数据类型
在c++的实现中,每一种类型在不同系统上使用的存储位数(宽度)是不同的,其规则为:
● short至少16位
● int至少与short一样长
● long至少32位,且至少与int一样长
● long long至少64位,且至少与long一样长
![image-20200723091352072](/Users/yangfan/Library/Application Support/typora-user-images/image-20200723091352072.png)
![image-20200723091405788](/Users/yangfan/Library/Application Support/typora-user-images/image-20200723091405788.png)
2. 面对对象基础
面对对象三大特征----封装、继承、多态
- 封装
把事物封装称抽象的累,并且累可以把自己的数据和方法只让可信的累或对象操作,对不可信的进行信息隐藏 - 继承
父类->子类 - 多态
- 重载多态 编译期:函数重载, 运算符重载
- 子类行多态(运行期):虚函数
- 参数多态性(编译期 :类模版,函数模版
- 强制多态(编译/运行):基本类型转换,自定义类型转换
-
析构函数
-
构造函数
-
拷贝构造函数
-
多态:纯虚函数和虚函数
-
多态分为两类
-
静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
- 函数重载 :函数名必须相同,函数参数必须不同(包括顺序,个数,类型),作用域相同,与返回值类型无关。
-
动态多态: 派生类和虚函数实现运行时多态, 作用域不同
-
重写:子类重新定义,一切相同
-
隐藏:在重写中,会将同名的基类函数隐藏,即使参数列表不同,也不会重载。
-
“如果基类声明被重载了,则应在派生类中重新定义所有的基类(函数重载的)版本。
如果只重新定义一个版本,则另外两个版本将被隐藏,派生类对象将无法使用它们。”
-
-
-
- 静态类型: 对象在声明是采用的类型,在编译期确定;
- 动态类型:当前对象所指的类型,在运行期决定,对象的动态类型可以更改,但静态类型无法更改。
-
静态多态和动态多态区别:
-
静态多态的函数地址早绑定 - 编译阶段确定函数地址
-
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
-
-
总结:只有虚函数才使用的是动态绑定,其他的全部是静态绑定。
-
-
-
虚函数:降低函数优先级,当有继承类时 重写时先调用
-
“如果定义的类将被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚的。”
摘录来自: [美] Stephen Prata. “C++ Primer Plus(第6版)中文版。” Apple Books.
-
就是在基类中,把将要重写的函数声明为虚函数
注意:
普通函数(非类成员函数)不能是虚函数
静态函数(static)不能是虚函数
构造函数不能是虚函数(在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成后才会形成虚表指针)
内联函数不能是表现多态性时的虚函数
-
-
虚析构函数
虚析构函数是为了解决父类指针指向子类对象无法正确删除对象的问题, 用了之后就会先调用子类析构函数,再调用父类析构函数-
虚函数表
- 虚函数表是在什么时期建立的? 编译期
-
-
纯虚函数
virtual void func() = 0;
- 直接在父类中不实现,靠子类实现,这样的类叫抽象类 无法实例化对象!
-
访问限定符
- private 私有权限 类内🉑️ 类外🈲️ ,子类不可访问
- public 公共权限 类内外均🉑️
- protected 保护权限 类内🉑️ 类外🈲️ ,子类🉑️访问
-
继承 : 子类从父类中继承了公有保护私有数据和成员函数,但是私有数据不能直接访问
-
-
默认访问权限不同
- struct 默认权限为公共
- class 默认权限为私有
-
-
友元函数/友元类
-
//写在类里 //类做友元 friend class remote; //全局函数做友元 friend void func();
* 访问原始类中的私有和保护成员 * 在原始类里 定义自己的朋友
-
-
this指针
-
指向被调用的成员函数所属的对象
-
Public ,private protect
-
在类的内部,可以相互访问没有限制。
-
在类的外部,
- publiic可以通过对象、友元函数、子类函数访问,
- protect可以通过友元函数、子类函数访问,
- private只能通过友元函数访问。
-
-
3. C++11新特性/语法进阶
1. 智能指针
智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。
-
stackflow上的讨论,
-
auto_ptr (已被弃用 )
-
Shared_ptr
- 析构函数引用计数,允许多个指针指向同一对象
- 成员数据中有count,当count == 1时才释放内存,否则析构函数只会 count--
- C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。
- 当新增一个时引用计数加1,当过期时引用计数减1。只有引用计数为0时,智能指针才会自动释放引用的内存资源。
- 对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。
-
Unique_ptr
- 独占(不可复制)的智能指针,不与其他智能指针共享对象
- 可以利用move转移给其他unique_ptr
-
weak_ptr
-
弱引用不会引起引用计数增加
-
用于防止互相强引用所导致的内存泄漏
-
这弱引⽤它并不对对象的内存进⾏管理,在功能上类似于普通指针,然⽽⼀个⽐较⼤的区别是,弱引⽤能检测到所管理的对象是否已经被释放,从⽽避免访问⾮法内存。
-
智能指针,如果循环引用了应该怎么办。
:用weak_ptr,
两个强引用,被析构后 变为 1, 1
其中一个是弱引用, 析构后变为 1,0
随后 0被析构,引用计数1变成了0,也被析构
-
-
函数指针
2. 右值 和 右值引用
-
右值就是暂时生成的值;通常赋给左值之后所生成的临时对象就会消亡
-
右值引用 延续了临时对象的生命周期,将其保留了下来。
移动语义 move: 避免拷贝 ,总是将一个左值转换成右值。
- 函数返回时,利用右值引用 将构造的临时对象 延长使用权限返回,而原本的临时指针被设置为nullptr,析构的时候不会删除原来的数据。
- 如果是拷贝的话,拷贝一份返回,之前的临时对象会被析构。
3. lambda
[捕获列表] 定义外的数据
-> 返回值类型
4. 强制类型转换
“进行类型转换的原因,并让编译器能够检查程序的行为是否与设计者想法吻合。”
摘录来自: [美] Stephen Prata. “C++ Primer Plus(第6版)中文版。” Apple Books.
int a = 10;
double b = (double)a;//显式转换
double c = a;//隐式
-
static_cast(最基础)
-
支持上/下行转换,下行时不安全(将基类指针 转换为 派生类指针)
-
基本数据类型之间转换
-
static_cast<typeName> (value)
-
-
Dynamic_cast
-
用途: 使得能够在类层次结构中进行向上转换(由于is-a关系,这样的类型转换是安全的),而不允许其他转换。
-
将子类指针 转换为 可访问基类指针类型,否则将返回空指针
-
这个是运行时处理,会进行类型检查。其他三种是编译时完成的
-
支持上/下行转换,比static_cast安全。
-
基类中一定要有虚函数,否则不能通过编译(虚函数说明可能会有基类指针指向派⽣类对象的情况),因为运⾏时类型信息存储在虚函数表中
-
dynamic_cast<指针或引用类型> (expression)
-
-
const_cast
-
删除const/volatile标签,使对象可以修改
-
“提供该运算符的原因是,有时候可能需要这样一个值,它在大多数时候是常量,而有时又是可以修改的。在这种情况下,可以将这个值声明为const,并在需要修改它的时候,使用const_cast。”
-
const_cast<指针或引用> (expression)
-
-
Reinterpret_cast
-
“与通用转换机制相比,dynamic_cast、static_cast、const_cast和reinterpret_cast提供了更安全、更明确的类型转换。”
摘录来自: [美] Stephen Prata. “C++ Primer Plus(第6版)中文版。” Apple Books.
4. STL基础
-
迭代器
- iterator是为简化迭代器所需类型的定义而提供的基类。
- 算法通过迭代器iterator来访问容器
-
空间配置器allocator
- 动态空间配置,空间管理,空间释放
- 为了各种泛型容器如vector,map等分配内存,使程序员不比为内存而担心,只需添加数据即可
-
顺序容器
-
vector
-
动态增加大小,申请二倍空间
-
动态数组
-
插入 On 删除 On 查找 O1
-
-
list
-
deque
-
以分段空间组合而成,随时可以增加一段新空间连接起来(list和vector综合)
-
stack,queue
-
stk.push(); / q.. stk.pop(); / q.. stk.top(); / q.front(); stk.size(); / q.. + q.back();
-
-
priority_queue
-
二叉树 默认大根堆(底部为vector)
-
priority_queue<int> max_heap; priority_queue<int, vector<int>, greater<int>> min_heap;
-
插入 | On | O1 | O1 | Ologn |
---|---|---|---|---|
删除 | On | O1 | O1 | Ologn |
查找 | O1 | On | Ologn | |
vector | list | deque | Priority_queue |
- 关联容器
-
set
-
与map一样,底层实现是红黑树
-
所有元素按照元素的键值⾃动排序;不允许有两个相同的元素;元素不能改变值。(默认从⼩到⼤)
-
set<int> s; s.insert(3); s.insert(5); for(auto i = s.begin(); i != s.end(); i++){ cout << *i<<endl; } for(auto x:s) cout << x<<endl;
-
-
-
map
-
Key-value存储,根据key⾃动排序(默认从⼩到⼤),key不可改变,value可以,不允许两个相同的key.
-
map<int, string> m; m[1] = "one"; m[2] = "two"; for(auto i = m.begin();i != m.end();i++) cout << i->first<<" "<<i->second<<endl; for(auto x:m) cout <<i.first<<endl;
-
-
-
Multimap/set 允许key重复
- Unordered_map/set 通过哈希表实现
构造方法: 直接地址法
除留余数法
。。。。
冲突处理方法: 拉链法
开放寻址法
- Unordered_map/set 通过哈希表实现
各种问题
- 函数():n(k){} ===> n = k;
四大金刚
计算机网络
http
- 用于从 WWW(World Wide Web,万维网)服务器传输超文本到本地浏览器的传送协议。
- http报头格式
- 方法 URL 协议版本
- Xxx
- SSL 安全套接字协议
- 状态码
- 403 服务器已经理解请求,但是拒绝执行它
- 404 请求失败,请求所希望得到的资源未被在服务器上发现
- 502 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
- 503 由于临时的服务器维护或者过载,服务器当前无法处理请求。
HTTPS
-
为了数据传输的安全,HTTPS在HTTP的基 础上加入了SSL/TLS协议,SSL/TLS依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。
-
主要作用:
- 一种是建立一个信息安全通道,来保证数据传输的安全;
- 另一种就是确认网站的真实性。
-
HTTPS与HTTP区别,SSL握手是怎么交换证书的?
-
-
-
客户端请求网址
-
服务端返回公钥
-
客户端验证公钥
-
浏览器和系统内置了默认信任的证书
-
签发机构如何验证你是否实际控制这个域名的?
-
你往网站根目录放置一个机构提供的特定的文件,然后机构会定时抓取这个文件,如果能抓取到说明你确实有这个网站的管理权限。注意,只支持 80 和 443 端口,8080 登端口不认。
-
机构往域名信息里的管理员邮箱发一封验证邮件,邮件里有验证链接,域名管理员要点开链接输入验证码确认。
-
要求你在域名 DNS 控制面板里添加一条特定的域名记录。
-
-
-
客户端使用公匙对对称密匙加密,发送给服务端。 一个随机数,可以为以后的信息加密解密
-
服务器用私钥解密,拿到对称加密的密匙。(服务端用私钥解密后,得到了客户端传过来的随机值,然后把内容通过该随机值进行对称加密
-
传输加密后的信息,这部分信息就是服务端用私钥加密后的信息,可以在客户端用随机值解密还原。
-
客户端解密信息,客户端用之前生产的私钥解密服务端传过来的信息,于是获取了解密后的内容。整个过程第三方即使监听到了数据,也束手无策。
-
-
-
客户端和服务端之间的加密机制:
-
TLS协议是基于TCP协议之上的,图中第一个蓝色往返是TCP的握手过程,之后两次橙色的往返,我们可以叫做TLS的握手。
-
client1:TLS版本号+所支持加密套件列表+希望使用的TLS选项
-
Server1:选择一个客户端的加密套件+自己的公钥+自己的证书+希望使用的TLS选项+(要求客户端证书);
-
Client2:(自己的证书)+使用服务器公钥和协商的加密套件加密一个对称秘钥(自己生成的一个随机值);
-
Server2:使用私钥解密出对称秘钥(随机值)后,发送加密的Finish消息,表明完成握手
-
TCP协议在哪一层?IP协议在那一层?HTTP在哪一层?
传输层, 网络层, 应用层
http是怎样保持登录状态的。
http协议是无状态的,要通过会话跟踪技术记住用户状态
通过cookie,session,URL重写(如果服务器使用URL重写,它将会话信息附加到URL上。)
cookie:保存到客户端的一个文本文件,与特定客户相关
- 服务器发给客户的片段信息,存在客户端内存或硬盘上
- 客户随后会在对服务器的访问中发回cookie
- 保存到硬盘上的cookie可以由多个浏览器之间共享。两个浏览器访问同一个url时,使用同一个信息
- 缺点:信息透明,不安全
session
- 基于session的httpSession对象
- 服务器在内存中创建一个session对象,将ID发给客户端,保存到cookie中
- 第二次请求时 客户端发送ID给服务器
cookie和session的区别
Cookie Session 客户端 服务端 存储地址 可在多浏览器中共享 保存session🆔的cookie在关闭浏览器后就删除了。
服务器为每一个浏览器创建不同的session对象,所以不能再多浏览器中共享共享 只能写文本, 有长度限制 无限制 缺点:信息透明,不安全 浏览器关闭后,cookie删除,服务端的seesion会在一段时间后才失效
https和http的区别,
http用明文传输,不安全;
https在http基础上增加了数据加密🔐
Post和Get区别
-
-
1.GET请求的数据会附在URL之后,以?分割URL和传输数据,参数之间以&相连,POST把提交的数据则放置在是HTTP包的包体中。
-
2.GET的长度受限于url的长度,而url的长度限制是特定的浏览器和服务器设置的,理论上GET的长度可以无限长。
-
3.POST是没有大小限制的,HTTP协议规范也没有进行大小限制,起限制作用的是服务器的处理程序的处理能力
-
4.在ASP中,服务端获取GET请求参数用Request.QueryString,获取POST请求参数用Request.Form。
-
5.POST的安全性要比GET的安全性高
私密请求用post,查询信息和想通过url分享的使用get
-
-
TCP/IP
TCP如何实现可靠性
-
TCP可靠传输实现
- 确认和超时重传
- 数据合理分片和排序:数据被合理分割,对数据排序
- 拥塞控制:防止过多的数据注入到网络中
- 流量控制:让发送方发送速率不要太快,让接收方来得及接受
- 流量控制协议是可变大小的滑动窗口协议。
- 数据校验:
- TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。
- 如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段。
-
TCP和UDP区别,使用场景
-
TCP是面向连接的,UDP无连接
-
TCP UDP 面向连接 无连接 保证可靠交付 不保证 全双工的可靠信道 不可靠信道 点对点 一对一,多对多 首部开销多 开销少 无拥塞控制 速度慢 速度快 -
应用场景 TCP UDP 要求可靠的:比如HTTP,FTP 要求快速的:QQ语音,
-
-
TCP三次握⼿四次挥⼿
-
我的理解
- 三次握手
- 客户发送请求建立连接 SYN(同步比特
- 服务端收到返回确认建立SYN+ACK(确认比特
- 客户收到后回复已收到ACK
- 为什么要最后一步确认?
- 防止失效的请求传送到服务端后,服务器傻傻等待
- 四次挥手
- 一二次
- 客户端发送FIN(终止比特)请求断开;服务端同意返回ACK。 断开客户到服务端单向连接,但是服务端可以继续往客户端发送数据
- 三四次
- 服务端发送数据完毕后,发送FIN+ACK,请求释放;客户端收到后返回ACK确认
-
为什么挥手四次 握手三次
答:挥手时数据没传输完,握手时无数据传输
为什么客户端等待2MSL
1.保证服务端能收到确认报文,如果没收到服务端会重传FIN+ACK,这个时候需要客户端回应
2.可使本连接持续的时间内所产生的所有报文段都从网络中消失,这样可使下次连接中不会出现旧的连接报文段。
-
-
请求⽹址的过程
-
DNS解析:将域名地址解析成IP地址
- 查询本地内存,查不到就请求域名服务器
-
TCP三次握手建立连接
-
发送请求:向web服务器发送http协议的通信内容,一般是get或post
- POST: 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改
- GET:请求指定的页面信息,并返回实体主体
-
接受响应:web服务器收到请求,进行处理
- 从它的文档空间中搜索子目录,如果找到该文件,web服务器把该文件内容传送给相应的浏览器。
-
渲染页面
-
断开连接
为什么我们需要端口,比如http80https443我们为什么需要这个端口。
因为端口是具有网络功能的软件的标识号,是程序与外界交换信息的窗口
相当于家里的邮筒
在OSI第四层传输层封装的数据段里。
- 计算机A访问B时,A要返回数据,就会创建一个大于1023的端口,并通知B。
- 随后A持续侦听该端口
- B收到数据后,读取源端口和目的端口,在要返回A的数据中反转两个端口号。
- TCP/UDP常用端口号
- 80 /tcp WWW (HTTP超文本传送协议)
- 443 /tcp HTTPS 经过加密的HTTP(used for securely transferring web pages)
-
OSI七层模型
因特网五层模型
- 应用层:报文(message),为应用程序提供网络服务
- 传输层:报文段, 应用程序端到端 ,TCP/UDP +TCP/UDP报头
- 网络层:数据报,主机到主机 +IP数据报头
- 数据链路层:将信息从一个节点传输到下一个节点
- 物理层 :电气连接+物理层面协议
操作系统
1. 线程/进程
- 进程隔离 通过进程控制块
- 进程是资源分配的最小单位, 线程是CPU调度的最小单位。
- 线程 = 进程 - 共享资源;
- 进程:代码段,数据,打开文件,有多个线程
- 线程:寄存器,堆、栈
- 进程 = 程序 + 执行状态
进程通信方式,线程通信方式?
线程可以共享进程的哪一部分内存?
同步问题
并发编程中 程序运行中由于进程切换会导致 运行出错。这就是要同步互斥
有资源共享就会出现:
互斥:一个进程占用,其他进程不能使用
死锁:多个进程占用部分资源,形成循环等待
饥饿:一些进程轮流占用资源,一个进程一直得不到资源
在操作系统里提供一组同步互斥机制,在保证资源使用率的前提下,避免同步出现的问题(线程安全)
2. 信号量(同步互斥机制)
信号量是操作系统提供的一种协调共享资源的方法。为了解决并发编程(底层:禁用中断,原子操作 )的问题
- 软件同步是平等线程之间的协商机制
- 信号量是OS管理的,地位高于进程
- 用信号量表示系统资源的数量
- 由信号量sem和两个原子操作p--(增加占用,占用资源), v++(减少占用,释放资源)
- Sem < 0 代表有等待
3. 管程(同步互斥机制)
-
一种用于多线程互斥访问共享资源的程序结构
-
任一时刻 最多只有一个线程执行管程代码
-
正在管程中的线程可以临时恢复放弃管程的互斥访问,等待时间出现时恢复
-
管程的使用
- 在对象/模块中,收集相关共享数据
- 定义访问数据的方法
4. 锁(同步互斥机制)
一个抽象的数据结构
实现: 禁用中断,软件方法,原子操作指令
- 二进制变量
- Acquire:锁被释放前一直等待,然后得到锁
- Release:释放锁,唤醒任何等待的进程
原子操作指令(由其他指令合成):在指令执行时,保证不存在部分执行状态
内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择:
-
一个是原地等待 (自旋锁)
- 中断上下文要用锁,不允许休眠 首选自旋锁
- 中断:内核不代表任何进程运行,也不访问当前进程的数据结构
- 开销:等待时间
- 保护共享资源需要在中断或者软终端情况下使用,最好选自旋锁,不会引起睡眠。
- 中断上下文要用锁,不允许休眠 首选自旋锁
-
一个是挂起当前进程,调度其他进程执行(睡眠)(互斥)
-
进程上下文的访问
-
进程上下文:内核代表某个进程运行,要访问进程的数据结构
-
开销 :进程上下文的切换时间
-
-
**自旋锁 **
调用者申请的如果被占⽤(⾃旋锁被别的单位保持),则调⽤者⼀直循环看是否资源被释放(互斥锁会引起调⽤者休眠)。效率较⾼,但可能会使CPU效率低,可能会造成死锁。
互斥锁
常用的三种同步实现方法(锁的实现方法)
- 禁用中断 仅适用于单处理器
- 软件方法(复杂)
- 原子操作指令
5. 死锁
- 出现死锁的必要条件 (同时出现)
- 互斥 任何时刻只能有一个进程使用一个资源实例
- 持有并等待 进程保持至少一个资源,并正在等待获取其他进程持有的资源
- 非抢占 不能抢别人的,资源只能在进程使用后资源释放
- 循环等待 存在等待进程集合,我等你,你等我
- 死锁处理方法
- 死锁预防 : 确保系统永远不会进入死锁状态
- 限制并发进程对资源的请求 (解决办法)
- 互斥 :把共享资源封装成可同时访问
- 持有并等待 :
- 1.进程请求资源时,要求它不持有任何其他资源
- 2.仅允许进程在开始执行时,一次请求所有需要的资源
- 资源利用效率低
- 非抢占
- 如进程请求不能立即分配资源,则释放已占有资源
- 只在能够同时获得所有需要资源时,才执行分配操作。
- 循环等待 对资源排序,要求进程按照顺序请求资源
- 资源利用效率低
- 限制并发进程对资源的请求 (解决办法)
- 死锁避免 : 使用之前判断,只允许不出现死锁的进程请求资源
- 利用先验信息,在分配资源时判断是否出现死锁,不出现才分配
- 要求进程声明需要资源的最大数目
- 限定提供和分配资源数目,确保满足进程的最大需求
- 动态检查资源分配状态,确保不会出现环形等待
- 系统资源分配的安全状态 : 针对所有已占有进程,存在安全序列
- 银行家算法 一种死锁避免的方法
- 以银行借贷分配策略为基础,判断并保证系统处于安全状态
- 线程在第一次申请资源时,说明所需最大资源,完成后及时释放
- 在所申请资源不超过系统拥有资源最大值时,系统尽量满足线程需要
- 利用先验信息,在分配资源时判断是否出现死锁,不出现才分配
- 死锁检测和恢复 : 检测系统进入死锁后,进行恢复
- 允许系统进入死锁状态
- 维护系统的资源分配图
- 定期调用死锁检测算法来搜索图中是否存在死锁
- 该算法与银行家算法最大区别 无最大资源请求量的判断
- 其他方面很相似
- 出现死锁时,调用死锁恢复机制恢复
- 死锁恢复:进程终止(如何选择)
- 终止所有的死锁进程
- 一次只终止一个进程直到死锁消除
- 终止进程的顺序应该是
- 进程优先级 最低的
- 进程已运行时间
- 进程已占用资源
- 进程完成需要的资源
- 终止进程数目 越小越好
- 进程是交互还是批处理 保护“交互”!
- 死锁恢复:资源抢占(如何来终止)
- 选择被抢占进程 最小成本目标
- 进程会退 返回到一些安全状态。重启进程到安全状态
- 可能出现饥饿 同一进程可能一直被选作被抢占者
- 死锁预防 : 确保系统永远不会进入死锁状态
6. 进程通信
- 是进程进行通讯和同步的机制
- 流程:在通信进程间建立通信链路, 通过send/receive交换消息
- 进程链路特征
- 物理 如 共享内存
- 逻辑 如 逻辑属性
信号
- 进程间的软件中断通知和处理机制 类似于 ctrl + c
- 信号的接收处理
- 捕获: 执行进程指定的信号处理函数
- 忽略: 执行系统默认处理函数
- 屏蔽: 禁止进程接受和处理信号
- 不足:传送信息量小,只有一个信号类型
管道:
- 进程间基于内存文件的通信机制
- 子进程从父进程继承文件描述符
- 默认文件描述符:0 stdin 1 stdout 2 stderr
- 管道不关心另一端,间接通讯
- 读管道: scanf() 基于它实现
- 写管道: printf() 基于它实现
消息队列:有操作系统维护的以字节序列为基本单位的间接通讯机制
共享内存:把同一段物理内存区域 同时映射到多个进程的内存地址空间的通信机制
- 线程:同一进程的线程总是共享相同的内存地址空间(天然存在
- 进程:每个进程都有私有内存地址空间 | 每个进程的内存地址空间需要明确设置共享内存段
- 优点:快速方便
- 最快的方法
- 一个进程写 另一个进程里即可见
- 没有系统调用干预
- 没有数据复制
- 不提供同步
- 不足:需要额外的同步机制来协调数据访问
- 需要信号量等机制协调共享内存的访问冲突
7. IO设备
同步和异步IO
-
阻塞IO:
- 读数据: 数据从发出到回来,进程一直要处于等待状态
- 写:发出写请求,进程一直等待,直到设备完成数据写入处理
-
非阻塞IO:不等待
- 立即从read或write系统返回,返回值是成功传输字节数
- 传输字节数可能为 零
-
异步IO:tell me later,待会儿跟我说
- 读数据:用指针标记好用户缓存区,立即返回;等内核填充完毕缓冲区后通知用户。
- 写数据:用指针标记好用户缓存区,立即返回;待内核处理好数据后通知用户
- 在过程中设备驱动需要等待,而应用程序 可以干别的
数据结构与算法
树
- 满二叉树: 满二叉树 深度为k 节点数为2^k -1;最后一层结点数为 2^(k-1).
- 完全二叉树
- 大根堆:根大于左右
- 小根堆:根小于左右
- 二叉搜索树:左 <根 <右
- 平衡二叉树(AVL):|左右子树树高之差| < 1 + 二叉搜索树的性质
完全二叉树
一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。
- 叶节点只能在底层或者次底层存在
堆
一颗完全二叉树
建堆 | 插入 | 删除/弹出根节点 | 堆顶 |
---|---|---|---|
On | logn | logn | O1 |
佛洛依德算法 | 插在最后,随后与父节点比较,新节点上浮 | 用最后的节点替换根节点,随后更新 | 返回根节点 |
假设堆所对应的完全二叉树高度为h,节点个数为n,假设为满二叉树 n = 2^h - 1
从上到下 每层有 2^0, 2^1, 2^(h-2), 2^(h-1) 个点,加起来是 2^h - 1个节点
有2(h-2)个结点向下访问一次,2(h-3)个结点向下访问2次,...1个结点向下访问h-1次
推导公式如下:
![image-20200801113337043](/Users/yangfan/Code/计算机相关基础/C++ 面试准备.assets/image-20200801113337043.png)
在堆所对应的二叉树为非满二叉树时,复杂度是n同阶的。
二叉搜索树BST
-
左 <根 <右,h = O(n), 查找最坏复杂度O(n)
-
查找 插入 删除 按父节点大于左侧节点,小于右侧节点,二分查找 不允许相同元素,先查找,元素不存在就返回_hot(父节点)的空节点,创建新节点连接上去。
更新全🌲规模,更新插入节点和祖先的高度查找,删除,更新
1.左右子树不同时存在时,将左子树或者右子树替换当前位置
2.左右子树同时存在时。现寻找删除节点中序遍历中的后继节点,即比它大的最小值(右节点的最左节点),交换数据。此时将右儿子替换自身位置O(n) O(h) O(h) -
随机生成平均高度为 根号n,logn 前者更可信
-
![image-20200725150954018](/Users/yangfan/Library/Application Support/typora-user-images/image-20200725150954018.png)
平衡二叉搜索树BBST
渐进意义下logn高度
AVL树
-
|左右子树树高之差| <= 1
-
h = O(n), 查找最坏复杂度O(logn)
-
一定是一颗二叉搜索树
-
高度为h的avl树,至少包含s(h) = fib(h + 3) - 1个节点
-
s(h) = 1 + s(h-1) + s(h-2)
-
插入 可能引起历代祖先失衡, 删除 可能只会影响最近祖先的失衡
-
平衡因子 左子树高度 - 右子树高度
-
查找 插入 删除 O1次调整
插入可以调整回原来的高度Ologn
删除后原来高度可能不变,可能-1AVL树中修正插入节点引发的失衡不会出现失衡传播 同时最多失衡一个节点。
因为有失衡传播现象,可能需要做Ologn次调整O(logn)因为要比较啊 O(lögn) -
单旋:
- zag逆时针旋转
- zag(x) 让x成为 右边子节点的 左子树
- zig顺时针旋转
- zag(x) 让x成为左边子节点的右子树
- zag逆时针旋转
红黑树
简介:在平衡二叉树上放松条件,任意借点左右子树的高度,相差不超过两倍
-
红黑树特点(这样的规则构造可以证明平衡)
- 根结点是黑色
- 父子节点不能同为红色
- 每条到空节点的路径上的黑色节点数相同
-
avl 红黑树 查找效率高,因为是完全平衡的(左右子树高度差不大于1 插入,删除效率高,更少的旋转,因为是不严格平衡 每个节点会记录左右高度差,要用int来记录 开销大 用颜色构造所谓的平衡性,一个比特就可以 数据库,查找次数多 库函数set,map
红黑树、B 树、B+ 树的区别?
- 红黑树的深度比较大,而 B 树和 B+ 树的深度则相对要小一些
- B+ 树则将数据都保存在叶子节点,同时通过链表的形式将他们连接在一起。
B 树 B+树
特点
- 一般化的二叉搜索树(binary search tree)
- “矮胖”,内部(非叶子)节点可以拥有可变数量的子节点(数量范围预先定义好)
应用
- 大部分文件系统、数据库系统都采用B树、B+树作为索引结构
区别
- B+树中只有叶子节点会带有指向记录的指针(ROWID),而B树则所有节点都带有,在内部节点出现的索引项不会再出现在叶子节点中。
- B+树中所有叶子节点都是通过指针连接在一起,而B树不会。
队列
顺序队列
循环队列
- 用固定长度数组,来构建队列
- head和tail
hash表
/除留余数法/ 构造方法
x 属于 -10^9 到 10^9 映射到 0 ~10^5
- 先 x mod 10^5,映射到0 ~10^5 -1
- 可能出现冲突(余数相同)
- 存储结构(处理冲突方式):
- 开放寻址法
- 开一个两倍大的数组
- 添加:从前到后一次看有没有位置,有的话就填充
- 查找:从前往后找
- 拉链法
- 开一个size = 10^5的数组
- 在每一个余数下面拉一个链表
- 插入,查找。删除 :开一个布尔数组,标记
- 开放寻址法
- 字符串哈希
排序算法
冒泡排序
-
最好情况 当有序时 可以为O(n)
-
void bobSort(vector<int>& a){ bool didSwap; int len = a.size(); for(int i = 0;i < len - 1; i++){ didSwap = false; for(int j = 0; j < len - i - 1 ; j++){ if(a[j] > a[j + 1] ) { swap(a[j], a[j+1]); didSwap = true; } } if(!didSwap) return; } }
-
如果某一次比较中,没有交换证明 已经排序完毕
快速排序
-
退化问题 如果每次选的轴点恰好是最大最小值
-
while(i < j){ do i++; while(nums[i] < x); do j--; while(nums[j] > x); if(i < j) swap(nums[i], nums[j]); } //如果x是最大值,则这次递归中 只会将最大值与末尾位置互换,只交换一次 //有一方为空
-
最好情况:每次划分都接近平均,轴点数值接近中位数
- T(n) = 2 * T( (n-1) / 2) + O(n) = O(logn)
-
最坏情况:每次划分极不均衡,比如轴点总是最大最小元素
- T(n) = T(n-1) + T(0) + O(n) = O(n^2) 与起泡排序相当
-
- 因为堆排序 弹出顶端后,会将最后的元素放进堆顶,会进行大量无效的比较
- 快速排序每次数据移动都意味着,该数据距离它正确的位置越来越近。
- 而在堆排序中,类似将堆尾部的数据移到堆顶这样的操作只会使相应的数据远离它正确的位置,后续必然有一些操作再将其移动,即“做了好多无用功”。
- 像标准库中的sort,是通过 先快排,递归深度超过一个阀值就改成堆排,然后对最后的几个进行插入排序来实现的
选择排序
用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
算法步骤
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复第二步,直到所有元素均排序完毕。
插入排序
假定前面的数排好序了,将下一个数插入
设计模式
常用设计模式:
- 单例模式
- 观察者模式
- 装饰者模式
- 适配器模式
- 工厂模式
- 代理模式(proxy)
https://blog.csdn.net/qq_33326449/article/details/78946364
https://zhuanlan.zhihu.com/p/129055059
https://www.bilibili.com/video/BV1c4411a7wk?from=search&seid=10715626980271172049
解决复杂性
- 分解 分而治之
- 抽象 选择忽视他的非本质
原则:
- 开闭原则 :对拓展开放,对修改封闭
- 里氏代换原则:对实现抽象化的具体步骤的规范。
- 依赖倒转原则:针对接口编程,高层模块不应该依赖底层模块,二者都应该依赖抽象而不依赖于具体。
- 接口隔离原则:使用多个隔离的接口,降低耦合度。
- 单一职责原则:类的职责要单一,不能将太多的职责放在一个类中。
- 最少知道原则:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
- 合成复用原则:尽量多使用
组合/聚合
的方式,尽量少使用甚至不使用继承关系。
基本不考 了解最重要的就行/
-
单例模式
- 保证一个类仅有一个实例,并提供一个访问它的全局访问点
- 单例模式的优点:
- 单例类只有一个实例(static修饰)、共享资源,全局使用节省创建时间,提高性能。
- 对唯一实例的受控访问,因为单例类封装它的唯一实例,所以它 可以严格控制客户怎样以及何时访问它。
- 缩小命名空间,单例模式是对全局变量的一种改进,它避免了那些存储唯一实例的全局变量污染名空间。
- 允许对操作和表示的精化,单例类可以有子类,而且用这个扩展类的实例来配置一个应用是很容易的。
- 实现:
- 懒汉:在第一次用到类实例的时候,才会去实例化类。
- 饿汉:在单例类定义的时候就去实例化。
-
线程安全的单例模式
-
下面两个都安全
-
饿汉
-
#ifndef SINGLETON_H #define SINGLETON_H class Singleton{ protected: Singleton(); ~Singleton(); static Singleton m_instance; //静态成员变量 只能用静态成员函数访问 //多对象共有 存在全局区 public: static Singleton* getInstance(); }; #endif // SINGLETON_H //源文件: #include "singleton.h" Singleton::Singleton() {} Singleton::~Singleton() {} Singleton *Singleton::getInstance() return &m_instance; //对于饿汉来说是线程安全的。
-
懒汉
-
QMutex类提供了一种保护一个变量或者一段代码的方法,这样可以每次只让一个线程访问它。
这个类提供了一个lock()函数用于锁住互斥量,如果互斥量是解锁状态,那么当前线程立即占用并锁定它;否则,当前线程会阻塞,直到这个互斥量的线程对它解锁为止。 -
//头文件: #ifndef SINGLETON_H //如果没定义xxx 就 定义xxx //否则执行endif后面的 #define SINGLETON_H #include <QMutex> class Singleton { protected: Singleton(); ~Singleton(); static Singleton * m_pInstance; QMutex m_mutex; public: static Singleton* getInstance(); }; #endif // SINGLETON_H //源文件: #include "singleton.h" Singleton::Singleton() {} Singleton::~Singleton() {} Singleton *Singleton::getInstance() { m_mutex.lock(); static Singleton instance; m_mutex.unlock(); return &instance; }
-
C++后端路线
c c++纯语言(这两门儿我公众号上有视频和书找我要)。
vc,gcc api了解(微软的更复杂些)即可,不可能学完。
qt。这些够你找工作了。
数据库
基本概念
- 数据
SQL语句
Redis
即远程字典服务(Remote Dictionary Server ),是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
数据类型:string , hash, list ,set , zset(sorted set)
类型 | 简介 | 特性 | 场景 |
---|---|---|---|
String(字符串) | 二进制安全 | 可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M | --- |
Hash(字典) | 键值对集合,即编程语言中的Map类型 | 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去) | 存储、读取、修改用户属性 |
List(列表) | 链表(双向链表) | 增删快,提供了操作某一段元素的API | 1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列 |
Set(集合) | 哈希表实现,元素不重复 | 1、添加、删除,查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作 | 1、共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐 |
Sorted Set(有序集合) | 将Set中的元素增加一个权重参数score,元素按score有序排列 | 数据插入集合时,已经进行天然排序 | 1、排行榜 2、带权重的消息队列 |
应用场景:会话,消息队列,订阅
####### 比memcached
-
支持的数据结构多
-
速度快
-
支持数据的持久化
- Memcached基于完全基于内存没有持久化操作,所有受到内存的限制
- Redis可保存到磁盘中,运行时加载在内存
#定时机制怎么实现的
redis是一个单线程的事件模型。分为文件事件和时间事件,其定时机制通过时间事件实现。
# rehash
因为redis是单线程,当键值对数量很多时,如果一次性将键值对全部rehash,庞大的计算量会影响服务器性能,甚至可能会导致服务器在一段时间内停止服务。不可能一步完成整个rehash操作,所以redis是分多次、渐进式的rehash。
渐进性哈希分为两种:
1)操作redis时,额外做一步rehash
对redis做读取、插入、删除等操作时,会把位于table[dict->rehashidx]位置的链表移 动到新的dictht中,然后把rehashidx做加一操作,移动到后面一个槽位。
2)后台定时任务调用rehash
后台定时任务rehash调用链,同时可以通过server.hz控制rehash调用频率
#为什么单线程的Redis这么高效呢?
- 单线程避免了线程安全相关问题和导致的代码逻辑结构数据结构的复杂度,线程间切换。
- 使用了 epoll 异步事件处理调度机制。 //??//
- 内存数据读写本身快。
GIT
常用指令
git clone + url 克隆仓库到本地
git remote -v
命令可以查看本地仓库所关联的远程仓库信息
LINUX
常用命令
ls -al 看目录下所有文件
shell脚本
补码
底层:补码 负数变补码 绝对值二进制表示取反再加1
ex:-1 1的二进制 00000..01
反码 取反 11111..10
-1补码 加一 11111...11
减法都是通过 把负数变成补码再相加
linux read/write和fread/fwrite有什么区别
用户地址空间和内核地址空间
为了操作系统的稳定性和可用性
用户空间:用户进程在此执行,
内核空间: 防止危险操作,给用户提供API调用
内核态:进程运行在内核空间时就处于内核态
用户态:进程运行在用户空间时则处于用户态
用户态的进程必须切换成内核态才能使用系统的资源,那么我们接下来就看看进程一共有多少种方式可以从用户态进入到内核态。概括的说,有三种方式:系统调用、软中断和硬件中断。
我们可以将每个处理器在任何指定时间点上的活动概括为下列三者之一:
- 运行于用户空间,执行用户进程。
- 运行于内核空间,处于进程上下文,代表某个特定的进程执行。
- 运行于内核空间,处于中断上下文,与任何进程无关,处理某个特定的中断。
用户调用和系统调用
用户调用 | 系统调用 |
---|---|
调用函数库的一段程序 | 调用系统内核服务 |
与用户程序相联系 | 操作系统的一个入口 |
在用户地址空间执行 | 在内核地址空间执行 |
过程调用,开销小 | 在用户空间和内核上下文切换,开销大 |
io多路复用
Select | Poll | Epoll |
---|---|---|
On | On | O1 |
线性扫描,效率低 | 链表替换某些select数据结构,无连接数限制 |
epoll的两种工作方式:1.水平触发(LT)2.边缘触发(ET)
- LT模式:若就绪的事件一次没有处理完要做的事件,就会一直去处理。即就会将没有处理完的事件继续放回到就绪队列之中(即那个内核中的链表),一直进行处理。
- ET模式:就绪的事件只能处理一次,若没有处理完会在下次的其它事件就绪时再进行处理。而若以后再也没有就绪的事件,那么剩余的那部分数据也会随之而丢失。
由此可见:ET模式的效率比LT模式的效率要高很多。 - 只是如果使用ET模式,就要保证每次进行数据处理时,要将其处理完,不能造成数据丢失,这样对编写代码的人要求就比较高。
注意:ET模式只支持非阻塞的读写:为了保证数据的完整性。
场景题
-
什么是秒杀
比如说抢商品啥的
-
秒杀架构设计
限制流量:允许少部分流量进入服务后端。
削峰:实现削峰的常用的方法有利用缓存和消息中间件等技术。把瞬间的高流量变成一段时间平稳的流量
-
异步处理:采用异步处理模式可以极大地提高系统并发量???
-
内存缓存:把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升
-
可拓展:将系统设计成弹性可拓展的,如果流量来了,拓展机器就好了