2018年秋招总结(面经&知识点归纳)
前言
我是2018年八月下旬决定放弃保研找工作的,从八月二十三号开始复习到九月下旬结束秋招,最后拿到了百度(手百feed),华为,腾讯,Intel,OPPO,360,农行总行,陌陌等offer。复习的时候总结了一些知识点,主要是C/C++语言基础,数据结构与算法,网络与协议,数据库,Linux及操作系统,设计模式,分布式系统这几个方面。
C/C++语言基础
C++相对于C语言的优点:
- 面向对象,把数据和操作绑定在一起,函数调用的时候看起来比较清晰
- 运算符重载
- 内存管理相比C好一些,比如可以用std:string
- 库相比C多一些(比如stl)
- 可以写工具类在多个项目中使用(比如计算程序运行时间的类,比如读写某个位置的文件)
面向对象的三大特征:
- 封装
- 多态
- 静态多态: 编译期间就可以确定调用那个函数,比如函数重载
- 动态多态:在运行时确定调用那个函数,比如虚函数
- 继承
extern关键字作用:
- extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义
- extern C 表示按C语言的规则编译
static关键字作用:
static修饰局部变量
- 静态局部变量存储在静态区
- 生存期为整个程序生命周期,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后,尽管该变量还继续存在,但不能使用它。
- 静态局部变量若在声明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。
static修饰全局变量
- 非静态全局变量的作用域是整个源程序,也即在各个源文件中都是有效的。
- 而静态全局变量则只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它
static 函数
- static的含义是指对函数的作用域仅局限于本文件
- 使用静态函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系
- 函数中使用static修饰变量:变量存储在全局区,函数退出时变量仍然存在,但是在函数外不能访问。
类中的static关键字
static 数据成员:
- 静态数据成员
- 无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问
- 在没有产生类的实例时,我们就可以操作它
- 静态数据成员存储在全局数据区
- 静态数据成员定义时才分配空间,不能在类声明中定义(在全局区定义)
- 同全局变量相比,使用静态数据成员有两个优势:
- 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性
- 可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能
static 成员函数:
- 静态成员函数
- 无法访问属于类对象的非静态数据成员和非静态成员函数
const的作用:
- const修饰变量:变量的值不能改变
- const修饰指针:
- 如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量
- 如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量
- 函数中使用const
- const修饰函数参数:表示参数不可变,若参数为引用,可以增加效率,const引用传递和函数按值传递的效果是一样的,但按值传递会先建立一个类对象的副本, 然后传递过去,而它直接传递地址,所以这种传递比按值传递更有效
- const 修饰函数返回值:含义和const修饰变量和指针的含义相同
- 类中使用const
- const修饰成员变量:表示成员变量不能被修改,同时只能在初始化列表中赋值
- const修饰成员函数:该函数不能改变对象的成员变量;不能调用非const成员函数,因为任何非const成员函数会有修改成员变量的企图;const的成员函数才能被一个const类对象调用;const关键字不能与static关键字同时使用,因为static关键字修饰静态成员函数,静态成员函数不含有this指针,即不能实例化,const成员函数必须具体到某一实例。
- const修饰类对象: 对象的任何成员都不能被修改;只能调用const成员函数
volatile:
- 访问寄存器要比访问内存要块,因此CPU会优先访问该数据在寄存器中的存储结果,但是内存中的数据可能已经发生了改变,而寄存器中还保留着原来的结果。为了避免这种情况的发生将该变量声明为volatile,告诉CPU每次都从内存去读取数据。
- 防止编译器对变量的优化
- 一个参数可以即是const又是volatile的吗? 可以
new与malloc区别:
- new分配内存按照数据类型进行分配,malloc分配内存按照大小分配
- new不仅分配一段内存,而且会调用构造函数,但是malloc则不会。
- new的实现原理:先通过malloc申请内存空间,然后再调用构造函数
- new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化;
- new是一个操作符可以重载,malloc是一个库函数;
- new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会;
- malloc分配的内存不够的时候,可以用realloc扩容。new没用这样操作;realloc扩容的原理:如果当前连续内存块足够扩容的话就直接扩容,如果当前内存块不够长就再找一个足够长的地方,分配一块新的内存,将原来的内容复制过来,将原来的内存空间释放。
- new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。
- new和new[]的区别,new[]一次分配所有内存,多次调用构造函数,分别搭配使用delete和delete[],同理,delete[]多次调用析构函数,销毁数组中的每个对象。
- 隐藏
- 隐藏指的是子类隐藏了父类的函数
- 如果是虚函数则叫覆盖
C++多态性与虚函数表:
- 一个基类指针指向派生类对象,在调用对象虚函数时,就会去查找该对象的虚函数表,虚函数表的地址存放在每个对象的头部位置,在虚函数表中找到对应的虚函数指针,然后进行调用
- 没有虚函数的类里面不会有虚函数表,对于有虚函数表的类,一个类对应一个虚函数表,这个类的所有对象共用这个虚函数表,虚函数表的地址保存在每个对象的头部位置
- 派生类的虚函数表从基类继承过来,如果覆盖了其中的某个虚函数,则虚函数表里面该函数对应的指针被替换
- 虚函数不能是静态成员函数??,其访问权限可以是protected或public
- 虚函数的作用:实现多态
- 动态绑定是如何实现
-
纯虚函数
- 定义的例子:virtual int fun()=0;
- 在基类中抽象出一个方法,且该基类只做能被继承,而不能被实例化
- 这个方法必须在派生类中被实现
友元函数:
- 类中通过使用关键字friend 来修饰友元函数,但该函数并不是类的成员函数,其声明可以放在类的私有部分,也可放在共有部分。友元函数的定义在类体外实现,不需要加类限定。
- 一个类中的成员函数可以是另外一个类的友元函数,而且一个函数可以是多个类友元函数。
- 友元函数可以访问类中的私有成员和其他数据,但是访问不可直接使用数据成员,需要通过对对象进行引用。
- 友元函数在调用上同一般函数一样,不必通过对对象进行引用。
- 友元类:友元类的所有成员函数都是另一个类的友元函数
为什么对于存在虚函数的类中析构函数要定义成虚函数?
因为将派生类对象绑定到基类指针上,销毁对象时,如果析构函数没有定义为虚函数,则会调用基类的析构函数,只能销毁基类部分的数据,若要调用派生类的析构函数,则应该将析构函数定义为虚函数,销毁时通过虚函数表找到对应的析构函数
析构函数能抛出异常吗?
不能,如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。
构造函数和析构函数中调用虚函数吗?
不能,构造函数或者析构函数中调用虚函数并不会发挥虚函数动态绑定的特性,即使在构造函数或者析构函数中成功调用了虚函数,程序的运行结果也是不可控的(在基类中调用了派生类的函数)
指针和引用的区别:
- 指针保存的是所指对象的地址,引用是所指对象的别名,指针需要通过解引用间接访问,而引用是直接访问;
- 指针可以改变地址,从而改变所指的对象,而引用必须从一而终;
- 引用在定义的时候必须初始化,而指针则不需要;
- 指针可以为空,引用不能为空
- 可以有const指针,没有const引用
智能指针:
- 智能指针是一个类,原理是RAII(构造时获取资源,析构时释放资源)
- 使用智能指针需要include<memory>
- 使用智能指针是为了解决内存泄漏和野指针这两个问题:
野指针:未初始化或未清零的指针
形成野指针的原因:
- 指针变量没有被初始化
- 指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针(浅拷贝)
内存泄漏:动态开辟的空间,在使用完毕后未释放,内存泄漏是资源泄漏中的一种,资源泄漏的另外一种是句柄泄漏
句柄泄漏:
举个例子,socket句柄泄漏:B多次尝试连接A,每一次重连使用一个socket id ,当C连接B的时候,B没有了可用的socket id,所以B拒绝服务,解决问题的方法是:在B每次重连A的时候,调用系统提供的close()函数,把已经断开连接的socket的socket id释放
- auto_ptr
- 解决了内存泄漏问题和浅拷贝导致的野指针问题
- get()得到原始指针
- reset(参数)重新绑定指向对象,原来的对象被释放(如果不带参数,则释放资源)
- release()把智能指针赋值为空,但是它原来指向的内存并没有被释放
- auto_ptr的成员函数时用的是“.”,访问指向对象的成员时用的是“->”
- 存在的问题:所有权转移(赋值或复制);对智能指针进行赋值时,如ptest2 = ptest,ptest2会接管ptest原来的内存管理权,ptest会变为空指针,如果ptest2原来不为空,则它会释放原来的资源;多个auto_ptr不能同时拥有同一个对象;Test *t1 = new Test(3); auto_ptr<Test> ptr1(t1); auto_ptr<Test> ptr2(t1);这里ptr1与ptr2都认为指针t1是归它管的,在析构时都试图删除t1,这样就造成了重复释放问题;不能用auto_ptr管理数组指针,auto_ptr的析构函数中删除指针用的是delete,而不是delete [];不能在stl中使用,p1=p2,使p2为空,这样会使得stl容器里很多元素为空
C++ 11标准中出现的新智能指针:
- unique_ptr
- auto_ptr中的函数仍可用
- move()可以将所有权由一个unique_ptr对象转移到另一个unique_ptr对象
- 禁止赋值和复制:同一时刻只能有一个unique_ptr实例指向给定对象。拷贝构造函数以及等号(“=”)是无法使用的
- 可以管理数组指针:因为unique_ptr有unique_ptr< X[ ] >重载版本,销毁动态对象时调用delete[],所以可以用unique_ptr来管理数组指针
- 可以在stl容器中使用:因为禁止了赋值和复制
- shared_ptr
- shared_ptr是共享所有权的,其内部有一个计数机制
- 默认使用delete实现资源释放,也可以定义自己的函数来释放,自定义释放的方法有两种:
- lambda表达式(匿名的局部函数)
- 括号操作符的重载
- shared_ptr对象的引用是强引用
- 强引用:对象被创建时,计数为1;每创建一个变量引用该对象时,该对象的计数就增加1;当上述变量销毁时,对象的计数减1,当计数为0时,这个对象也就被析构了
- 弱引用:弱引用不修改对象的引用计数,它只是检测所管理的对象是否已经被释放
- 存在的问题
- 多个独立的shared_ptr实例不能共享一个对象(不能将一个对象绑定到多个智能指针,可以共享所有权,通过=实现),因为可能会重复释放
- 循环引用问题:会造成内存泄漏
- this指针的问题:类的一个成员函数需要传递其this指针到一个普通函数,类的对象在主函数里会绑定到一个智能指针,在传递this的时候又绑定了一个智能指针,这样会造成重复释放。
- weak_ptr
- 解决shared_ptr的循环引用和this指针的问题:
- 循环引用类里面的shared_ptr改成weak_ptr
- this指针用weak_ptr绑定后通过weak_ptr的lock()来获取shared_ptr
- 不具有普通指针的行为,它的作用在于协助shared_ptr工作,被设计为与shared_ptr共同工作
- weak_ptr对象的引用是弱引用
- weak_ptr观测资源,但没有共享资源,不会修改引用计数
- use_count():观测资源的引用计数
- expired():等价于use_count()==0,但更快
- lock():获取shared_ptr,当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr
- 解决shared_ptr的循环引用和this指针的问题:
private protected public区别:
- private:只能由本类中的函数,友元函数访问
- protected:可以被本类中的函数,子类的函数,友元函数访问
- public : 可以被本类函数,子类的函数,类对象,友元函数访问
- private属性不能被继承
- 使用private继承,父类中protected , public属性变成private
- 使用protected继承,父类中的protected,public属性变成protected
- 使用public继承,父类中的属性在子类中不变
CPP内存空间:
- 栈:由编译器分配和释放,存放函数的参数值,局部变量的值。在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用
- 堆:由程序员分配和释放,若程序员不释放,则程序结束时被OS回收。存放由new,malloc分配的内存,可动态扩展和收缩
- 全局区(静态区):全局变量和静态变量的存储是放在一起的,初始化的全局变量和初始化的静态变量在一块区域;未初始化的全局变量和未初始化的静态变量在相邻的另一块区域
- 文字常量区:常量字符串放在这里,程序结束后由系统释放
- 程序代码区:存放函数的二进制代码
堆和栈的区别:
- 栈由编译器管理,堆由程序员控制
- 堆会产生碎片,栈不会产生碎片
- 堆是向上(内存增加的方向)生长的,栈是向下生长的
- 左值和右值:左值既能够出现在等号左边也能出现在等号右边的变量(或表达式),右值指的则是只能出现在等号右边的变量(或表达式);左值是指表达式结束后依然存在的持久对象,而右值是指表达式结束时就不再存在的临时对象
动态链接库和静态链接库:
- 静态链接:在编译链接的时候将引用库文件和自己的文件一起打包成可执行文件,windows下是文件名后缀是.lib,linux下是.a
- 动态链接:是在程序运行时才被载入,windows文件名后缀是下是.dll,linux下是 .so
explicit:
声明为explicit的构造函数不能在隐式转换中使用
内联函数有什么优点?内联函数与宏定义的区别?
- 宏定义在预编译的时候就会进行宏替换;
- 内联函数在编译阶段,在调用内联函数的地方进行替换,减少了函数的调用过程,但是使得编译文件变大。因此,内联函数适合简单函数,对于复杂函数,即使定义了内联编译器可能也不会按照内联的方式进行编译。
- 内联函数相比宏定义更安全,内联函数可以检查参数,而宏定义只是简单的文本替换。因此推荐使用内联函数,而不是宏定义。
- 使用宏定义函数要特别注意给所有单元都加上括号,#define MUL(a, b) a b,这很危险,正确写法:#define MUL(a, b) ((a) (b))
检查内存泄漏:
Windows下在debug模式下用CRT库
内存分配要通过CRT在运行时实现,只要在分配内存和释放内存时分别做好记录,程序结束时对比分配内存和释放内存的记录就可以确定是不是有内存泄漏,通过包括 crtdbg.h,将malloc和 free函数映射到它们的调试版本,即 _malloc_dbg和 _free_dbg,这两个函数将跟踪内存分配和释放。 此映射只在调试版本(在其中定义了_DEBUG)中发生。 发布版本使用普通的 malloc和 free函数。
Linux下用valgrind工具
匿名函数:[int a](int b){} :a是在函数内可以访问的外部变量,b是函数参数,可以通过在函数后面用()传入参数如:[](int a){cout<<a<<endl;}(123)
STL:
- 容器及迭代器使用
- 算法使用
- 迭代器原理
- hash_map比map查找速度快,但是比map需要的内存多,是一种用空间换时间的做法
- hash_map使用
必须在构造函数初始化列表里进行初始化的数据成员有哪些:
- const成员
- 引用类型
- 没有默认构造函数的
构造函数中赋值和初始化列表赋值的区别:
- 构造函数中赋值,先调用默认构造函数,再调用拷贝构造函数
- 初始化列表只调用一次拷贝构造函数赋值
手写strcpy:
char * strcpy( char *strDest, const char *strSrc )
{
assert( (strDest != NULL) && (strSrc != NULL) );
if(strlen(strDest)<strlen(strSrc))
return NULL; //标准的strcpy函数没有此判断
char *address = strDest;
while( (*strDest++ = * strSrc++) != ‘\0’ );
*strDest='\0'; //标准的strcpy函数没有此操作
return address;
}
i++和++i
- i++是先把i的值拿来用,然后在自增1
- ++i是想把i自增1然后拿来用
跨平台技术有哪些:
- 代码转换:将某个语言转成另一种语言(比如将java转成c)
- 编译流:将某个语言编译为二进制文件,生成动态库或打包成 apk/ipa/xap 文件
- 虚拟机:通过将某个语言的虚拟机移植到不同平台上来运行
链式操作:
利用运算符进行的连续操作,如连续的赋值,连续的相加
将引用作为函数返回值要注意的地方:
- 不能返回局部变量,因为局部变量在栈里面创建,函数调用完就会被释放,所引用的地址就没有了
- 不能返回new分配的内存的引用,因为new了以后没有delete会造成内存泄漏
返回引用的好处:可以实现连续的输入输出赋值等操作(链式操作)
include<>和include“”
include<>去编译器的类库路径里面找头文件,include""先在项目的当前路径里面找头文件,如果没找到再去编译器的类库路径里面找。
struct和class的区别
- struct默认是public的,class默认是private的
- struct 能包含成员函数吗,能继承吗,能多态吗?都能
ifndef/define/endif作用
防止重复包含头文件和重复定义宏
C++11新特性
数据结构与算法
这部分内容很多一定要多刷牛客
- 高度平衡的二叉搜索树
- 左子树和右子树的高度差不超过1
- 左子树和右子树都是平衡树
- 常见问题
- 结点要么是红色要么是黑色
- 根结点永远是黑色
- 所有叶子结点都是红色
- 红色结点的两个孩子结点都是黑色
- 任意一个结点到其子树的任意一个叶子结点所经过的黑结点数目是一样的
位运算:
- 按位取反~ 翻转操作数的每一位 每个 1 被设置为 0 而每个 0 被设置为 1。~y=-y-1
- ^异或(模2加)
- 左移操作符 << :从右边开始用 0 补空位
- 右移操作符 >> :从左边开始,或者插入符号位的拷贝 或者插入 0
- 按位与&
- 按位异或^
- 按位或 |
- 优先级:移位符高于逻辑符
最大子串问题
#include<iostream>
#include<string>
#include<map>
#include<algorithm>
using namespace std;
int longstr(string s){
int len=s.size();
int pre=0;
int max=0;
map<char,int> m;
int i;
for(i=0;i<len;i++){
if(m.find(s[i])!=m.end()){
pre=m[s[i]]+1;
}
m[s[i]]=i;
if(max<(i-pre+1))
max=i-pre+1;
}
return max;
}
int main(){
string s;
cin>>s;
cout<<longstr(s)<<endl;
return 0;
}
位图,是实现海量数据处理的常用的方法,用一位表示一个数据(1出现,0没出现)
兔子繁殖
- 斐波那契数列
- 这个月的兔子等于上个月兔子的数量加上上个月可繁殖的兔子(即上上个月的兔子)数量。
- f(n)=f(n-1)+f(n-2)
网络与TCP/IP
为什么是三次握手不是两次?
服务端验证客户端能收到自己的信息
cookie和session
- cookie存放在客户端,session存放在服务器端,都是用于会话跟踪
- 浏览器第一次访问服务器,服务器创建一个session,并将session id返回给浏览器,浏览器将其保存在cookie里面
- cookie用来保存用户信息(如账号密码)及实现session(保存session id,每次访问服务器的时候都带上这个session id),要是浏览器禁用了cookie则通过url重写技术来跟踪会话(url后面被附加上一个诸如 sid=xxxxx 这样的参数)
- session用来识别用户
- Cookie有大小限制以及浏览器在存cookie的个数也有限制,Session理论上没有大小限制(服务器端保存session的方法很多:比如内存,数据库,文件)
- Cookie有安全隐患,通过拦截或本地文件找得到你的cookie后可以进行攻击。session保存在服务器安全一点
- Session保存在服务器端一段时间后会消失,如果session过多会增加服务器的压力,cookie有效期为正数则为持久化cookie,写入对应的cookie文件无论用户关闭了浏览器还是电脑,只要在有效期之前cookie都是有效的,若cookie有效期为负数则为临时cookie,只在本窗口及本窗口子窗口有效,关闭浏览器cookie就消失,默认cookie有效期是-1
OSI模型中ARP协议属于链路层;TCP/IP模型中,ARP协议属于网络层
浏览器中输入一个URL发生什么:
- 首先在应用层进行DNS解析,先从本地DNS缓存找,如果没有找到就访问DNS服务器(递归和迭代两种方式)
- 用DNS解析出ip以后在应用层发送http请求
- 然后在传输层tcp三次握手建立连接,然后传输数据
- 在网络层使用IP协议来传输分割好的tcp数据包数据,使用ARP协议根据ip得到mac地址
- 找到mac地址后把数据发送到数据链路层进行传输
- 接受方在数据链路层收到数据后把数据一层一层的往上传,一直传到应用层
- 然后服务器响应请求,返回html文件
- 浏览器收到服务器返回的文件以后进行页面渲染
状态码
- 1xx表示请求被接受,需要进一步操作
- 2xx表示请求成功
- 3xx表示需要更多的操作来完成这个请求
- 4xx表示请求失败
- 5xx表示服务器内部错误
流量控制和拥塞避免的区别
- 流量控制是控制发送方的发送速度从而使得接收方能正常接收
- 拥塞避免是避免过多的流量进入网络造成网络拥堵
数据库
- SQL语句
- 看MySQL必知必会
- and 的优先级比 or 要高,其实用的时候加上括号就好了
- 使用通配符必须使用like,通配符(%0个或多个字符,_一个字符)
- 正则表达式regexp
- \\(匹配)
- 几乎所有类型的 WHERE 子句都可以用 HAVING 来替代。唯一的差别是WHERE 过滤行,而 HAVING 过滤分组
- 外键是另一个表中的主键
- join
- https://blog.****.net/huatian5/article/details/80854455
- inner join 内连接,inner可以省略,两表取交集
- left join 左连接,以左表为基础,返回右表中匹配的行,若右表中没有匹配的行则返回NULL
- right join 右连接,以右表为基础,返回左表中匹配的行,若左表中没有匹配的行则返回NULL
- 游标是一种从包括多条数据记录的结果集中每次提取一条记录的机制
- 看MySQL必知必会
MySQL索引结构
- B+树索引
- Hash索引
- B+树索引和Hash索引的区别?
- Hash索引等值查询效率更高,但是Hash索引不支持范围查询
索引
- 索引可以增加检索性能,同时会降低修改性能
- 索引的作用
- 可以大大加快数据的检索速度
- 创建唯一性索引,可以保证表中的每一行数据的唯一性
- 加速表和表之间的连接
- 可以减少查询中分组和排序的时间
- 索引并不是越多越好
- 创建索引和维护索引要耗费时间,数据量越大耗费的时间越多
- 索引需要占用物理空间
- 对数据进行维护的时候(增加,删除,修改),索引也要动态维护,这样就降低了数据的维护速度
- 索引是建立在数据库表的某些列上面的,一般在需要经常搜素的列上创建索引,很少查询的列不创建索引(因为不能明显提高查询的速度,反而还会因为增加了索引降低了维护的速度和增加了空间的需求),需要经常增加,删除,修改操作的列也不适合增加索引
- 创建索引 create index
- 唯一性索引:索引列中的数据是唯一的
- 复合索引:索引创建在两个列或者多个列上
- 聚集索引:表记录的排列顺序和与索引的排列顺序一致
- 非聚集索引:表记录的排列顺序和与索引的排列顺序不一致
- 聚集索引在数据库中开辟一个物理空间存放,非聚集索引看成是一个含有聚集索引的表
InnoDB与MyISAM区别:
- myisam提高了查和增加的效率
- innodb的功能更全面,但相对来说效率低一些
- innodb支持事务支持主键不支持全文索引,myisam不支持事务不支持主键支持全文索引
- myisam使用的是表锁(直接锁定整张表),innodb使用的是行锁(也支持表锁)
- myisam在磁盘上存储成三个文件(.frm文件存储表定义,.MYD是数据文件,.MYI是索引文件;innodb则由.frm文件,表空间和日志文件组成。
- myisam内置了一个计算器存储了表的总行数,innod则没有。
- myisam只把索引load到内存,innodb把索引和数据都load到内存
什么时候需要用到事务
- 事务,它是一个操作序列,这些操作要么都执行,要么都不执行
- 例如,银行转账工作:从一个账号扣款并使另一个账号增款,这两个操作要么都执行,要么都不执行
事务的基本要素(ACID):
- 原子性:一个事务中的所有操作要么全都提交成功,要么全都失败回滚
- 一致性:从一个一致的状态转到另一个一致的状态
- 隔离性:一个事务的做的修改,在提交前对其它事务是不可见的
- 持久性:一旦事务提交,所做的修改就会永久保存在数据库中
事务的四个隔离级别 :
- 未授权读取:一个事务写数据,不允许其他事务写,但允许其他事务读(解决了更新丢失,存在脏读的问题)
- 授权读取:一个事务读数据,允许其他事务访问数据,但是未提交的写事务禁止其他事务对该行的访问(解决了脏读的问题,可能出现不可重复读)
- 可重复读:事务读的时候禁止其他事务写(但是允许其他事务读),事务写的时候禁止其他事务读写。解决了不可重复读,但是可能出现幻读。幻读:事务在操作的时候进行了两次查询,第二次查询出现了第一次没有的数据或者缺少了第一次出现的数据(其他事务插入或删除数据造成的)。序列化:事务只能一个接一个的执行(问题都解决了,但是性能低)。更新丢失:两个事物对一行数据更新,一个事务对数据的更新把另一个数据对事务的更新给覆盖了。脏读:一个事务读取到了另一个事务未提交的数据
- 不可重复读:一个事务对同一行数据读取两次得到不一样的结果
锁:
- 共享锁(读锁):加了读锁,可以读不能写,其他事务还可以对数据对象加读锁但是不能加写锁
- 排他锁(写锁):加了写锁,事务对数据可读可写,但是其他事务不能再加任何锁
- 悲观锁:顾名思义,就是很悲观,每次去拿数据都认为别人会修改,所以每次拿数据都会上锁
- 乐观锁:顾名思义,就是很乐观,每次去拿数据都认为别人不会修改,所以每次拿数据都不上锁,但是在更新的时候会看一下在这期间有没有人更新过这个数据,如果发现有人修改就会返回错误信息,让用户决定如何处理。
Linux及操作系统知识
进程:是程序关于某个数据集合上的一次运行
线程:是进程的一个实体,是进程的一个执行单位
进程与线程区别:
- 进程有自己独立的地址空间和资源,而线程没有,线程必须依赖于进程而存在
- 进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源
- 进程是系统资源分配的单位,线程是CPU调度的单位
- 线程是进程的执行单元
- 一个程序至少有一个进程,一个进程至少有一个线程
- 一个线程只属于一个进程,一个进程可以拥有多个线程
- 进程切换的开销较大。线程相对较小
- 线程间栈和寄存器不能共享,堆和全局变量以及局部变量都可以共享
线程比进程具有哪些优势?
什么时候用多进程?什么时候用多线程?
Linux中进程和线程使用的函数?
线程同步方法
- 临界区:保证每次只能有一个线程进入临界区,在几种同步处理中,临界区速度最快,但它只能实现同进程中的多个线程同步。c++11并没有为我们提供临界区类
- 互斥量:mutex支持多进程。c++11标准库中提供了mutex类
- 信号量(pv操作):p操作(申请资源)v操作(释放资源)
- 事件(windows里面)
- 创建事件
- 等待事件被触发
- WaitForSingleObject(aEvent ,3000);意思就是当代码执行到这的时候,就会在此处阻塞着,直到另外一个线程里触发这个事件后或者等待超时后,才会继续往下执行
共享文件映射
常见的信号有哪些
内存管理
虚拟内存
操作系统层面对内存的管理
内存池的作用
STL里内存池如何实现
进程空间和内核空间对内存的管理不同
Linux的slab层,VAM
伙伴算法
高端内存
进程调度
死锁:多个线程因竞争资源而造成的一种僵局
发生的原因:
- 系统资源不足
- 资源分配不当
- 线程间推进的顺序不当
死锁发生的条件
- 资源独占:一段时间内资源只能被一个进程占有
- 保持申请:进程在占有资源的情况下还能继续申请其它资源
- 不可剥夺:申请者不能强行从占有者手里剥夺资源
- 循环等待:p1等p2,p2等p3....................pn等p1
死锁检测?
Linux命令
- IO模型
- 线程池
- fork与vfork区别
- exit()与_exit()区别
- 孤儿进程与僵尸进程
- Linux是如何避免内存碎片的
- 共享内存的实现原理
- 同步方法有哪些
- ++i是否是原子操作
明显不是,++i主要有三个步骤,把数据从内存放在寄存器上,在寄存器上进行自增,把数据从寄存器拷贝会内存,每个步骤都可能被中断。
- 判断大小端
- epoll和select,poll
设计模式
单例模式
class A{
private:
A(){a};
static A* a;
public:
static A* getA(){
if(a==NULL)
a=new A();
return a;
}
}
一个类只有一个实例(比如windows的任务管理器,无论你打开多少次,始终显示一个窗口)
STL里的迭代器使用了迭代器模式
MVC的理解
分布式系统
- map_reduce原理
- 负载均衡
- CDN
收集的帖子