—> 转载自https://github.com/chankeh/cpp-backend-reference
突击复习常见题,要提前批了,之前的忘了不少,知识广度先再拓展一下。文章中的一些详细讲解的链接可以深度学习。
volatile作用
Volatile关键词的第一个特性:易变性。所谓的易变性,在汇编层面反映出来,就是两条语句,下一条语句不会直接使用上一条语句对应的volatile变量的寄存器内容,而是重新从内存中读取。
Volatile关键词的第二个特性:“不可优化”特性。volatile告诉编译器,不要对我这个变量进行各种激进的优化,甚至将变量直接消除,保证程序员写在代码中的指令,一定会被执行。
Volatile关键词的第三个特性:”顺序性”,能够保证Volatile变量间的顺序性,编译器不会进行乱序优化。 (C/C++ Volatile变量,与非Volatile变量之间的操作,是可能被编译器交换顺序的; C/C++ Volatile变量间的操作,是不会被编译器交换顺序的)
C/C++ Volatile关键词深度剖析
static
控制变量的存储方式和可见性。
(1)修饰局部变量
一般情况下,对于局部变量是存放在栈区的,并且局部变量的生命周期在该语句块执行结束时便结束了。但是如果用static进行修饰的话,该变量便存放在静态数据区,其生命周期一直持续到整个程序执行结束。但是在这里要注意的是,虽然用static对局部变量进行修饰过后,其生命周期以及存储空间发生了变化,但是其作用域并没有改变,其仍然是一个局部变量,作用域仅限于该语句块。
(2)修饰全局变量
对于一个全局变量,它既可以在本源文件中被访问到,也可以在同一个工程的其它源文件中被访问(只需用extern进行声明即可)。用static对全局变量进行修饰改变了其作用域的范围,由原来的整个工程可见变为本源文件可见。
(3)修饰函数
用static修饰函数的话,情况与修饰全局变量大同小异,就是改变了函数的作用域, 变为本源文件可见。
(4)C++中的static
如果在C++中对类中的某个函数用static进行修饰,则表示该函数属于一个类而不是属于此类的任何特定对象;如果对类中的某个变量进行static修饰,表示该变量为类以及其所有的对象所有它们在存储空间中都只存在一个副本。可以通过类和对象去调用。
const的含义及实现机制
const名叫常量限定符,用来限定特定变量,以通知编译器该变量是不可修改的。习惯性的使用const,可以避免在函数中对某些不应修改的变量造成可能的改动。
(1) const修饰基本数据类型
const修饰一般常量及数组
基本数据类型,修饰符const可以用在类型说明符前,也可以用在类型说明符后,其结果是一样的。在使用这些常量的时候,只要不改变这些常量的值便好。
const修饰指针变量*及引用变量&
如果const位于星号*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;
如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。
(2) const应用到函数中,
作为参数的const修饰符
调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,保护了原对象的属性。
[注意]:参数const通常用于参数为指针或引用的情况;
作为函数返回值的const修饰符
声明了返回值后,const按照"修饰原则"进行修饰,起到相应的保护作用。
(3) const在类中的用法
(4) const修饰类对象,定义常量对象
http://www.cnblogs.com/wintergrass/archive/2011/04/15/2015020.html
extern
在C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。
在C++中extern还有另外一种作用,用于指示C或者C++函数的调用规范。比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同,用此来解决名字匹配的问题。
宏定义和展开、内联函数区别,
宏定义不检查函数参数,返回值什么的,只是展开,相对来说,内联函数会检查参数类型,所以更安全。
宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。
宏是预编译器的输入,然后宏展开之后的结果会送去编译器做语法分析。宏与函数等处于不同的级别,操作不同的实体。宏操作的是 token, 可以进行 token的替换和连接等操作,在语法分析之前起作用。而函数是语言中的概念,会在语法树中创建对应的实体,内联只是函数的一个属性。
对于问题:有了函数要它们何用?答案是:一:函数并不能完全替代宏,有些宏可以在当前作用域生成一些变量,函数做不到。二:内联函数只是函数的一种,内联是给编译器的提示,告诉它最好把这个函数在被调用处展开,省掉一个函数调用的开销(压栈,跳转,返回)
内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率一样
内联函数必须是和函数体申明在一起,才有效。
宏定义和内联函数区别
STL提供六大组件,彼此可以组合套用:
容器(Containers):各种数据结构,如:序列式容器vector、list、deque、关联式容器set、map、multiset、multimap。用来存放数据。从实现的角度来看,STL容器是一种class template。
算法(algorithms):各种常用算法,如:sort、search、copy、erase。从实现的角度来看,STL算法是一种 function template。注意一个问题:任何的一个STL算法,都需要获得由一对迭代器所标示的区间,用来表示操作范围。这一对迭代器所标示的区间都是前闭后开区间,例如[first, last)
迭代器(iterators):容器与算法之间的胶合剂,是所谓的“泛型指针”。共有五种类型,以及其他衍生变化。从实现的角度来看,迭代器是一种将 operator*、operator->、operator++、operator- - 等指针相关操作进行重载的class template。所有STL容器都有自己专属的迭代器,只有容器本身才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。
仿函数(functors):行为类似函数,可作为算法的某种策略(policy)。从实现的角度来看,仿函数是一种重载了operator()的class或class template。一般的函数指针也可视为狭义的仿函数。
配接器(adapters):一种用来修饰容器、仿函数、迭代器接口的东西。例如:STL提供的queue 和 stack,虽然看似容器,但其实只能算是一种容器配接器,因为它们的底部完全借助deque,所有操作都由底层的deque供应。改变 functors接口者,称为function adapter;改变 container 接口者,称为container adapter;改变iterator接口者,称为iterator adapter。
配置器(allocators):负责空间配置与管理。从实现的角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放的class template。
这六大组件的交互关系:container(容器) 通过 allocator(配置器) 取得数据储存空间,algorithm(算法)通过 iterator(迭代器)存取 container(容器) 内容,functor(仿函数) 可以协助 algorithm(算法) 完成不同的策略变化,adapter(配接器) 可以修饰或套接 functor(仿函数)
STL六大组件
序列式容器及原理:
关联式容器:
list和vector有什么区别?
C++的多态分为静态多态(编译时多态)和动态多态(运行时多态)两大类。静态多态通过重载、模板来实现;动态多态就是通过本文的主角虚函数来体现的。
虚函数的作用说白了就是:当调用一个虚函数时,被执行的代码必须和调用函数的对象的动态类型相一致。编译器需要做的就是如何高效的实现提供这种特性。不同编译器实现细节也不相同。大多数编译器通过vtbl(virtual table)和vptr(virtual table pointer)来实现的。 当一个类声明了虚函数或者继承了虚函数,这个类就会有自己的vtbl。vtbl实际上就是一个函数指针数组,有的编译器用的是链表,不过方法都是差不多。vtbl数组中的每一个元素对应一个函数指针指向该类的一个虚函数,同时该类的每一个对象都会包含一个vptr,vptr指向该vtbl的地址。
结论:
结论:单继承时性能差不多,多继承的时候会慢
调用性能方面
从前面虚函数的调用过程可知。当调用虚函数时过程如下(引自More Effective C++):
通过对象的 vptr 找到类的 vtbl。这是一个简单的操作,因为编译器知道在对象内 哪里能找到 vptr(毕竟是由编译器放置的它们)。因此这个代价只是一个偏移调整(以得到 vptr)和一个指针的间接寻址(以得到 vtbl)。
找到对应 vtbl 内的指向被调用函数的指针。这也是很简单的, 因为编译器为每个虚函数在 vtbl 内分配了一个唯一的索引。这步的代价只是在 vtbl 数组内 的一个偏移。
调用第二步找到的的指针所指向的函数。
在单继承的情况下,调用虚函数所需的代价基本上和非虚函数效率一样,在大多数计算机上它多执行了很少的一些指令,所以有很多人一概而论说虚函数性能不行是不太科学的。在多继承的情况下,由于会根据多个父类生成多个vptr,在对象里为寻找 vptr 而进行的偏移量计算会变得复杂一些,但这些并不是虚函数的性能瓶颈。 虚函数运行时所需的代价主要是虚函数不能是内联函。这也是非常好理解的,是因为内联函数是指在编译期间用被调用的函数体本身来代替函数调用的指令,但是虚函数的“虚”是指“直到运行时才能知道要调用的是哪一个函数。”但虚函数的运行时多态特性就是要在运行时才知道具体调用哪个虚函数,所以没法在编译时进行内联函数展开。当然如果通过对象直接调用虚函数它是可以被内联,但是大多数虚函数是通过对象的指针或引用被调用的,这种调用不能被内联。 因为这种调用是标准的调用方式,所以虚函数实际上不能被内联。
占用空间方面
C++虚函数浅析
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
virtual void funtion1()=0
原因:
为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。
虚函数和纯虚函数的区别
资料: 虚析构函数(√)、纯虚析构函数(√)、虚构造函数(X)
静态化并不是单例 (Singleton) 模式:
饿汉模式: 是指单例实例在程序运行时被立即执行初始化:
class Log {
public:
static Log* Instance() {
return &m_pInstance;
}
virtual void Write(char const *logline);
virtual bool SaveTo(char const *filename);
private:
Log(); // ctor is hidden
Log(Log const&); // copy ctor is hidden
static Log m_pInstance;
static std::list m_data;
};
// in log.cpp we have to add
Log Log::m_pInstance;
懒汉模式 (堆栈-粗糙版)
class Log {
public:
static Log* Instance() {
if (!m_pInstance)
m_pInstance = new Log;
return m_pInstance;
}
virtual void Write(char const *logline);
virtual bool SaveTo(char const *filename);
private:
Log(); // ctor is hidden
Log(Log const&); // copy ctor is hidden
static Log* m_pInstance;
static std::list m_data;
};
// in log.cpp we have to add
Log* Log::m_pInstance = NULL;
懒汉模式 (局部静态变量-最佳版):
它也被称为 Meyers Singleton [Meyers]:
class Log {
public:
static Log& Instance() {
static Log theLog;
return theLog;
}
virtual void Write(char const *logline);
virtual bool SaveTo(char const *filename);
private:
Log(); // ctor is hidden
Log(Log const&); // copy ctor is hidden
Log& operator=(Log const&); // assign op is hidden
static std::list m_data;
};
在 Instance() 函数内定义局部静态变量的好处是, theLog 的构造函数只会在第一次调用
Instance() 时被初始化, 达到了和 “堆栈版” 相同的动态初始化效果, 保证了成员变量和 Singleton 本身的初始化顺序.
它还有一个潜在的安全措施, Instance() 返回的是对局部静态变量的引用, 如果返回的是指针, Instance() 的调用者很可能会误认为他要检查指针的有效性, 并负责销毁. 构造函数和拷贝构造函数也私有化了, 这样类的使用者不能自行实例化.
另外, 多个不同的 Singleton 实例的析构顺序与构造顺序相反.
考虑多线程+自动析构:(补充)C++中的单例模式
class Singleton
{
private:
Singleton(){}
~Singleton(){}
static Singleton *pInstance;
class Garbo{ //它的唯一工作就是在析构函数中删除Singleton的实例
public:
~Garbo(){
if (pInstance != NULL){
Lock();
if (pInstance != NULL){
delete pInstance;
pInstance = NULL;
cout << "Delete instance!" << endl;
}
Unlock();
}
}
};
static Garbo garbo; //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
public:
static Singleton *GetInstance() // 对GetInstance稍加修改,这个设计模板便可以适用于可变多实例情况,如一个类允许最多五个实例。
{
if (pInstance == NULL) //判断是否第一次调用
{
Lock();
if (pInstance == NULL) // 此处进行了两次m_Instance == NULL的判断,是借鉴了Java的单例模式实现时,
// 使用的所谓的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,
// 而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。
{
pInstance = new Singleton();
cout << "Create instance" << endl;
}
Unlock();
}
return pInstance;
}
};
Singleton *Singleton::pInstance = NULL;
Singleton::Garbo Singleton::garbo;
只能在堆内存上实例化的类:将析构函数定义为private,在栈上不能自动调用析构函数,只能手动调用。也可以将构造函数定义为private,但这样需要手动写一个函数实现对象的构造。
只能在栈内存上实例化的类:将函数operator new和operator delete定义为private,这样使用new操作符创建对象时候,无法调用operator new,delete销毁对象也无法调用operator delete。
设计一个只能在堆上或栈上实例化的类
String 类的原型如下
class String
{
public:
String(const char *str=NULL); //构造函数
String(const String &other); //拷贝构造函数
~String(void); //析构函数
String& operator=(const String &other); //等号操作符重载
ShowString();
private:
char *m_data; //指针
};
String::~String()
{
delete [] m_data; //析构函数,释放地址空间
}
String::String(const char *str)
{
if (str==NULL)//当初始化串不存在的时候,为m_data申请一个空间存放'\0';
{
m_data=new char[1];
*m_data='\0';
}
else//当初始化串存在的时候,为m_data申请同样大小的空间存放该串;
{
int length=strlen(str);
m_data=new char[length+1];
strcpy(m_data,str);
}
}
String::String(const String &other)//拷贝构造函数,功能与构造函数类似。
{
int length=strlen(other.m_data);
m_data=new [length+1];
strcpy(m_data,other.m_data);
}
String& String::operator =(const String &other)
{
if (this==&other)//当地址相同时,直接返回;
return *this;
delete [] m_data;//当地址不相同时,删除原来申请的空间,重新开始构造;
int length=sizeof(other.m_data);
m_data=new [length+1];
strcpy(m_data,other.m_data);
return *this;
}
String::ShowString()//由于m_data是私有成员,对象只能通过public成员函数来访问;
{
cout<<this->m_data<<endl;
}
main()
{
String AD;
char * p="ABCDE";
String B(p);
AD.ShowString();
AD=B;
AD.ShowString();
}
先调用基类的构造函数,在调用派生类的构造函数
先构造的后析构,后构造的先析构
相同点:
区别:
Linux虚拟内存的实现需要6种机制的支持:地址映射机制、内存分配回收机制、缓存和刷新机制、请求页机制、交换机制和内存共享机制
内存管理程序通过映射机制把用户程序的逻辑地址映射到物理地址。当用户程序运行时,如果发现程序中要用的虚地址没有对应的物理内存,就发出了请求页要求。如果有空闲的内存可供分配,就请求分配内存(于是用到了内存的分配和回收),并把正在使用的物理页记录在缓存中(使用了缓存机制)。如果没有足够的内存可供分配,那么就调用交换机制;腾出一部分内存。另外,在地址映射中要通过TLB(翻译后援存储器)来寻找物理页;交换机制中也要用到交换缓存,并且把物理页内容交换到交换文件中,也要修改页表来映射文件地址。
//法一:int k=~0;
if((unsigned int)k >63356) cout<<"at least 32bits"<<endl;
else cout<<"16 bits"<<endl;
//法二://32为系统
int i=65536;
cout<<i<<endl;
int j=65535;
cout<<j<<endl;
BOOL IsBigEndian()
{
int a = 0x1234;
char b = *(char *)&a; //通过将int强制类型转换成char单字节,通过判断起始存储位置。即等于 取b等于a的低地址部分
if( b == 0x12)
{
return TRUE;
}
return FALSE;
}
BOOL IsBigEndian()
{
union NUM
{
int a;
char b;
}num;
num.a = 0x1234;
if( num.b == 0x12 )
{
return TRUE;
}
return FALSE;
}
信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断。
进程之间可以互相通过系统调用kill发送软中断信号。
SIGHUP 1 A 终端挂起或者控制进程终止
SIGINT 2 A 键盘中断(如break键被按下)
SIGQUIT 3 C 键盘的退出键被按下
SIGILL 4 C 非法指令
SIGABRT 6 C 由abort(3)发出的退出指令
SIGFPE 8 C 浮点异常
SIGKILL 9 AEF Kill信号
SIGSEGV 11 C 无效的内存引用
SIGPIPE 13 A 管道破裂: 写一个没有读端口的管道
进程可以通过三种方式来响应一个信号:(1)忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;(3)执行缺省操作,
在后台运行。
为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。
if(pid=fork())
exit(0); //是父进程,结束父进程,子进程继续
脱离控制终端,登录会话和进程组
有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长:
setsid();
说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
禁止进程重新打开控制终端
现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:
if(pid=fork()) exit(0); //结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们:
for(i=0;i 关闭打开的文件描述符close(i);>
改变当前工作目录
进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如 /tmpchdir("/")
重设文件创建掩模
进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0);
处理SIGCHLD信号
处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将 SIGCHLD信号的操作设为SIG_IGN。
signal(SIGCHLD,SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。
系统调用
库函数调用
对于sock编程来说:
阻塞I/O模式: 进程处于阻塞模式时,让出CPU,进入休眠状态
非阻塞模式I/O: 非阻塞模式的使用并不普遍,因为非阻塞模式会浪费大量的CPU资源。 我们开始对 recvfrom 的三次调用,因为系统还没有接收到网络数据,所以内核马上返回一个EWOULDBLOCK的错误。 应用程序不停的 polling 内核来检查是否 I/O操作已经就绪。这将是一个极浪费 CPU资源的操作。这种模式使用中不是很普遍。
I/O多路复用:针对批量IP操作时,使用I/O多路复用,非常有好。
对于单个I/O操作,和阻塞模式相比较,select()和poll()或epoll并没有什么高级的地方。
多路复用的高级之处在于:它能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。
IO 多路技术一般在下面这些情况中被使用:
异步IO模式有:
信号驱动 I/O 模式下,内核在操作可以被操作的时候通知给我们的应用程序发送SIGIO 消息。
异步 I/O 模式下,内核在所有的操作都已经被内核操作结束之后才会通知我们的应用程序。
常用模型的缺点
PPC/TPC 模型: 两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我。只是 PPC 是为它开了一个进程,而 TPC 开了一个线程。可是别烦我是有代价的,它要时间和空间啊,连接多了之后,那么多的进程 / 线程切换,这开销就上来了;因此这类模型能接受的最大连接数都不会高,一般在几百个左右。
select 模型
poll 模型
Epoll 的提升
Epoll 为什么高效
Epoll 的高效和其数据结构的设计是密不可分的,这个下面就会提到。
Epoll 关键数据结构
前面提到Epoll 速度快和其数据结构密不可分,其关键数据结构就是:
struct epoll_event {
__uint32_tevents; // Epoll events
epoll_data_tdata; // User data variable
};
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
可见epoll_data 是一个 union 结构体 , 借助于它应用程序可以保存很多类型的信息 :fd 、指针等等。有了它,应用程序就可以直接定位目标了。
select和epoll的区别(必问)
epoll哪些触发模式,有啥区别?(必须非常详尽的解释水平触发和边缘触发的区别,以及边缘触发在编程中要做哪些更多的确认)
epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
惊群现象
多个进程/线程在等待同一资源是,每当资源可用,所有的进程/线程都来竞争资源,造成的后果:
最常见的例子就是对于socket描述符的accept操作,当多个用户进程/线程监听在同一个端口上时,由于实际只可能accept一次,因此就会产生惊群现象,当然前面已经说过了,这个问题是一个古老的问题,新的操作系统内核已经解决了这一问题。
对于一些已知的惊群问题,内核开发者增加了一个“互斥等待”选项。一个互斥等待的行为与睡眠基本类似,主要的不同点在于:
inode包含文件的元信息,具体来说有以下内容:
文件的字节数
文件拥有者的User ID
文件的Group ID
文件的读、写、执行权限
文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。
链接数,即有多少文件名指向这个inode
文件数据block的位置
每个inode都有一个号码,操作系统用inode号码来识别不同的文件。
用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:
硬链接:一般情况下,文件名和inode号码是"一一对应"关系,每个inode号码对应一个文件名。但是,Unix/Linux系统允许,多个文件名指向同一个inode号码。
软连接:
详解: 软链接和硬链接
TIME_WAIT:主动发起关闭的一方,表示收到了对方的FIN报文,并发送出了ACK报文。 TIME_WAIT状态下的TCP连接会等待2*MSL(Max Segment Lifetime,最大分段生存期,RFC 1122建议是2分钟),然后即可回到CLOSED 可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
主要有两个原因
time_wait太多解决
对于基于TCP的HTTP协议,关闭TCP连接的是Server端,这样,Server端会进入TIME_WAIT状态,可想而知,对于访问量大的Web Server,会存在大量的TIME_WAIT状态,维护这些状态给Server带来负担。当然现代操作系统都会用快速的查找算法来管理这些TIME_WAIT,所以对于新的TCP连接请求,判断是否hit中一个TIME_WAIT不会太费时间,但是有这么多状态要维护总是不好。
聚集索引:索引和数据在一起。该索引中键值的逻辑顺序决定了表中相应行的物理顺序。聚集索引一个表只能有一个,而非聚集索引一个表可以存在多个。
聚集索引存储记录是物理上连续存在,而非聚集索引是逻辑上的连续,物理存储并不连续。
Innodb中的主键是聚簇索引。适用于: 区分度高的列;范围值查询的列;被连续访问的列;
Innodb中的非主键索引是非聚集索引:数据存储在一个地方,索引存储在另一个地方,索引带有指针指向数据的存储位置。
可以创建不唯一的聚簇索引吗(主键可以不是唯一索引吗)
在数据库中通过什么描述聚集索引与非聚集索引的?
建立索引的优点
索引的缺点
非关系型数据库提出另一种理念,例如,以键值对存储,且结构不固定,每一个元组可以有不一样的字段,每个元组可以根据需要增加一些自己的键值对,这 样就不会局限于固定的结构,可以减少一些时间和空间的开销。使用这种方式,用户可以根据需要去添加自己需要的字段,这样,为了获取用户的不同信息,不需要 像关系型数据库中,要对多表进行关联查询。仅需要根据id取出相应的value就可以完成查询。
但非关系型数据库由于很少的约束,他也不能够提供像SQL 所提供的where这种对于字段属性值情况的查询。并且难以体现设计的完整性。他只适合存储一些较为简单的数据,对于需要进行较复杂查询的数据,SQL数 据库显的更为合适。
关系型数据库的最大特点就是事务的一致性:传统的关系型数据库读写操作都是事务的,具有ACID的特点,这个特性使得关系型数据库可以用于几乎所有对一致性有要求的系统中,如典型的银行系统。但是影响效率。
关系数据库的另一个特点就是其具有固定的表结构,因此,其扩展性极差。
非关系型数据库分类:
面向高性能并发读写的key-value数据库:
面向海量数据访问的面向文档数据库:
面向可扩展性的分布式数据库:
关系型数据库和非关系型数据库
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。悲观锁假定其他用户企图访问或者改变你正在访问、更改的对象的概率是很高的,因此在悲观锁的环境中,在你开始改变此对象之前就将该对象锁住,并且直到你提交了所作的更改之后才释放锁。
乐观锁:假设不会发生并发冲突,直到你准备提交所作的更改时才将对象锁住,当你读取以及改变该对象时并不加锁。但是如果第二个用户恰好在第一个用户提交更改之前读取了该对象,那么当他完成了自己的更改进行提交时,数据库就会发现该对象已经变化了,这样,第二个用户不得不重新读取该对象并作出更改,才能保证完整性。这说明在乐观锁环境中,会增加并发用户读取对象的次数。
乐观锁与悲观锁的区别
JOIN用于按照条件联接两个表,主要有四种:
UNION运算符
它实际上是一个很长的二进制向量和一系列随机映射函数(Hash函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。Bloom Filter广泛的应用于各种需要查询的场合中,如Orocle的数据库,Google的BitTable也用了此技术。
Bloom Filter特点:
C: C程序设计语言(K&R)->C和指针->C专家编程->C陷阱与缺陷->你必须知道的495个C语言问题
C++: C++ primer -> effective C++->深度探索C++对象模型 ->stl源码分析->C++必知必会
java:java编程思想->java并发编程->深入理解Java虚拟机:JVM高级特性与最佳实践
算法导论->数据结构与算法分析(维斯)->编程之美->剑指offer
深入理解计算机操作系统->编译原理(龙书)
鸟哥的linux私房菜->linux内核设计与实现->深入理解linux内核
linux shell脚本攻略(短小精悍)
TCP/IP协议详解v1->
unix高级环境编程->
unix网络编程(卷1&卷2)->
unix编程艺术(进阶)
大型网站技术架构:核心原理与案例分析,
深入理解nginx:模块开发与架构解析,
大规模分布式存储系统 : 原理解析与架构实战
程序员自我修养,
重构,
编写可读代码的艺术,
headfirst设计模式
Coolshell: http://coolshell.cn/
Matrix67大牛的博客: http://www.matrix67.com/blog/。
July的CSDN博客: http://blog.csdn.net/v_JULY_v。
何海涛博客: http://zhedahht.blog.163.com/。
笔试面试经典问题与解答: http://hawstein.com/posts/ctci-solutions-contents.html
LeetCode: http://leetcode.com/
这里有不少笔试题集锦: http://blog.csdn.net/hackbuteer1
程序员编程艺术:面试和算法心得 http://taop.marchtea.com/