最常使用的2种存储引擎:
1.Myisam是Mysql的默认存储引擎,当create创建新表时,未指定新表的存储引擎时,默认使用Myisam。每个MyISAM在磁盘上存储成三个文件。文件名都和表名相同,扩展名分别是.frm(存储表定义)、.MYD(MYData,存储数据)、.MYI(MYIndex,存储索引)。数据文件和索引文件可以放置在不同的目录,平均分布io,获得更快的速度。
2.InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比Myisam的存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。
目前世界上主流的存储系统大部分还是采用了关系型数据库,其主要有一下优点:
1.事务处理—保持数据的一致性;
2.由于以标准化为前提,数据更新的开销很小(相同的字段基本上只有一处);
3.可以进行Join等复杂查询。(如SELECT子句(SELECT A.a,A.b,B.a,B.d)中既有表A的字段,同时还有B表的字段,此时使用单独使用FROM A或FROM B已经解决不了问题了,使用JOIN来关联表A和表B即可解决问题,即FROM A,B或A JOIN B,这种方式得到的结果其实是表A与表B的笛卡尔积,)
nosql在优势方面,主要体现在下面这三点:
1. 简单的扩展:典型例子是Cassandra,由于其架构是类似于经典的P2P,所以能通过轻松地添加新的节点来扩展这个集群;
2. 快速的读写:主要例子有Redis,由于其逻辑简单,而且纯内存操作,使得其性能非常出色,单节点每秒可以处理超过10万次读写操作;
3. 低廉的成本:这是大多数分布式数据库共有的特点,因为主要都是开源软件,没有昂贵的License成本;
4. 但瑕不掩瑜,NoSQL数据库还存在着很多的不足,
卡特兰数
C(n, 2n) - C(n +1, 2n)、
h(n)=C(n,2n)/(n+1)
0读取C/C++源程序,对其中的伪指令(以#开头的指令)进行处理
①将所有的“#define”删除,并且展开所有的宏定义
②处理所有的条件编译指令,如:“#if”、“#ifdef”、“#elif”、“#else”、“endif”等。这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。
③处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。
2. 编译
将预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后,产生相应的汇编代码文件。
3. 汇编
将编译完的汇编代码文件翻译成机器指令,并生成可重定位目标程序的.o文件,该文件为二进制文件,字节编码是机器指令。
汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。所以汇编器的汇编过程相对于编译器来讲比较简单,它没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译即可。
4. 链接
通过链接器将一个个目标文件(或许还会有库文件)链接在一起生成一个完整的可执行程序。
由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。
例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
链接程序的主要工作就是将有关的目标文件彼此相连接,也就是将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
5循环列表
头指针指向最开始的位置
尾指针指向最后一个元素的下一个
const修饰符
const char *a(即*a)由于const的修饰而不可通过指针a去修改。故a是个普通的指针,可以修改
char *const a故a是个不可修改的指针,但可通过指针a去修改a所指向的数据(即*a)。
char const *a和const char *a是同一个意思。
和char没关系,去掉它。const后面跟着啥就修饰啥。
函数重载
反汇编后返回类型+函数名+参数列表
void print(long l) --> _Z5printl
void print(char str) --> _Z5printc
可以发现大概是int->i,long->l,char->c,string->Ss….基本上都是用首字母代表
之所以重载不考虑返回类型是为了避免函数不能独立于上下文运算。
比如同时在之前定义了float a,和double b。在后面用同一个函数名字的话
- fl=sqrt(fla);
- d=sqrt(fla);我得回去查fl和d的类型定义。
request header:
包括了,1.请求的方法是POST/GET,请求的URL,http协议版本2.请求的数据,和编码方式3是否有cookie和cooies,是否缓存等。
post和get请求方式的区别是,get把请求内容放在URL后面,但是URL长度有限制。而post是以表单的形势,适合要输入密码之类的,因为不在URL中显示,所以比较安全。
request body:
即请求的内容.
第二:S收到了http请求,然后根据请求头,返回http响应。
response header:包括了1.cookies或者sessions2.状态吗3.内容大小等
response body:
即响应的内容,包括,JS什么的。
第三,C收到了以后,就由浏览器完成一系列的渲染,包括执行JS脚本等。
- 通用头:是客户端和服务器都可以使用的头部,可以在客户端、服务器和其他应用程序之间提供一些非常有用的通用功能,如Date头部。
- 请求头:是请求报文特有的,它们为服务器提供了一些额外信息,比如客户端希望接收什么类型的数据,如Accept头部。
- 响应头:便于客户端提供信息,比如,客服端在与哪种类型的服务器进行交互,如Server头部。
- 实体头:指的是用于应对实体主体部分的头部,比如,可以用实体头部来说明实体主体部分的数据类型,如Content-Type头部。
函数调用入栈出栈的过程
<4>对于_stdcall调用约定,函数调用时用到的指令序列大致如下:
push 参数3 ;假设该函数有3个参数,将从右向做依次入栈
push 参数2
push 参数1
call 函数地址 ;call指令将同时完成两项工作:a)向栈中压入当前指令地址的下一个指令地址,即保存返回地址。 b)跳转到所调用函数的入口处。
push ebp ;保存旧栈帧的底部
mov ebp,esp ;设置新栈帧的底部 (栈帧切换)
sub esp,xxx ;设置新栈帧的顶部 (抬高栈顶,为新栈帧开辟空间)
函数返回的步骤如下:
<1>保存返回值,通常将函数的返回值保存在寄存器EAX中。
<2>弹出当前帧,恢复上一个栈帧。具体包括:
(1)在堆栈平衡的基础上,给ESP加上栈帧的大小,降低栈顶,回收当前栈帧的空间。
(2)将当前栈帧底部保存的前栈帧EBP值弹入EBP寄存器,恢复出上一个栈帧。
(3)将函数返回地址弹给EIP寄存器。
<3>跳转:按照函数返回地址跳回母函数中继续执行。
还是以C语言和Win32平台为例,函数返回时的相关的指令序列如下:
add esp,xxx ;降低栈顶,回收当前的栈帧
pop ebp ;将上一个栈帧底部位置恢复到ebp
retn ;a)弹出当前栈顶元素,即弹出栈帧中的返回地址,至此,栈帧恢复到上一个栈帧工作完成。b)让处理器跳转到弹出的返回地址,恢复调用前代码区
(esp当前栈顶,ebp当前栈底,eip命令行指针),每一个C++类成员函数都有一个this指针,在Windows平台中,这个指针一般是用ECX寄存器来传递的,但如果用GCC编译器来编译,这个指针会作为最后一个参数压入栈中。
c++用c的函数的时候,c不会重载,不会生成那个返回值+函数名+参数名的中间函数。所以c函数要在黄色区域写,不会瞎几把把c的函数转换成别的格式。
#ifdef __cplusplus //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
extern "C"{
#endif
/*…*/
#ifdef __cplusplus
}
#endif
C++——复制构造函数的形参为什么要是const引用
因为隐式转换,所以要创造实参,无限循环。复制构造函数如果函数的形参是引用,则调用函数时不需要复制实参。加上const,防止对实参的意外修改(只要不修改实参无所谓啦)
拷贝函数被调用的情况有:
1,定义新对象,并用已有对象初始化新对象时; 即执行语句“CExample B=A; ” 时(定义对象时使用赋值初始化);b(a)
2,当对象直接作为参数传给函数时,
3,当函数中的局部对象被返回值,
explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。
circle
隐式转换 20. Circle A = 1.23;
17. //tmp = Circle(1.23)
18. //Circle A(tmp);
19. //tmp.~Circle();
非隐式
22. Circle A = Circle(1.23);
23. Circle B = Circle(123);
24. Circle C = A;
函数末尾声明const表示函数体内不能对成员数据做任何改动 。 const对象只能调用const成员函数。
c独有(c++强制转换,int a=(int)b(b之前声明为char))
const_cast< type-id> ( expression)
这个转换类型操纵传递对象的const属性,或者是设置或者是移除:
reinterpret_cast< type-id> ( expression)
用在任意指针(或引用)类型之间的转换;以及指针与足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。
dynamic_cast < type-id> ( expression)
只用于对象的指针和引用之间的转换,需要虚函数。
static_cast<type-id >( expression )
用于数值类型之间的转换,也可以用于指针之间的转换不过要先到void*
信号量互斥锁异同
信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait使得线程阻塞,直到sem_post释放后value值加一,但是sem_wait返回之前还是会将此value值减一。因为信号量不为01,所以可以建立一个队列,顺便保证执行有序。
互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源
互斥锁哪个线程锁的就得谁解。
流量控制(滑动窗口)与拥塞控制(慢启动)
- 流控制是一种局部控制机制,其参与者仅仅是发送方和接收方,它只考虑了接收端的接收能力,而没有考虑到网络的传输能力;
- 而拥塞控制则注重于整体,其考虑的是整个网络的传输能力,是一种全局控制机制。
慢开始与拥塞避免
无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络拥塞(没有收到确认,虽然没有收到确认可能是其他原因的分组丢失,但是因为无法判定,所以都当作拥塞来处理),就把慢开始门限设置为出现拥塞时的发送窗口大小的一半,然后把拥塞窗口设置为1,执行慢开始算法
快重传和快恢复
快重传要求接收方在收到一个失序的报文端就立即发出重复确认(为的是使发送及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认
TCP协议保证数据可靠性的一个重要机制,其原理是在发送一个数据以后就开启一个计时器,
在一定时间内如果没有得到发送数据报的ACK报文,那么就重新发送数据,直到发送成功为止。一般来说,重传发生在超时之后,但是如果发送端接收到3个以上
的重复ACK,就应该意识到,数据丢了,需要重新传递。这个机制不需要等到重传定时器溢出,所以叫做快重传
而快速重传以后,因为走的不是慢启动而是拥塞避免算法,所以叫快速恢复算法
快重传和快恢复旨在:快速恢复丢失的数据包
hashtree
(快速查找,每个节点用质数,10层可分辨6464693230 )每层为2,3,,5,7节点数因为取余。
mapreduce(类似分治法)将重复类型的海量数据分类并做统一操作
先在磁盘分区,用map转换成(k,v)数据形式,然后整体排序。将相同k的递给reduce做操作。
bitmap
普通的hash,每一个位存储一个数据表示有无进行标记00.01.10.11.分别代表0,1,2,3这4个数字有无,可以增加个映射00->0-100的数字从而快速查询当前类别是否包含该数据。
前缀树
将具有大量相同前缀的数字放到一个节点(方便存储,快速查找)
后缀树:压缩存储
从已知字母展开其后面所有的可能性、
大小端字节
struct
1、数据成员对齐规则:结构体(struct)的数据成员,第一个数据成员放在offset为0的地方,之后的每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机子上为4字节,所以要从4的整数倍地址开始存储)。
2、结构体作为成员:如果一个结构体里同时包含结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储(如struct a里有struct b,b里有char,int ,double等元素,那么b应该从8(即double类型的大小)的整数倍开始存储)。
3、结构体的总大小:即sizeof的结果。在按之前的对齐原则计算出来的大小的基础上,必须还得是其内部最大成员的整数倍,不足的要补齐(如struct里最大为double,现在计算得到的已经是11,则总大小为16)。
union
当一个共用体被声明时, 编译程序自动地产生一个变量, 其长度为联合中元类型(如数组,取其类型的数据长度)最大的变量长度的整数倍,且要大于等于其最大成员所占的存储空间.
cin输入和后如果用getline和getchar,
要多加一个char ch=getchar(),读掉'\10',回车
内联函数只是用代码复制取代了函数调用,如果代码较长就不适用使用内联函数。
一个20G大小的文件,假设内存只有256M,如何对文件进行排序
随便吧,比如说归并,或者什么mapreduce,反正想怎么排怎么排啊
1. 第一范式(1NF):属性不可拆分 或 无重复的列
2. 第二范式(2NF):完全函数依赖,消除部分函数依赖
部分函数依赖,就是多个属性决定另一个属性,但事实上,这多个属性是有冗余的。例如,(学号,班级)->姓名,事实上,只需要学号就能决定姓名,因此班级是冗余的,应该去掉。
3.第三范式(3NF):消除传递依赖
也就是,数据库中的属性依赖仅能依赖于主属性,不存在于其他非主属性的关联。
例如,图书,图书室的关系。图书包括编号、出版商、页码等信息,图书室包括图书室编号、所存图书(外键)。其中,图书室的表中不应该存储任何图书的具体信息(例如,出版商。。),而只能通过主键图书编号来获得对应图书的信息。
4.BC范式,主属性不依赖于主属性。在关系模式中每一个决定因素都包含候选键,也就是说,只要属性或属性组A能够决定任何一个属性B,则A的子集中必须有候选键。BCNF范式排除了任何属性(不光是非主属性,2NF和3NF所限制的都是非主属性)对候选键的传递依赖与部分依赖。
(仓库ID) → (管理员ID)
(管理员ID) → (仓库ID)
即存在关键字段决定关键字段的情况,所以其不符合BCNF范式。它会出现如下异常情况:
5.第四范式的定义很简单:已经是BC范式,并且不包含多值依赖关系。
6.多值依赖
并且Z=U-X-Y,多值依赖X->->Y成立当且仅当对R的任一个关系r,r在(X,Z)上的每个值对应一组Y的值,这组值仅仅决定于X值而与Z值无关。
如下表:
课程C 教师T 参考书B
数学 邓军 数学分析
数学 邓军 高等代数
数学 邓军 微分方程
表中,U = C+T+B,(C,T)确定一组B,但是这组B其实与T无关,仅由C确定,所以(C,T)->->B。又因为T不是空集,所以(C,T)->->B为非平凡多值依赖。
要想消除多只依赖,可以分解为:(C,T), (C,B)及
表1:
课程C 教师T
数学 邓军
表2:
课程C 参考书B
数学 数学分析
数学 高等代数
数学 微分方程
HTTP的头域包括通用头,请求头,响应头和实体头四个部分
请求行 - 通用信息头 - 请求头 - 实体头 - 报文主体
状态行 - 通用信息头 - 响应头 - 实体头 - 报文主体
tcp谁断开连接断开连接
总结: (不考虑keepalive)
http1.0
带content-length,body长度可知,客户端在接收body时,就可以依据这个长度来接受数据。接受完毕后,就表示这个请求完毕了。客户端主动调用close进入四次挥手。
不带content-length ,body长度不可知,客户端一直接受数据,直到服务端主动断开
http1.1
带content-length body长度可知 客户端主动断开
带Transfer-encoding:chunked body会被分成多个块,每块的开始会标识出当前块的长度,body就不需要通过content-length来指定了。但依然可以知道body的长度 客户端主动断开
不带Transfer-encoding:chunked且不带content-length 客户端接收数据,直到服务端主动断开连接。
线程池
使用智能指针会不会内存泄漏:
智能指针
1.shared_ptr计算引用指针数量。
2.unique_ptr抢夺智能指针所有权(程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做,最常见的是函数返回一个指针赋值给另一个,这种情况下允许)
3.weak_ptr去考察对象是不是真的存在,存在等同于shared_ptr,不存在变为空指针。(为了解决的空悬指针问题以及循环引用问题。)
关于printf的缓冲区问
1 缓冲区填满
2 写入的字符中有‘\n’ '\r'
3 调用fflush手动刷新缓冲区
4 调用scanf要从缓冲区中读取数据时,也会将缓冲区内的数据刷新
printf的缓冲区是1024byte
classA和classB互相引用
A.h
1 2 3 4 5 6 7 8 9 10 | #ifndef AH #define AH class B; class A { public: B* b; void setB(); ~A(); }; #endif |
B.h
1 2 3 4 5 6 7 8 9 10 11 | #ifndef BH #define BH #include "A.h" class B { public: A a; void haha() { } }; #endif |
A.cpp
#include "A.h" #include "B.h" A::~A() { delete b; } void A::setB() { b->haha(); } |
模板类:h声明。hpp实现,cpp调用。
自由存储区和堆
- 自由存储是C++中通过new与delete动态分配和释放对象的抽象概念,而堆(heap)是C语言和操作系统的术语,是操作系统维护的一块动态分配内存。
- new所申请的内存区域在C++中称为自由存储区。藉由堆实现的自由存储,可以说new所申请的内存区域在堆上。
- 堆与自由存储区还是有区别的,它们并非等价。
new malloc
new和malloc的区别是C/C++一道经典的面试题,我也遇到过几次,回答的都不是很好,今天特意整理了一下。
0. 属性
new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。
1. 参数
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
2. 返回类型
new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
3. 分配失败
new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
4. 自定义类型
new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。
malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
5. 重载
C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。