后端开发面试题 =================== #后端开发面试知识点大纲: ##语言类(C++): ###关键字做用解释: volatile做用 Volatile关键词的第一个特性:易变性。所谓的易变性,在汇编层面反映出来,就是两条语句,下一条语句不会直接使用上一条语句对应的volatile变量的寄存器内容,而是从新从内存中读取。 Volatile关键词的第二个特性:“不可优化”特性。volatile告诉编译器,不要对我这个变量进行各类激进的优化,甚至将变量直接消除,保证程序员写在代码中的指令,必定会被执行。 Volatile关键词的第三个特性:”顺序性”,可以保证Volatile变量间的顺序性,编译器不会进行乱序优化。 C/C++ Volatile变量,与非Volatile变量之间的操做,是可能被编译器交换顺序的。C/C++ Volatile变量间的操做,是不会被编译器交换顺序的。哪怕将全部的变量所有都声明为volatile,哪怕杜绝了编译器的乱序优化,可是针对生成的汇编代码,CPU有可能仍旧会乱序执行指令,致使程序依赖的逻辑出错,volatile对此无能为力 针对这个多线程的应用,真正正确的作法,是构建一个happens-before语义。 [C/C++ Volatile关键词深度剖析](http://hedengcheng.com/?p=725) static 控制变量的存储方式和可见性。 (1)修饰局部变量 通常状况下,对于局部变量是存放在栈区的,而且局部变量的生命周期在该语句块执行结束时便结束了。可是若是用static进行修饰的话,该变量便存放在静态数据区,其生命周期一直持续到整个程序执行结束。可是在这里要注意的是,虽然用static对局部变量进行修饰事后,其生命周期以及存储空间发生了变化,可是其做用域并无改变,其仍然是一个局部变量,做用域仅限于该语句块。 (2)修饰全局变量 对于一个全局变量,它既能够在本源文件中被访问到,也能够在同一个工程的其它源文件中被访问(只需用extern进行声明便可)。用static对全局变量进行修饰改变了其做用域的范围,由原来的整个工程可见变为本源文件可见。 (3)修饰函数 用static修饰函数的话,状况与修饰全局变量大同小异,就是改变了函数的做用域。 (4)C++中的static 若是在C++中对类中的某个函数用static进行修饰,则表示该函数属于一个类而不是属于此类的任何特定对象;若是对类中的某个变量进行static修饰,表示该变量为类以及其全部的对象全部。它们在存储空间中都只存在一个副本。能够经过类和对象去调用。 const的含义及实现机制 const名叫常量限定符,用来限定特定变量,以通知编译器该变量是不可修改的。习惯性的使用const,能够避免在函数中对某些不该修改的变量形成可能的改动。 (1)const修饰基本数据类型 1.const修饰通常常量及数组 基本数据类型,修饰符const能够用在类型说明符前,也能够用在类型说明符后,其结果是同样的。在使用这些常量的时候,只要不改变这些常量的值便好。 2.const修饰指针变量*及引用变量& 若是const位于星号*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量; 若是const位于星号的右侧,const就是修饰指针自己,即指针自己是常量。 (2)const应用到函数中, 1.做为参数的const修饰符 调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,保护了原对象的属性。 [注意]:参数const一般用于参数为指针或引用的状况; 2.做为函数返回值的const修饰符 声明了返回值后,const按照"修饰原则"进行修饰,起到相应的保护做用。 (3)const在类中的用法 不能在类声明中初始化const数据成员。正确的使用const实现方法为:const数据成员的初始化只能在类构造函数的初始化表中进行 类中的成员函数:A fun4()const; 其意义上是不能修改所在类的的任何变量。 (4)const修饰类对象,定义常量对象 常量对象只能调用常量函数,别的成员函数都不能调用。 http://www.cnblogs.com/wintergrass/archive/2011/04/15/2015020.html extern 在C语言中,修饰符extern用在变量或者函数的声明前,用来讲明“此变量/函数是在别处定义的,要在此处引用”。 注意extern声明的位置对其做用域也有关系,若是是在main函数中进行声明的,则只能在main函数中调用,在其它函数中不能调用。其实要调用其它文件中的函数和变量,只需把该文件用#include包含进来便可,为啥要用extern?由于用extern会加速程序的编译过程,这样能节省时间。 在C++中extern还有另一种做用,用于指示C或者C++函数的调用规范。好比在C++中调用C库函数,就须要在C++程序中用extern “C”声明要引用的函数。这是给连接器用的,告诉连接器在连接的时候用C函数规范来连接。主要缘由是C++和C程序编译完成后在目标代码中命名规则不一样,用此来解决名字匹配的问题。 宏定义和展开、内联函数区别, 内联函数是代码被插入到调用者代码处的函数。如同 #define 宏,内联函数经过避免被调用的开销来提升执行效率,尤为是它可以经过调用(“过程化集成”)被编译器优化。 宏定义不检查函数参数,返回值什么的,只是展开,相对来讲,内联函数会检查参数类型,因此更安全。 内联函数和宏很相似,而区别在于,宏是由预处理器对宏进行替代,而内联函数是经过编译器控制来实现的。并且内联函数是真正的函数,只是在须要用到的时候,内联函数像宏同样的展开,因此取消了函数的参数压栈,减小了调用的开销。 宏是预编译器的输入,而后宏展开以后的结果会送去编译器作语法分析。宏与函数等处于不一样的级别,操做不一样的实体。宏操做的是 token, 能够进行 token的替换和链接等操做,在语法分析以前起做用。而函数是语言中的概念,会在语法树中建立对应的实体,内联只是函数的一个属性。 对于问题:有了函数要它们何用?答案是:一:函数并不能彻底替代宏,有些宏能够在当前做用域生成一些变量,函数作不到。二:内联函数只是函数的一种,内联是给编译器的提示,告诉它最好把这个函数在被调用处展开,省掉一个函数调用的开销(压栈,跳转,返回) 内联函数也有必定的局限性。就是函数中的执行代码不能太多了,若是,内联函数的函数体过大,通常的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率同样 内联函数必须是和函数体申明在一块儿,才有效。 [宏定义和内联函数区别](http://www.cnblogs.com/chengxuyuancc/archive/2013/04/04/2999844.html) ###库函数实现: malloc,strcpy,strcmp的实现,经常使用库函数实现,哪些库函数属于高危函数 ###STL原理及实现: STL各种型容器实现,STL共有六大组件 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(仿函数) 序列式容器: vector-数组,元素不够时再从新分配内存,拷贝原来数组的元素到新分配的数组中。 list-单链表。 deque-分配中央控制器map(并不是map容器),map记录着一系列的固定长度的数组的地址.记住这个map仅仅保存的是数组的地址,真正的数据在数组中存放着.deque先从map中央的位置(由于双向队列,先后均可以插入元素)找到一个数组地址,向该数组中放入数据,数组不够时继续在map中找空闲的数组来存数据。当map也不够时从新分配内存看成新的map,把原来map中的内容copy的新map中。因此使用deque的复杂度要大于vector,尽可能使用vector。 stack-基于deque。 queue-基于deque。 heap-彻底二叉树,使用最大堆排序,以数组(vector)的形式存放。 priority_queue-基于heap。 slist-双向链表。 关联式容器: set,map,multiset,multimap-基于红黑树(RB-tree),一种加上了额外平衡条件的二叉搜索树。 hash table-散列表。将待存数据的key通过映射函数变成一个数组(通常是vector)的索引,例如:数据的key%数组的大小=数组的索引(通常文本经过算法也能够转换为数字),而后将数据看成此索引的数组元素。有些数据的key通过算法的转换多是同一个数组的索引值(碰撞问题,能够用线性探测,二次探测来解决),STL是用开链的方法来解决的,每个数组的元素维护一个list,他把相同索引值的数据存入一个list,这样当list比较短时执行删除,插入,搜索等算法比较快。 hash_map,hash_set,hash_multiset,hash_multimap-基于hashtable。 [STL六大组件] (http://blog.csdn.net/chenguolinblog/article/details/30336805) 什么是“标准非STL容器”? list和vector有什么区别? vector拥有一段连续的内存空间,所以支持随机存取,若是须要高效的随即存取,而不在意插入和删除的效率,使用vector。 list拥有一段不连续的内存空间,所以不支持随机存取,若是须要大量的插入和删除,而不关心随即存取,则应使用list。 ###虚函数: 虚函数的做用和实现原理,什么是虚函数,有什么做用? C++的多态分为静态多态(编译时多态)和动态多态(运行时多态)两大类。静态多态经过重载、模板来实现;动态多态就是经过本文的主角虚函数来体现的。 虚函数实现原理:包括虚函数表、虚函数指针等 虚函数的做用说白了就是:当调用一个虚函数时,被执行的代码必须和调用函数的对象的动态类型相一致。编译器须要作的就是如何高效的实现提供这种特性。不一样编译器实现细节也不相同。大多数编译器经过vtbl(virtual table)和vptr(virtual table pointer)来实现的。 当一个类声明了虚函数或者继承了虚函数,这个类就会有本身的vtbl。vtbl实际上就是一个函数指针数组,有的编译器用的是链表,不过方法都是差很少。vtbl数组中的每个元素对应一个函数指针指向该类的一个虚函数,同时该类的每个对象都会包含一个vptr,vptr指向该vtbl的地址。 结论: 每一个声明了虚函数或者继承了虚函数的类,都会有一个本身的vtbl 同时该类的每一个对象都会包含一个vptr去指向该vtbl 虚函数按照其声明顺序放于vtbl表中, vtbl数组中的每个元素对应一个函数指针指向该类的虚函数 若是子类覆盖了父类的虚函数,将被放到了虚表中原来父类虚函数的位置 在多继承的状况下,每一个父类都有本身的虚表。子类的成员函数被放到了第一个父类的表中 衍生问题:为何 C++里访问虚函数比访问普通函数慢? 单继承时性能差很少,多继承的时候会慢 调用性能方面 从前面虚函数的调用过程可知。当调用虚函数时过程以下(引自More Effective C++): 经过对象的 vptr 找到类的 vtbl。这是一个简单的操做,由于编译器知道在对象内 哪里能找到 vptr(毕竟是由编译器放置的它们)。所以这个代价只是一个偏移调整(以获得 vptr)和一个指针的间接寻址(以获得 vtbl)。 找到对应 vtbl 内的指向被调用函数的指针。这也是很简单的, 由于编译器为每一个虚函数在 vtbl 内分配了一个惟一的索引。这步的代价只是在 vtbl 数组内 的一个偏移。 调用第二步找到的的指针所指向的函数。 在单继承的状况下,调用虚函数所需的代价基本上和非虚函数效率同样,在大多数计算机上它多执行了不多的一些指令,因此有不少人一律而论说虚函数性能不行是不太科学的。在多继承的状况下,因为会根据多个父类生成多个vptr,在对象里为寻找 vptr 而进行的偏移量计算会变得复杂一些,但这些并非虚函数的性能瓶颈。 虚函数运行时所需的代价主要是虚函数不能是内联函。这也是很是好理解的,是由于内联函数是指在编译期间用被调用的函数体自己来代替函数调用的指令,可是虚函数的“虚”是指“直到运行时才能知道要调用的是哪个函数。”但虚函数的运行时多态特性就是要在运行时才知道具体调用哪一个虚函数,因此无法在编译时进行内联函数展开。固然若是经过对象直接调用虚函数它是能够被内联,可是大多数虚函数是经过对象的指针或引用被调用的,这种调用不能被内联。 由于这种调用是标准的调用方式,因此虚函数实际上不能被内联。 占用空间方面 在上面的虚函数实现原理部分,能够看到为了实现运行时多态机制,编译器会给每个包含虚函数或继承了虚函数的类自动创建一个虚函数表,因此虚函数的一个代价就是会增长类的体积。在虚函数接口较少的类中这个代价并不明显,虚函数表vtbl的体积至关于几个函数指针的体积,若是你有大量的类或者在每一个类中有大量的虚函数,你会发现 vtbl 会占用大量的地址空间。但这并非最主要的代价,主要的代价是发生在类的继承过程当中,在上面的分析中,能够看到,当子类继承父类的虚函数时,子类会有本身的vtbl,若是子类只覆盖父类的一两个虚函数接口,子类vtbl的其他部份内容会与父类重复。这在若是存在大量的子类继承,且重写父类的虚函数接口只占总数的一小部分的状况下,会形成大量地址空间浪费。在一些GUI库上这种大量子类继承自同一父类且只覆盖其中一两个虚函数的状况是常常有的,这样就致使UI库的占用内存明显变大。 因为虚函数指针vptr的存在,虚函数也会增长该类的每一个对象的体积。在单继承或没有继承的状况下,类的每一个对象会多一个vptr指针的体积,也就是4个字节;在多继承的状况下,类的每一个对象会多N个(N=包含虚函数的父类个数)vptr的体积,也就是4N个字节。当一个类的对象体积较大时,这个代价不是很明显,但当一个类的对象很轻量的时候,如成员变量只有4个字节,那么再加上4(或4N)个字节的vptr,对象的体积至关于翻了1(或N)倍,这个代价是很是大的。 [C++虚函数浅析](http://glgjing.github.io/blog/2015/01/03/c-plus-plus-xu-han-shu-qian-xi/) 纯虚函数,为何须要纯虚函数? 纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义本身的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0” virtual void funtion1()=0 缘由: 一、为了方便使用多态特性,咱们经常须要在基类中定义虚拟函数。 二、在不少状况下,基类自己生成对象是不合情理的。例如,动物做为一个基类能够派生出老虎、孔雀等子类,但动物自己生成对象明显不合常理。 为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。声明了纯虚函数的类是一个抽象类。因此,用户不能建立类的实例,只能建立它的派生类的实例。 定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。 纯虚函数的意义,让全部的类对象(主要是派生类对象)均可以执行纯虚函数的动做,但类没法为纯虚函数提供一个合理的缺省实现。因此类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。 [虚函数和纯虚函数的区别](http://blog.csdn.net/hackbuteer1/article/details/7558868) 为何须要虚析构函数,何时不须要?父类的析构函数为何要定义为虚函数 通常状况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会形成内存泄漏。这样作是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。 固然,并非要把全部类的析构函数都写成虚函数。由于当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增长类的存储空间。因此,只有当一个类被用来做为基类的时候,才把析构函数写成虚函数。 内联函数、构造函数、静态成员函数能够是虚函数吗? inline, static, constructor三种函数都不能带有virtual关键字。 inline是编译时展开,必须有实体; static属于class本身的,也必须有实体; virtual函数基于vtable(内存空间),constructor函数若是是virtual的,调用时也须要根据vtable寻找,可是constructor是virtual的状况下是找不到的,由于constructor本身自己都不存在了,建立不到class的实例,没有实例,class的成员(除了public static/protected static for friend class/functions,其他不管是否virtual)都不能被访问了。 虚函数实际上不能被内联:虚函数运行时所需的代价主要是虚函数不能是内联函。这也是很是好理解的,是由于内联函数是指在编译期间用被调用的函数体自己来代替函数调用的指令,可是虚函数的“虚”是指“直到运行时才能知道要调用的是哪个函数。”但虚函数的运行时多态特性就是要在运行时才知道具体调用哪一个虚函数,因此无法在编译时进行内联函数展开。固然若是经过对象直接调用虚函数它是能够被内联,可是大多数虚函数是经过对象的指针或引用被调用的,这种调用不能被内联。 由于这种调用是标准的调用方式,因此虚函数实际上不能被内联。 构造函数不能是虚函数。并且,在构造函数中调用虚函数,实际执行的是父类的对应函数,由于本身尚未构造好, 多态是被disable的。 静态的对象是属于整个类的,不对某一个对象而言,同时其函数的指针存放也不一样于通常的成员函数,其没法成为一个对象的虚函数的指针以实现由此带来的动态机制。 构造函数中能够调用虚函数吗? 最后,总结一下关于虚函数的一些常见问题: 1) 虚函数是动态绑定的,也就是说,使用虚函数的指针和引用可以正确找到实际类的对应函数,而不是执行定义类的函数。这是虚函数的基本功能,就再也不解释了。 2) 构造函数不能是虚函数。并且,在构造函数中调用虚函数,实际执行的是父类的对应函数,由于本身尚未构造好, 多态是被disable的。 3) 析构函数能够是虚函数,并且,在一个复杂类结构中,这每每是必须的。 4) 将一个函数定义为纯虚函数,其实是将这个类定义为抽象类,不能实例化对象。 5) 纯虚函数一般没有定义体,但也彻底能够拥有。 6) 析构函数能够是纯虚的,但纯虚析构函数必须有定义体,由于析构函数的调用是在子类中隐含的。 7) 非纯的虚函数必须有定义体,否则是一个错误。 8) 派生类的override虚函数定义必须和父类彻底一致。除了一个特例,若是父类中返回值是一个指针或引用,子类override时能够返回这个指针(或引用)的派生。例如,在上面的例子中,在Base中定义了 virtual Base* clone(); 在Derived中能够定义为 virtual Derived* clone()。能够看到,这种放松对于Clone模式是很是有用的。 [虚析构函数(√)、纯虚析构函数(√)、虚构造函数(X)](http://www.cnblogs.com/chio/archive/2007/09/10/888260.html) 为何须要虚继承?虚继承实现原理解析, 虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。 如:类D继承自类B一、B2,而类B一、B2都继 承自类A,所以在类D中两次出现类A中的变量和函数。为了节省内存空间,能够将B一、B2对A的继承定义为虚拟继承,而A就成了虚拟基类,虚拟继承在通常的应用中不多用到,因此也每每被忽视,这也主要是由于在C++中,多重继承是不推荐的,也并不经常使用,而一旦离开了多重继承,虚拟继承就彻底失去了存在的必要由于这样只会下降效率和占用更多的空间。 虚继承的特色是,在任何派生类中的virtual基类总用同一个(共享)对象表示, [C++虚拟继承](http://blog.csdn.net/hyg0811/article/details/11951855) ###设计模式: C++单例模式写法: 静态化并非单例 (Singleton) 模式: 第一, 静态成员变量初始化顺序不依赖构造函数, 得看编译器心情的, 无法保证初始化顺序 (极端状况: 有 a b 两个成员对象, b 须要把 a 做为初始化参数传入, 你的类就 必须 得要有构造函数, 并确保初始化顺序). 第二, 最严重的问题, 失去了面对对象的重要特性 -- "多态", 静态成员方法不多是 virtual 的. Log 类的子类无法享受 "多态" 带来的便利. class Log { public: static void Write(char const *logline); static bool SaveTo(char const *filename); private: static std::list<std::string> m_data; }; In log.cpp we need to add std::list<std::string> Log::m_data; 饿汉模式: 饿汉模式 是指单例实例在程序运行时被当即执行初始化: 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<std::string> 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<std::string> m_data; }; // in log.cpp we have to add Log* Log::m_pInstance = NULL; Instance() 只在第一次被调用时为 m_pInstance 分配内存并初始化. 嗯, 看上去全部的问题都解决了, 初始化顺序有保证, 多态也没问题. 程序退出时, 析构函数没被执行. 这在某些设计不可靠的系统上会致使资源泄漏, 好比文件句柄, socket 链接, 内存等等 对于这个问题, 比较土的解决方法是, 给每一个 Singleton 类添加一个 destructor() 方法: 懒汉模式 (局部静态变量-最佳版) 它也被称为 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<std::string> m_data; }; 在 Instance() 函数内定义局部静态变量的好处是, theLog `` 的构造函数只会在第一次调用 ``Instance() 时被初始化, 达到了和 "堆栈版" 相同的动态初始化效果, 保证了成员变量和 Singleton 自己的初始化顺序. 它还有一个潜在的安全措施, Instance() 返回的是对局部静态变量的引用, 若是返回的是指针, Instance() 的调用者极可能会误认为他要检查指针的有效性, 并负责销毁. 构造函数和拷贝构造函数也私有化了, 这样类的使用者不能自行实例化. 另外, 多个不一样的 Singleton 实例的析构顺序与构造顺序相反. [C++ Singleton (单例) 模式最优实现](http://blog.yangyubo.com/2009/06/04/best-cpp-singleton-pattern/) 用C++设计一个不能被继承的类。 构造函数或析构函数为私有函数,因此该类是没法被继承的, 如何定义一个只能在堆上定义对象的类?栈上呢 只能在堆内存上实例化的类:将析构函数定义为private,在栈上不能自动调用析构函数,只能手动调用。也能够将构造函数定义为private,但这样须要手动写一个函数实现对象的构造。 只能在栈内存上实例化的类:将函数operator new和operator delete定义为private,这样使用new操做符建立对象时候,没法调用operator new,delete销毁对象也没法调用operator delete。 [设计一个只能在堆上或栈上实例化的类](http://www.cnblogs.com/luxiaoxun/archive/2012/08/03/2621827.html) 知足上述3个条件 [C++中的单例模式](http://www.cnblogs.com/xiehongfeng100/p/4781013.html) 多重类构造和析构的顺序 先调用基类的构造函数,在调用派生类的构造函数 先构造的后析构,后构造的先析构 ###内存分配: 内存分配方式有三种: (1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。 (2)在栈上建立。在执行函数时,函数内局部变量的存储单元均可以在栈上建立,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,可是分配的内存容量有限。 (3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员本身负责在什么时候用free或delete释放内存。动态内存的生存期由咱们决定,使用很是灵活,但问题也最多。 c++运行时各种型内存分配(堆,栈,静态区,数据段,BSS,ELF),BSS段, sizeof一个类求大小(字节对齐原则)、 C++四种强制类型转换, int char float,long long long类型长度 ###指针: 防止指针的越界使用, 必须让指针指向一个有效的内存地址, 1 防止数组越界 2 防止向一块内存中拷贝过多的内容 3 防止使用空指针 4 防止改变const修改的指针 5 防止改变指向静态存储区的内容 6 防止两次释放一个指针 7 防止使用野指针. 什么是指针退化及防止、 若是用一个数组做为函数入参 好比 void fun(char a[100]) { cout<<SIZEOF(A)< } 指针的移动问题, 指针P ++具体移动的字节数等于指针指向的变量类型大小. Const,volatile修饰指针的含义, 堆和栈上的指针, 指针所指向的这块内存是在哪里分配的,在堆上称为堆上的指针,在栈上为栈上的指针. 在堆上的指针,能够保存在全局数据结构中,供不一样函数使用访问同一块内存. 在栈上的指针,在函数退出后,该内存即不可访问. 指针的释放及内存泄露缘由, 指针做为函数的参数,函数指针, 指针和引用及地址的区别,数组名, 指针与地址的区别? 区别: 1指针意味着已经有一个指针变量存在,他的值是一个地址,指针变量自己也存放在一个长度为四个字节的地址当中,而地址概念自己并不表明有任何变量存在. 2 指针的值,若是没有限制,一般是能够变化的,也能够指向另一个地址. 地址表示内存空间的一个位置点,他是用来赋给指针的,地址自己是没有大小概念,指针指向变量的大小,取决于地址后面存放的变量类型. 指针与数组名的关系? 其值都是一个地址,但前者是能够移动的,后者是不可变的. 指针和引用的区别(通常都会问到) 相同点:1. 都是地址的概念; 指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。 区别:1. 指针是一个实体,而引用仅是个别名; 2. 引用使用时无需解引用(*),指针须要解引用; 3. 引用只能在定义时被初始化一次,以后不可变;指针可变; 4. 引用没有 const,指针有 const; 5. 引用不能为空,指针能够为空; 6. “sizeof 引用”获得的是所指向的变量(对象)的大小,而“sizeof 指针”获得的是指针自己(所指向的变量或对象的地址)的大小; 7. 指针和引用的自增(++)运算意义不同; 8.从内存分配上看:程序为指针变量分配内存区域,而引用不须要分配内存区域。 迭代器与普通指针有什么区别 智能指针的原理, 智能指针:实际指行为相似于指针的类对象 ,它的一种通用实现方法是采用引用计数的方法。 1.智能指针将一个计数器与类指向的对象相关联,引用计数跟踪共有多少个类对象共享同一指针。 2.每次建立类的新对象时,初始化指针并将引用计数置为1; 3.当对象做为另外一对象的副本而建立时,拷贝构造函数拷贝指针并增长与之相应的引用计数; 4.对一个对象进行赋值时,赋值操做符减小左操做数所指对象的引用计数(若是引用计数为减至0,则删除对象),并增长右操做数所指对象的引用计数;这是由于左侧的指针指向了右侧指针所指向的对象,所以右指针所指向的对象的引用计数+1; 5.调用析构函数时,构造函数减小引用计数(若是引用计数减至0,则删除基础对象)。 6.实现智能指针有两种经典策略:一是引入辅助类,二是使用句柄类。这里主要讲一下引入辅助类的方法 其余:override和overload的区别, override(重写) 一、方法名、参数、返回值相同。 二、子类方法不能缩小父类方法的访问权限。 三、子类方法不能抛出比父类方法更多的异常(但子类方法能够不抛出异常)。 四、存在于父类和子类之间。 五、方法被定义为final不能被重写。 overload(重载) 一、参数类型、个数、顺序至少有一个不相同。 二、不能重载只有返回值不一样的方法名。 三、存在于父类和子类、同类中。 Overload是重载的意思,Override是覆盖的意思,也就是重写。 重载Overload表示同一个类中能够有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不一样)。 重写Override表示子类中的方法能够与父类中的某个方法的名称和参数彻底相同,经过子类建立的实例对象调用这个方法时,将调用子类中的定义方法,这至关于把父类中定义的那个彻底相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。 子类覆盖父类的方法时,只能比父类抛出更少的异常,或者是抛出父类抛出的异常的子异常,由于子类能够解决父类的一些问题,不能比父类有更多的问题。子类方法的访问权限只能比父类的更大,不能更小。若是父类的方法是private类型,那么,子类则不存在覆盖的限制,至关于子类中增长了一个全新的方法。 写string类的构造,析构,拷贝函数 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(); } 1 指针的四要素 1指针变量,表示一个内存地址,一般为逻辑地址,与实际的物理地址还有一个映射关系. 2指针变量的长度,在WIN32下为四个字节, 3指针指向的变量 该内存地址空间下存放的变量,具体内容多是各类类型的变量. 4 指针指向的变量的长度,以该内存地址空间开始的内存空间大小. ##数据结构算法: 链表、树、哈希表、有效避免hash结果值的碰撞 排序算法性能比较 ##操做系统: linux的内存管理机制,内存寻址方式,什么叫虚拟内存,内存调页算法,任务调度算法、 Linux虚拟内存的实现须要6种机制的支持:地址映射机制、内存分配回收机制、缓存和刷新机制、请求页机制、交换机制和内存共享机制 内存管理程序经过映射机制把用户程序的逻辑地址映射到物理地址。当用户程序运行时,若是发现程序中要用的虚地址没有对应的物理内存,就发出了请求页要求。若是有空闲的内存可供分配,就请求分配内存(因而用到了内存的分配和回收),并把正在使用的物理页记录在缓存中(使用了缓存机制)。若是没有足够的内存可供分配,那么就调用交换机制;腾出一部份内存。另外,在地址映射中要经过TLB(翻译后援存储器)来寻找物理页;交换机制中也要用到交换缓存,而且把物理页内容交换到交换文件中,也要修改页表来映射文件地址。 进程和线程、进程间及线程通讯方式、共享内存的使用实现原理 死锁必要条件及避免算法、 一、资源不能共享,只能由一个进程使用。 二、请求与保持(Hold andwait):已经获得资源的进程能够再次申请新的资源。 三、不可剥夺(Nopre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。 四、循环等待:系统中若干进程组成环路,该环路中每一个进程都在等待相邻进程正占用的资源 处理死锁的策略:1.忽略该问题。例如鸵鸟算法,该算法能够应用在极少发生死锁的的状况下。为何叫鸵鸟算法呢,由于传说中鸵鸟看到危险就把头埋在地底下,可能鸵鸟以为看不到危险也就没危险了吧。跟掩耳盗铃有点像。2.检测死锁而且恢复。3.仔细地对资源进行动态分配,以免死锁。4.经过破除死锁四个必要条件之一,来防止死锁产生。) 动态连接和静态连接的区别、 动态连接是只创建一个引用的接口,而真正的代码和数据存放在另外的可执行模块中,在运行时再装入;而静态连接是把全部的代码和数据都复制到本模块中,运行时就再也不须要库了。 c程序辨别系统是16位or32位,大端or小端字节序、 16or32 法一: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; 大or小 1) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。 2) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。 举一个例子,好比数字0x12 34 56 78在内存中的表示形式为: 1)大端模式: 低地址 -----------------> 高地址 0x12 | 0x34 | 0x56 | 0x78 2)小端模式: 低地址 ------------------> 高地址 0x78 | 0x56 | 0x34 | 0x12 32bit宽的数0x12345678在Little-endian模式以及Big-endian模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为: 内存地址 小端模式存放内容 大端模式存放内容 0x4000 0x78 0x12 0x4001 0x56 0x34 0x4002 0x34 0x56 0x4003 0x12 0x78 4)大端小端没有谁优谁劣,各自优点即是对方劣势: 小端模式 :强制转换数据不须要调整字节内容,一、二、4字节的存储方式同样。 大端模式 :符号位的断定固定为第一个字节,容易判断正负。 BOOL IsBigEndian() { int a = 0x1234; char b = *(char *)&a; //经过将int强制类型转换成char单字节,经过判断起始存储位置。即等于 取b等于a的低地址部分 if( b == 0x12) { return TRUE; } return FALSE; } 联合体union的存放顺序是全部成员都从低地址开始存放,利用该特性能够轻松地得到了CPU对内存采用Little-endian仍是Big-endian模式读写: BOOL IsBigEndian() { union NUM { int a; char b; }num; num.a = 0x1234; if( num.b == 0x12 ) { return TRUE; } return FALSE; } 通常操做系统都是小端,而通信协议是大端的。 常见CPU的字节序 Big Endian : PowerPC、IBM、Sun Little Endian : x8六、DEC ARM既能够工做在大端模式,也能够工做在小端模式。 常见的信号、系统如何将一个信号通知到进程、 信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称做软中断。 进程之间能够互相经过系统调用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 管道破裂: 写一个没有读端口的管道 信号机制是异步的;当一个进程接收到一个信号时,它会马上处理这个信号,而不会等待当前函数甚至当前一行代码结束运行。信号有几十种,分别表明着不一样的意义。信号之间依靠它们的值来区分,可是一般在程序中使用信号的名字来表示一个信号。在Linux系统中,这些信号和以它们的名称命名的常量均定义在/usr/include/bits/signum.h文件中。(一般程序中不须要直接包含这个头文件,而应该包含<signal.h>。) 信号事件的发生有两个来源:硬件来源(好比咱们按下了键盘或者其它硬件故障);软件来源,最经常使用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操做。 发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。 进程能够经过三种方式来响应一个信号:(1)忽略信号,即对信号不作任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;(3)执行缺省操做, linux系统的各种同步机制、linux系统的各种异步机制、 如何实现守护进程 守护进程最重要的特性是后台运行。 1. 在后台运行。 为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。 if(pid=fork()) exit(0); //是父进程,结束父进程,子进程继续 2. 脱离控制终端,登陆会话和进程组 有必要先介绍一下Linux中的进程与控制终端,登陆会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登陆会话能够包含多个进程组。这些进程组共享一个控制终端。这个控制终端一般是建立进程的登陆终端。控制终端,登陆会话和进程组一般是从父进程继承下来的。咱们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长: setsid(); 说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登陆会话和进程组脱离。因为会话过程对控制终端的独占性,进程同时与控制终端脱离。 3. 禁止进程从新打开控制终端 如今,进程已经成为无终端的会话组长。但它能够从新申请打开一个控制终端。能够经过使进程再也不成为会话组长来禁止进程从新打开控制终端: if(pid=fork()) exit(0); //结束第一子进程,第二子进程继续(第二子进程再也不是会话组长) 4. 关闭打开的文件描述符 进程从建立它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,形成进程所在的文件系统没法卸下以及引发没法预料的错误。按以下方法关闭它们: for(i=0;i 关闭打开的文件描述符close(i);> 5. 改变当前工做目录 进程活动时,其工做目录所在的文件系统不能卸下。通常须要将工做目录改变到根目录。对于须要转储核心,写运行日志的进程将工做目录改变到特定目录如 /tmpchdir("/") 6. 重设文件建立掩模 进程从建立它的父进程那里继承了文件建立掩模。它可能修改守护进程所建立的文件的存取位。为防止这一点,将文件建立掩模清除:umask(0); 7. 处理SIGCHLD信号 处理SIGCHLD信号并非必须的。但对于某些进程,特别是服务器进程每每在请求到来时生成子进程处理请求。若是父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。若是父进程等待子进程结束,将增长父进程的负担,影响服务器进程的并发性能。在Linux下能够简单地将 SIGCHLD信号的操做设为SIG_IGN。 signal(SIGCHLD,SIG_IGN); 这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不一样,BSD4下必须显式等待子进程结束才能释放僵尸进程。 标准库函数和系统调用的区别, 一、系统调用 系统调用提供的函数如open, close, read, write, ioctl等,需包含头文件unistd.h。以write为例:其函数原型为 size_t write(int fd, const void *buf, size_t nbytes),其操做对象为文件描述符或文件句柄fd(file descriptor),要想写一个文件,必须先以可写权限用open系统调用打开一个文件,得到所打开文件的fd,例如fd=open(/"/dev/video/", O_RDWR)。fd是一个整型值,每新打开一个文件,所得到的fd为当前最大fd加1。Linux系统默认分配了3个文件描述符值:0-standard input,1-standard output,2-standard error。 系统调用一般用于底层文件访问(low-level file access),例如在驱动程序中对设备文件的直接访问。 系统调用是操做系统相关的,所以通常没有跨操做系统的可移植性。 系统调用发生在内核空间,所以若是在用户空间的通常应用程序中使用系统调用来进行文件操做,会有用户空间到内核空间切换的开销。事实上,即便在用户空间使用库函数来对文件进行操做,由于文件老是存在于存储介质上,所以无论是读写操做,都是对硬件(存储器)的操做,都必然会引发系统调用。也就是说,库函数对文件的操做其实是经过系统调用来实现的。例如C库函数fwrite()就是经过write()系统调用来实现的。 这样的话,使用库函数也有系统调用的开销,为何不直接使用系统调用呢?这是由于,读写文件一般是大量的数据(这种大量是相对于底层驱动的系统调用所实现的数据操做单位而言),这时,使用库函数就能够大大减小系统调用的次数。这一结果又缘于缓冲区技术。在用户空间和内核空间,对文件操做都使用了缓冲区,例如用fwrite写文件,都是先将内容写到用户空间缓冲区,当用户空间缓冲区满或者写操做结束时,才将用户缓冲区的内容写到内核缓冲区,一样的道理,当内核缓冲区满或写结束时才将内核缓冲区内容写到文件对应的硬件媒介。 二、库函数调用 标准C库函数提供的文件操做函数如fopen, fread, fwrite, fclose,fflush, fseek等,需包含头文件stdio.h。以fwrite为例,其函数原型为size_t fwrite(const void *buffer,size_t size, size_t item_num, FILE *pf),其操做对象为文件指针FILE *pf,要想写一个文件,必须先以可写权限用fopen函数打开一个文件,得到所打开文件的FILE结构指针pf,例如pf=fopen(/"~/proj/filename/",/"w/")。实际上,因为库函数对文件的操做最终是经过系统调用实现的,所以,每打开一个文件所得到的FILE结构指针都有一个内核空间的文件描述符fd与之对应。一样有相应的预约义的FILE指针:stdin-standard input,stdout-standard output,stderr-standard error。 库函数调用一般用于应用程序中对通常文件的访问。 库函数调用是系统无关的,所以可移植性好。 因为库函数调用是基于C库的,所以也就不可能用于内核空间的驱动程序中对设备的操做 fd和PCB, 32位系统一个进程最多有多少堆内存, 五种I/O 模式, 五种I/O 模式: 【1】 阻塞I/O (Linux下的I/O操做默认是阻塞I/O,即open和socket建立的I/O都是阻塞I/O) 【2】 非阻塞 I/O (能够经过fcntl或者open时使用O_NONBLOCK参数,将fd设置为非阻塞的I/O) 【3】 I/O 多路复用 (I/O多路复用,一般须要非阻塞I/O配合使用) 【4】 信号驱动 I/O (SIGIO) 【5】 异步 I/O Apache 模型(Process Per Connection,简称PPC),TPC(ThreadPer Connection)模型,以及 select 模型和 poll 模型,epoll模型 通常来讲,程序进行输入操做有两步: 1.等待有数据能够读 2.将数据从系统内核中拷贝到程序的数据区。 对于sock编程来讲: 第一步: 通常来讲是等待数据从网络上传到本地。当数据包到达的时候,数据将会从网络层拷贝到内核的缓存中; 第二步: 是从内核中把数据拷贝到程序的数据区中。 阻塞I/O模式 //进程处于阻塞模式时,让出CPU,进入休眠状态 阻塞 I/O 模式是最广泛使用的 I/O 模式。是Linux系统下缺省的IO模式。 大部分程序使用的都是阻塞模式的 I/O 。 一个套接字创建后所处于的模式就是阻塞 I/O 模式。(由于Linux系统默认的IO模式是阻塞模式) 对于一个UDP 套接字来讲,数据就绪的标志比较简单: (1)已经收到了一整个数据报 (2)没有收到。 而 TCP 这个概念就比较复杂,须要附加一些其余的变量。 一个进程调用 recvfrom ,而后系统调用并不返回知道有数据报到达本地系统,而后系统将数据拷贝到进程的缓存中。(若是系统调用收到一个中断信号,则它的调用会被中断) 咱们称这个进程在调用recvfrom一直到从recvfrom返回这段时间是阻塞的。当recvfrom正常返回时,咱们的进程继续它的操做 非阻塞模式I/O //非阻塞模式的使用并不广泛,由于非阻塞模式会浪费大量的CPU资源。 当咱们将一个套接字设置为非阻塞模式,咱们至关于告诉了系统内核: “当我请求的I/O 操做不可以立刻完成,你想让个人进程进行休眠等待的时候,不要这么作,请立刻返回一个错误给我。” 咱们开始对 recvfrom 的三次调用,由于系统尚未接收到网络数据,因此内核立刻返回一个EWOULDBLOCK的错误。 第四次咱们调用 recvfrom 函数,一个数据报已经到达了,内核将它拷贝到咱们的应用程序的缓冲区中,而后 recvfrom 正常返回,咱们就能够对接收到的数据进行处理了。 当一个应用程序使用了非阻塞模式的套接字,它须要使用一个循环来不听的测试是否一个文件描述符有数据可读(称作 polling(轮询))。应用程序不停的 polling 内核来检查是否 I/O操做已经就绪。这将是一个极浪费 CPU资源的操做。这种模式使用中不是很广泛。 例如: 对管道的操做,最好使用非阻塞方式! I/O多路复用 //针对批量IP操做时,使用I/O多路复用,很是有好。 在使用 I/O 多路技术的时候,咱们调用select()函数和 poll()函数或epoll函数(2.6内核开始支持),在调用它们的时候阻塞,而不是咱们来调用 recvfrom(或recv)的时候阻塞。 当咱们调用 select函数阻塞的时候,select 函数等待数据报套接字进入读就绪状态。当select函数返回的时候,也就是套接字能够读取数据的时候。这时候咱们就能够调用 recvfrom函数来将数据拷贝到咱们的程序缓冲区中。 对于单个I/O操做,和阻塞模式相比较,select()和poll()或epoll并无什么高级的地方。 并且,在阻塞模式下只须要调用一个函数: 读取或发送函数。 在使用了多路复用技术后,咱们须要调用两个函数了: 先调用 select()函数或poll()函数,而后才能进行真正的读写。 多路复用的高级之处在于:: 它能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就能够返回。 IO 多路技术通常在下面这些状况中被使用: 一、当一个客户端须要同时处理多个文件描述符的输入输出操做的时候(通常来讲是标准的输入输出和网络套接字),I/O 多路复用技术将会有机会获得使用。 二、当程序须要同时进行多个套接字的操做的时候。 三、若是一个 TCP 服务器程序同时处理正在侦听网络链接的套接字和已经链接好的套接字。 四、若是一个服务器程序同时使用 TCP 和 UDP 协议。 五、若是一个服务器同时使用多种服务而且每种服务可能使用不一样的协议(好比 inetd就是这样的)。 异步IO模式有:: 一、信号驱动I/O模式 二、异步I/O模式 信号驱动I/O模式 //本身没有用过。 咱们能够使用信号,让内核在文件描述符就绪的时候使用 SIGIO 信号来通知咱们。咱们将这种模式称为信号驱动 I/O 模式。 为了在一个套接字上使用信号驱动 I/O 操做,下面这三步是所必须的。 (1)一个和 SIGIO信号的处理函数必须设定。 (2)套接字的拥有者必须被设定。通常来讲是使用 fcntl 函数的 F_SETOWN 参数来 进行设定拥有者。 (3)套接字必须被容许使用异步 I/O。通常是经过调用 fcntl 函数的 F_SETFL 命令,O_ASYNC为参数来实现。 虽然设定套接字为异步 I/O 很是简单,可是使用起来困难的部分是怎样在程序中判定产生 SIGIO信号发送给套接字属主的时候,程序处在什么状态。 1.UDP 套接字的 SIGIO 信号 (比较简单) 在 UDP 协议上使用异步 I/O 很是简单.这个信号将会在这个时候产生: 一、套接字收到了一个数据报的数据包。 二、套接字发生了异步错误。 当咱们在使用 UDP 套接字异步 I/O 的时候,咱们使用 recvfrom()函数来读取数据报数据或是异步 I/O 错误信息。 2.TCP 套接字的 SIGIO 信号 (不会使用) 不幸的是,异步 I/O 几乎对 TCP 套接字而言没有什么做用。由于对于一个 TCP 套接字来讲,SIGIO 信号发生的概率过高了,因此 SIGIO 信号并不能告诉咱们究竟发生了什么事情。 在 TCP 链接中, SIGIO 信号将会在这个时候产生: l 在一个监听某个端口的套接字上成功的创建了一个新链接。 l 一个断线的请求被成功的初始化。 l 一个断线的请求成功的结束。 l 套接字的某一个通道(发送通道或是接收通道)被关闭。 l 套接字接收到新数据。 l 套接字将数据发送出去。 l 发生了一个异步 I/O 的错误。 一个对信号驱动 I/O 比较实用的方面是NTP(网络时间协议 Network TimeProtocol)服务器,它使用 UDP。这个服务器的主循环用来接收从客户端发送过来的数据报数据包,而后再发送请求。对于这个服务器来讲,记录下收到每个数据包的具体时间是很重要的。 由于那将是返回给客户端的值,客户端要使用这个数据来计算数据报在网络上来回所花费的时间。图 6-8 表示了怎样创建这样的一个 UDP 服务器。 异步I/O模式 //好比写操做,只需用写,不必定写入磁盘(这就是异步I/O)的好处。异步IO的好处效率高。 当咱们运行在异步 I/O 模式下时,咱们若是想进行 I/O 操做,只须要告诉内核咱们要进行 I/O 操做,而后内核会立刻返回。具体的 I/O 和数据的拷贝所有由内核来完成,咱们的程序能够继续向下执行。当内核完成全部的 I/O 操做和数据拷贝后,内核将通知咱们的程序。 异步 I/O 和 信号驱动I/O的区别是: 一、信号驱动 I/O 模式下,内核在操做能够被操做的时候通知给咱们的应用程序发送SIGIO 消息。 二、异步 I/O 模式下,内核在全部的操做都已经被内核操做结束以后才会通知咱们的应用程序。 select,poll,epoll . Epoll 是何方神圣? Epoll 但是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 类似,其实都 I/O 多路复用技术而已,并无什么神秘的。 其实在Linux 下设计并发网络程序,向来不缺乏方法,好比典型的 Apache 模型( Process Per Connection ,简称PPC ), TPC ( ThreadPer Connection )模型,以及 select 模型和 poll 模型,那为什么还要再引入 Epoll 这个东东呢?那仍是有得说说的 … 2. 经常使用模型的缺点 若是不摆出来其余模型的缺点,怎么能对比出 Epoll 的优势呢。 2.1 PPC/TPC 模型 这两种模型思想相似,就是让每个到来的链接一边本身作事去,别再来烦我。只是 PPC 是为它开了一个进程,而 TPC 开了一个线程。但是别烦我是有代价的,它要时间和空间啊,链接多了以后,那么多的进程 / 线程切换,这开销就上来了;所以这类模型能接受的最大链接数都不会高,通常在几百个左右。 2.2 select 模型 1. 最大并发数限制,由于一个进程所打开的 FD (文件描述符)是有限制的,www.linuxidc.com 由FD_SETSIZE 设置,默认值是 1024/2048 ,所以 Select 模型的最大并发数就被相应限制了。本身改改这个 FD_SETSIZE ?想法虽好,但是先看看下面吧 … 2. 效率问题, select 每次调用都会线性扫描所有的 FD 集合,这样效率就会呈现线性降低,把 FD_SETSIZE 改大的后果就是,你们都慢慢来,什么?都超时了??!! 3. 内核 / 用户空间内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采起了内存拷贝方法。 2.3 poll 模型 基本上效率和select 是相同的,select 缺点的 2 和 3 它都没有改掉。 3. Epoll 的提高 把其余模型逐个批判了一下,再来看看 Epoll 的改进之处吧,其实把 select 的缺点反过来那就是 Epoll 的优势了。 3.1. Epoll 没有最大并发链接的限制,上限是最大能够打开文件的数目,这个数字通常远大于 2048, 通常来讲这个数目和系统内存关系很大,具体数目能够 cat /proc/sys/fs/file-max 察看。 3.2. 效率提高, Epoll 最大的优势就在于它只管你“活跃”的链接,而跟链接总数无关,所以在实际的网络环境中, Epoll 的效率就会远远高于 select 和 poll 。 3.3. 内存拷贝, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。 4. Epoll 为何高效 Epoll 的高效和其数据结构的设计是密不可分的,这个下面就会提到。 首先回忆一下select 模型,当有I/O 事件到来时,select 通知应用程序有事件到了快去处理,而应用程序必须轮询全部的 FD 集合,测试每一个 FD 是否有事件发生,并处理事件;代码像下面这样: int res = select(maxfd+1, &readfds,NULL, NULL, 120); if (res > 0) { for (int i = 0; i <MAX_CONNECTION; i++) { if (FD_ISSET(allConnection[i], &readfds)) { handleEvent(allConnection[i]); } } } // if(res == 0) handle timeout, res < 0handle error Epoll 不只会告诉应用程序有I/0事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,所以根据这些信息应用程序就能直接定位到事件,而没必要遍历整个FD 集合。 int res = epoll_wait(epfd, events, 20,120); for (int i = 0; i < res;i++) { handleEvent(events[n]); } 5. 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 、指针等等。有了它,应用程序就能够直接定位目标了。 socket服务端的实现,select和epoll的区别(必问) select的本质是采用32个整数的32位,即32*32= 1024来标识,fd值为1-1024。当fd的值超过1024限制时,就必须修改FD_SETSIZE的大小。这个时候就能够标识32*max值范围的fd。 对于单进程多线程,每一个线程处理多个fd的状况,select是不适合的。 1.全部的线程均是从1-32*max进行扫描,每一个线程处理的均是一段fd值,这样作有点浪费 2.1024上限问题,一个处理多个用户的进程,fd值远远大于1024 因此这个时候应该采用poll, poll传递的是数组头指针和该数组的长度,只要数组的长度不是很长,性能仍是很不错的,由于poll一次在内核中申请4K(一个页的大小来存放fd),尽可能控制在4K之内 epoll仍是poll的一种优化,返回后不须要对全部的fd进行遍历,在内核中维持了fd的列表。select和poll是将这个内核列表维持在用户态,而后传递到内核中。可是只有在2.6的内核才支持。 epoll更适合于处理大量的fd ,且活跃fd不是不少的状况,毕竟fd较多仍是一个串行的操做 epoll哪些触发模式,有啥区别?(必须很是详尽的解释水平触发和边缘触发的区别,以及边缘触发在编程中要作哪些更多的确认) epoll能够同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,若是咱们没有采起行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,可是代码实现至关复杂。 epoll一样只告知那些就绪的文件描述符,并且当咱们调用epoll_wait()得到就绪文件描述符时,返回的不是实际的描述符,而是一个表明就绪描述符数量的值,你只须要去epoll指定的一个数组中依次取得相应数量的文件描述符便可,这里也使用了内存映射(mmap)技术,这样便完全省掉了这些文件描述符在系统调用时复制的开销。 另外一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用必定的方法后,内核才对全部监视的文件描述符进行扫描,而epoll事先经过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用相似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便获得通知。 惊群现象, 举一个很简单的例子,当你往一群鸽子中间扔一块食物,虽然最终只有一个鸽子抢到食物,但全部鸽子都会被惊动来争夺,没有抢到食物的鸽子只好回去继续睡觉,等待下一块食物到来。这样,每扔一块食物,都会惊动全部的鸽子,即为惊群。对于操做系统来讲,多个进程/线程在等待同一资源是,也会产生相似的效果,其结果就是每当资源可用,全部的进程/线程都来竞争资源,形成的后果: 1)系统对用户进程/线程频繁的作无效的调度、上下文切换,系统系能大打折扣。 2)为了确保只有一个线程获得资源,用户必须对资源操做进行加锁保护,进一步加大了系统开销。 什么是惊群 最多见的例子就是对于socket描述符的accept操做,当多个用户进程/线程监听在同一个端口上时,因为实际只可能accept一次,所以就会产生惊群现象,固然前面已经说过了,这个问题是一个古老的问题,新的操做系统内核已经解决了这一问题。 linux内核解决惊群问题的方法 对于一些已知的惊群问题,内核开发者增长了一个“互斥等待”选项。一个互斥等待的行为与睡眠基本相似,主要的不一样点在于: 1)当一个等待队列入口有 WQ_FLAG_EXCLUSEVE 标志置位, 它被添加到等待队列的尾部. 没有这个标志的入口项, 相反, 添加到开始. 2)当 wake_up 被在一个等待队列上调用时, 它在唤醒第一个有 WQ_FLAG_EXCLUSIVE 标志的进程后中止。 也就是说,对于互斥等待的行为,好比如对一个listen后的socket描述符,多线程阻塞accept时,系统内核只会唤醒全部正在等待此时间的队列的第一个,队列中的其余人则继续等待下一次事件的发生,这样就避免的多个线程同时监听同一个socket描述符时的惊群问题。 块设备和字符设备有什么区别, (1) 字符设备:提供连续的数据流,应用程序能够顺序读取,一般不支持随机存取。相反,此类设备支持按字节/字符来读写数据。举例来讲,调制解调器是典型的字符设备。 (2) 块设备:应用程序能够随机访问设备数据,程序可自行肯定读取数据的位置。硬盘是典型的块设备,应用程序能够寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块(一般是512B)的倍数进行。与字符设备不一样,块设备并不支持基于字符的寻址。 两种设备自己并没用严格的区分,主要是字符设备和块设备驱动程序提供的访问接口(file I/O API)是不同的。本文主要就数据接口、访问接口和设备注册方法对两种设备进行比较。 用户态和内核态的区别 虽然用户态下和内核态下工做的程序有不少差异,但最重要的差异就在于特权级的不一样,即权力的不一样。运行在用户态下的程序不能直接访问操做系统内核数据结构和程序, 当咱们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其须要操做系统帮助完成某些它没有权力和能力完成的工做时就会切换到内核态, linux文件系统:inode,inode存储了哪些东西,目录名,文件名存在哪里 inode包含文件的元信息,具体来讲有如下内容: * 文件的字节数 * 文件拥有者的User ID * 文件的Group ID * 文件的读、写、执行权限 * 文件的时间戳,共有三个:ctime指inode上一次变更的时间,mtime指文件内容上一次变更的时间,atime指文件上一次打开的时间。 * 连接数,即有多少文件名指向这个inode * 文件数据block的位置 inode也会消耗硬盘空间,因此硬盘格式化的时候,操做系统自动将硬盘分红两个区域。一个是数据区,存放文件数据;另外一个是inode区(inode table),存放inode所包含的信息。 每一个inode节点的大小,通常是128字节或256字节。inode节点的总数,在格式化时就给定,通常是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每一个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。 每一个inode都有一个号码,操做系统用inode号码来识别不一样的文件。 这里值得重复一遍,Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来讲,文件名只是inode号码便于识别的别称或者绰号。 表面上,用户经过文件名,打开文件。实际上,系统内部这个过程分红三步:首先,系统找到这个文件名对应的inode号码;其次,经过inode号码,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。 通常状况下,文件名和inode号码是"一一对应"关系,每一个inode号码对应一个文件名。可是,Unix/Linux系统容许,多个文件名指向同一个inode号码。 这意味着,能够用不一样的文件名访问一样的内容;对文件内容进行修改,会影响到全部文件名;可是,删除一个文件名,不影响另外一个文件名的访问。这种状况就被称为"硬连接"(hard link)。 ln命令能够建立硬连接:ln 源文件 目标文件 文件A和文件B的inode号码虽然不同,可是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。所以,不管打开哪个文件,最终读取的都是文件B。这时,文件A就称为文件B的"软连接"(soft link)或者"符号连接(symbolic link)。 这意味着,文件A依赖于文件B而存在,若是删除了文件B,打开文件A就会报错:"No such file or directory"。这是软连接与硬连接最大的不一样:文件A指向文件B的文件名,而不是文件B的inode号码,文件B的inode"连接数"不会所以发生变化。 ln -s命令能够建立软连接。:ln -s 源文文件或目录 目标文件或目录 http://www.ruanyifeng.com/blog/2011/12/inode.html /proc存在哪里(存在内存上) /proc 文件系统是一个虚拟文件系统,经过它能够使用一种新的方法在 Linux® 内核空间和用户空间之间进行通讯。在 /proc 文件系统中,咱们能够将对虚拟文件的读写做为与内核中实体进行通讯的一种手段,可是与普通文件不一样的是,这些虚拟文件的内容都是动态建立的 http://www.ibm.com/developerworks/cn/linux/l-proc.html ##网络: TCP和UDP区别、 key:TCP是一种面向链接的、可靠的、字节流服务 1.面向连接:TCP面向连接,面向链接意味着两个使用TCP的应用(一般是一个客户和一个服务器)在彼此交换数据以前必须经过三次握手先创建一个TCP链接。在一个TCP中仅有两方彼此通讯,多播和广播不能用于TCP。UDP是不可靠的传输,传输前不须要创建连接,能够应用多播和广播实现一对多的通讯。 2.可靠性:TCP提供端到端的流量控制,对收到的数据进行确认,采用超时重发,对失序的数据进行从新排序等机制保证数据通讯的可靠性。而UDP是一种不可靠的服务,接收方可能不能收到发送方的数据报。 3.TCP是一种流模式的协议,UDP是一种数据报模式的协议。进程的每一个输出操做都正好产生一个UDP数据报,并组装成一份待发送的IP数据报。TCP应用程序产生的全体数据与真正发送的单个IP数据报可能没有什么联系。TCP会有粘包和半包的现象。 4.效率上:速度上,通常TCP速度慢,传输过程当中须要对数据进行确认,超时重发,还要对数据进行排序。UDP没有这些机制因此速度快。数据比例,TCP头至少20个字节,UDP头8个字节,相对效率高。组装效率上:TCP头至少20个字节,UDP头8个字节,系统组装上TCP相对慢。 5.用途上:用于TCP可靠性,http,ftp使用。而因为UDP速度快,视频,在线游戏多用UDP,保证明时性 对于第三点的理解。TCP可能发送100个“包”,而接收到50个“包”,不是丢“包”了,而是每次接受的“包”都比发送的多,其实TCP并无包的概念。例如,每次发10个字节,可能读得时候一次读了20个字节。TCP是一种流模式的协议,在接收到的缓存中按照发送的包得顺序自动按照顺序拼接好,由于数据基原本自同一个主机,并且是按照顺序发送过来的,TCP的缓存中存放的就是,连续的数据。感受好像是多封装了一步比UDP。而UDP由于可能两个不一样的主机,给同一个主机发送,(一个端口可能收到多个应用程序的数据),或者按照TCP那样合并数据,必然会形成数据错误。我以为关键的缘由仍是,TCP是面向链接,而UDP是无链接的,这就致使,TCP接收的数据为一个主机发来且有序无误的,而UDP多是多个主机发来的无序,可能错误的。 TCP和UDP头部字节定义, TCP和UDP三次握手和四次挥手状态及消息类型, time_wait,close_wait状态产生缘由,keepalive, TIME_WAIT:表示收到了对方的FIN报文,并发送出了ACK报文。 TIME_WAIT状态下的TCP链接会等待2*MSL(Max Segment Lifetime,最大分段生存期,指一个TCP报文在Internet上的最长生存时间。每一个具体的TCP协议实现都必须选择一个肯定的MSL值,RFC 1122建议是2分钟,但BSD传统实现采用了30秒,Linux能够cat /proc/sys/net/ipv4/tcp_fin_timeout看到本机的这个值),而后便可回到CLOSED 可用状态了。若是FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,能够直接进入到TIME_WAIT状态,而无须通过FIN_WAIT_2状态。 若是使用了nginx代理,那么系统TIME_WAIT的数量会变得比较多,这是因为nginx代理使用了短连接的方式和后端交互的缘由,使得nginx和后端的ESTABLISHED变得不多而TIME_WAIT不少。这不但发生在安装nginx的代理服务器上,并且也会使后端的app服务器上有大量的TIME_WAIT。查阅TIME_WAIT资料,发现这个状态不少也没什么大问题,但可能由于它占用了系统过多的端口,致使后续的请求没法获取端口而形成障碍。 虽然TIME_WAIT会形成一些问题,可是要彻底枪毙掉它也是不正当的,虽然看起来这么作没什么错。具体可看这篇文档: http://hi.baidu.com/tim_bi/blog/item/35b005d784ca91d5a044df1d.html 因此目前看来最好的办法是让每一个TIME_WAIT早点过时。 在linux上能够这么配置: #让TIME_WAIT状态能够重用,这样即便TIME_WAIT占满了全部端口,也不会拒绝新的请求形成障碍 echo "1" > /proc/sys/net/ipv4/tcp_tw_reuse #让TIME_WAIT尽快回收,我也不知是多久,观察大概是一秒钟 echo "1" > /proc/sys/net/ipv4/tcp_tw_recycle 不少文档都会建议两个参数都配置上,可是我发现只用修改tcp_tw_recycle就能够解决问题的了,TIME_WAIT重用TCP协议自己就是不建议打开的。 不能重用端口可能会形成系统的某些服务没法启动,好比要重启一个系统监控的软件,它用了40000端口,而这个端口在软件重启过程当中恰好被使用了,就可能会重启失败的。linux默认考虑到了这个问题,有这么个设定: #查看系统本地可用端口极限值 cat /proc/sys/net/ipv4/ip_local_port_range 用这条命令会返回两个数字,默认是:32768 61000,说明这台机器本地能向外链接61000-32768=28232个链接,注意是本地向外链接,不是这台机器的全部链接,不会影响这台机器的80端口的对外链接数。但这个数字会影响到代理服务器(nginx)对app服务器的最大链接数,由于nginx对app是用的异步传输,因此这个环节的链接速度很快,因此堆积的链接就不多。假如nginx对app服务器之间的带宽出了问题或是app服务器有问题,那么可能使链接堆积起来,这时能够经过设定nginx的代理超时时间,来使链接尽快释放掉,通常来讲极少能用到28232个链接。 由于有软件使用了40000端口监听,经常出错的话,能够经过设定ip_local_port_range的最小值来解决: echo "40001 61000" > /proc/sys/net/ipv4/ip_local_port_range 可是这么作很显然把系统可用端口数减小了,这时能够把ip_local_port_range的最大值往上调,可是好习惯是使用不超过32768的端口来侦听服务,另外也没必要要去修改ip_local_port_range数值成1024 65535之类的,意义不大。 由于使用了nginx代理,在windows下也会形成大量TIME_WAIT,固然windows也能够调整: 在注册表(regedit)的HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters上添加一个DWORD类型的值TcpTimedWaitDelay,值就是秒数,便可。 windows默认是重用TIME_WAIT,我如今还不知道怎么改为不重用的,本地端口也没查到是什么值,但这些都关系不大,均可以按系统默认运做。 ------------------------------------------------------------------------------------------------------------------------ TIME_WAIT状态 根据TCP协议,主动发起关闭的一方,会进入TIME_WAIT状态,持续2*MSL(Max Segment Lifetime),缺省为240秒,在这个post中简洁的介绍了为何须要这个状态。 值得一说的是,对于基于TCP的HTTP协议,关闭TCP链接的是Server端,这样,Server端会进入TIME_WAIT状态,可想而知,对于访问量大的Web Server,会存在大量的TIME_WAIT状态,假如server一秒钟接收1000个请求,那么就会积压240*1000=240,000个TIME_WAIT的记录,维护这些状态给Server带来负担。固然现代操做系统都会用快速的查找算法来管理这些TIME_WAIT,因此对于新的TCP链接请求,判断是否hit中一个TIME_WAIT不会太费时间,可是有这么多状态要维护老是很差。 HTTP协议1.1版规定default行为是Keep-Alive,也就是会重用TCP链接传输多个request/response,一个主要缘由就是发现了这个问题。还有一个方法减缓TIME_WAIT压力就是把系统的2*MSL时间减小,由于240秒的时间实在是忒长了点,对于Windows,修改注册表,在HKEY_LOCAL_MACHINE\ SYSTEM\CurrentControlSet\Services\ Tcpip\Parameters上添加一个DWORD类型的值TcpTimedWaitDelay,通常认为不要少于60,否则可能会有麻烦。 对于大型的服务,一台server搞不定,须要一个LB(Load Balancer)把流量分配到若干后端服务器上,若是这个LB是以NAT方式工做的话,可能会带来问题。假如全部从LB到后端Server的IP包的source address都是同样的(LB的对内地址),那么LB到后端Server的TCP链接会受限制,由于频繁的TCP链接创建和关闭,会在server上留下TIME_WAIT状态,并且这些状态对应的remote address都是LB的,LB的source port撑死也就60000多个(2^16=65536,1~1023是保留端口,还有一些其余端口缺省也不会用),每一个LB上的端口一旦进入Server的TIME_WAIT黑名单,就有240秒不能再用来创建和Server的链接,这样LB和Server最多也就能支持300个左右的链接。若是没有LB,不会有这个问题,由于这样server看到的remote address是internet上广阔无垠的集合,对每一个address,60000多个port实在是够用了。 一开始我以为用上LB会很大程度上限制TCP的链接数,可是实验代表没这回事,LB后面的一台Windows Server 2003每秒处理请求数照样达到了600个,难道TIME_WAIT状态没起做用?用Net Monitor和netstat观察后发现,Server和LB的XXXX端口之间的链接进入TIME_WAIT状态后,再来一个LB的XXXX端口的SYN包,Server照样接收处理了,而是想像的那样被drop掉了。翻书,从书堆里面找出覆满尘土的大学时代买的《UNIX Network Programming, Volume 1, Second Edition: Networking APIs: Sockets and XTI》,中间提到一句,对于BSD-derived实现,只要SYN的sequence number比上一次关闭时的最大sequence number还要大,那么TIME_WAIT状态同样接受这个SYN,难不成Windows也算BSD-derived?有了这点线索和关键字(BSD),找到这个post,在NT4.0的时候,仍是和BSD-derived不同的,不过Windows Server 2003已是NT5.2了,也许有点差异了。 作个试验,用Socket API编一个Client端,每次都Bind到本地一个端口好比2345,重复的创建TCP链接往一个Server发送Keep-Alive=false的HTTP请求,Windows的实现让sequence number不断的增加,因此虽然Server对于Client的2345端口链接保持TIME_WAIT状态,可是老是可以接受新的请求,不会拒绝。那若是SYN的Sequence Number变小会怎么样呢?一样用Socket API,不过此次用Raw IP,发送一个小sequence number的SYN包过去,Net Monitor里面看到,这个SYN被Server接收后如泥牛如海,一点反应没有,被drop掉了。 按照书上的说法,BSD-derived和Windows Server 2003的作法有安全隐患,不过至少这样至少不会出现TIME_WAIT阻止TCP请求的问题,固然,客户端要配合,保证不一样TCP链接的sequence number要上涨不要降低。 ---------------------------------------------------------------------------------------------------------------------------- Socket中的TIME_WAIT状态 在高并发短链接的server端,当server处理完client的请求后马上closesocket此时会出现time_wait状态而后若是client再并发2000个链接,此时部分链接就链接不上了,用linger强制关闭能够解决此问题,可是linger会致使数据丢失,linger值为0时是强制关闭,不管并发多少多能正常链接上,若是非0会发生部分链接不上的状况!(可调用setsockopt设置套接字的linger延时标志,同时将延时时间设置为0。) TCP/IP的RFC文档。TIME_WAIT是TCP链接断开时一定会出现的状态。 是没法避免掉的,这是TCP协议实现的一部分。 在WINDOWS下,能够修改注册表让这个时间变短一些 time_wait的时间为2msl,默认为4min. 你能够经过改变这个变量: TcpTimedWaitDelay 把它缩短到30s TCP要保证在全部可能的状况下使得全部的数据都可以被投递。当你关闭一个socket时,主动关闭一端的socket将进入TIME_WAIT状态,而被动关闭一方则转入CLOSED状态,这的确可以保证全部的数据都被传输。当一个socket关闭的时候,是经过两端互发信息的四次握手过程完成的,当一端调用close()时,就说明本端没有数据再要发送了。这好似看来在握手完成之后,socket就都应该处于关闭CLOSED状态了。但这有两个问题,首先,咱们没有任何机制保证最后的一个ACK可以正常传输,第二,网络上仍然有可能有残余的数据包(wandering duplicates),咱们也必须可以正常处理。 经过正确的状态机,咱们知道双方的关闭过程以下 图 假设最后一个ACK丢失了,服务器会重发它发送的最后一个FIN,因此客户端必须维持一个状态信息,以便可以重发ACK;若是不维持这种状态,客户端在接收到FIN后将会响应一个RST,服务器端接收到RST后会认为这是一个错误。若是TCP协议可以正常完成必要的操做而终止双方的数据流传输,就必须彻底正确的传输四次握手的四个节,不能有任何的丢失。这就是为何socket在关闭后,仍然处于 TIME_WAIT状态,由于他要等待以便重发ACK。 若是目前链接的通讯双方都已经调用了close(),假定双方都到达CLOSED状态,而没有TIME_WAIT状态时,就会出现以下的状况。如今有一个新的链接被创建起来,使用的IP地址与端口与先前的彻底相同,后创建的链接又称做是原先链接的一个化身。还假定原先的链接中有数据报残存于网络之中,这样新的链接收到的数据报中有多是先前链接的数据报。为了防止这一点,TCP不容许从处于TIME_WAIT状态的socket创建一个链接。处于TIME_WAIT状态的socket在等待两倍的MSL时间之后(之因此是两倍的MSL,是因为MSL是一个数据报在网络中单向发出到认定丢失的时间,一个数据报有可能在发送图中或是其响应过程当中成为残余数据报,确认一个数据报及其响应的丢弃的须要两倍的MSL),将会转变为CLOSED状态。这就意味着,一个成功创建的链接,必然使得先前网络中残余的数据报都丢失了。 因为TIME_WAIT状态所带来的相关问题,咱们能够经过设置SO_LINGER标志来避免socket进入TIME_WAIT状态,这能够经过发送RST而取代正常的TCP四次握手的终止方式。但这并非一个很好的主意,TIME_WAIT对于咱们来讲每每是有利的。 客户端与服务器端创建TCP/IP链接后关闭SOCKET后,服务器端链接的端口 状态为TIME_WAIT 是否是全部执行主动关闭的socket都会进入TIME_WAIT状态呢? 有没有什么状况使主动关闭的socket直接进入CLOSED状态呢? 主动关闭的一方在发送最后一个 ack 后 就会进入 TIME_WAIT 状态 停留2MSL(max segment lifetime)时间 这个是TCP/IP必不可少的,也就是“解决”不了的。 也就是TCP/IP设计者原本是这么设计的 主要有两个缘由 1。防止上一次链接中的包,迷路后从新出现,影响新链接 (通过2MSL,上一次链接中全部的重复包都会消失) 2。可靠的关闭TCP链接 在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会从新发 fin, 若是这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。因此 主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。 TIME_WAIT 并不会占用很大资源的,除非受到攻击。 还有,若是一方 send 或 recv 超时,就会直接进入 CLOSED 状态 socket-faq中的这一段讲的也很好,摘录以下: 2.7. Please explain the TIME_WAIT state. 什么是滑动窗口,超时重传, 列举你所知道的tcp选项, connect会阻塞检测及防止,socket什么状况下可读? connect会阻塞,怎么解决?(必考必问) 最一般的方法最有效的是加定时器;也能够采用非阻塞模式。 设置非阻塞,返回以后用select检测状态) 若是select返回可读,结果只读到0字节,什么状况? 某个套接字集合中没有准备好,可能会select内存用FD_CLR清该位为0; socket什么状况下可读? 每次读操做返回前都要检查是否还有剩余数据没读完,若是是的话保持数据有效标志,不这样设计的话会出现明显的不一致,那就是数据在读缓冲但没有读有效标志。 keepalive是什么东东?如何使用? 设置Keepalive参数,检测已中断的客户链接 在TCP中有一个Keep-alive的机制能够检测死链接,原理很简单,TCP会在空闲了必定时间后发送数据给对方: 1.若是主机可达,对方就会响应ACK应答,就认为是存活的。 2.若是可达,但应用程序退出,对方就发RST应答,发送TCP撤消链接。 3.若是可达,但应用程序崩溃,对方就发FIN消息。 4.若是对方主机不响应ack, rst,继续发送直到超时,就撤消链接。这个时间就是默认 的二个小时。 UDP中使用connect的好处: 1:会提高效率.前面已经描述了.2:高并发服务中会增长系统稳定性.缘由:假设client A 经过非connect的UDP与serverB,C通讯.B,C提供相同服务.为了负载均衡,咱们让A与B,C交替通讯.A 与 B通讯IPa:PORTa<----> IPb:PORTbA 与 C通讯IPa:PORTa'<---->IPc:PORTc 假设PORTa 与 PORTa'相同了(在大并发状况下会发生这种状况),那么就有可能出现A等待B的报文,却收到了C的报文.致使收报错误.解决方法内就是采用connect的UDP通讯方式.在A中建立两个udp,而后分别connect到B,C. 长链接和短链接, DNS和HTTP协议,HTTP请求方式, cookie,session,localstroage, 一致性哈希负载均衡, 描述在浏览器中敲入一个网址并按下回车后所发生的事情, PING命令 ping命令所利用的原理是这样的:网络上的机器都有惟一肯定的IP地址,咱们给目标IP地址发送一个数据包,对方就要返回一个一样大小的数据包,根据返回的数据包咱们能够肯定目标主机的存在,能够初步判断目标主机的操做系统等。 ##数据库: 谈谈你对数据库中索引的理解,索引和主键区别 - 汇集索引一个表只能有一个,而非汇集索引一个表能够存在多个。 - 汇集索引存储记录是物理上连续存在,而非汇集索引是逻辑上的连续,物理存储并不连续。 汇集索引:该索引中键值的逻辑顺序决定了表中相应行的物理顺序。 汇集索引肯定表中数据的物理顺序。汇集索引相似于电话簿,后者按姓氏排列数据。因为汇集索引规定数据在表中的物理存储顺序,所以一个表只能包含一个汇集索引。但该索引能够包含多个列(组合索引),就像电话簿按姓氏和名字进行组织同样。 汇集索引使用注意事项 定义汇集索引键时使用的列越少越好。 • 包含大量非重复值的列。 .• 使用下列运算符返回一个范围值的查询:BETWEEN、>、>=、< 和 <=。 • 被连续访问的列。 • 回大型结果集的查询。 • 常常被使用联接或 GROUP BY 子句的查询访问的列;通常来讲,这些是外键列。对 ORDER BY 或 GROUP BY 子句中指定的列进行索引,能够使 SQL Server 没必要对数据进行排序,由于这些行已经排序。这样能够提升查询性能。 • OLTP 类型的应用程序,这些程序要求进行很是快速的单行查找(通常经过主键)。应在主键上建立汇集索引。 汇集索引不适用于: • 频繁更改的列 。这将致使整行移动(由于 SQL Server 必须按物理顺序保留行中的数据值)。这一点要特别注意,由于在大数据量事务处理系统中数据是易失的。 • 宽键 。来自汇集索引的键值由全部非汇集索引做为查找键使用,所以存储在每一个非汇集索引的叶条目内。 非汇集索引:数据存储在一个地方,索引存储在另外一个地方,索引带有指针指向数据的存储位置。 非汇集索引中的项目按索引键值的顺序存储,而表中的信息按另外一种顺序存储(这能够由汇集索引规定)。对于非汇集索引,能够为在表非汇集索引中查找数据时经常使用的每一个列建立一个非汇集索引。有些书籍包含多个索引。例如,一本介绍园艺的书可能会包含一个植物通俗名称索引,和一个植物学名索引,由于这是读者查找信息的两种最经常使用的方法。 一个通俗的举例,说明二者的区别 其实,咱们的汉语字典的正文自己就是一个汇集索引。好比,咱们要查“安”字,就会很天然地翻开字典的前几页,由于“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就天然地排在字典的前部。若是您翻完了全部以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;一样的,若是查“张”字,那您也会将您的字典翻到最后部分,由于“张”的拼音是“zhang”。也就是说,字典的正文部分自己就是一个目录,您不须要再去查其余目录来找到您须要找的内容。咱们把这种正文内容自己就是一种按照必定规则排列的目录称为“汇集索引”。 若是您认识某个字,您能够快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而须要去根据“偏旁部首”查到您要找的字,而后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并非真正的正文的排序方法,好比您查“张”字,咱们能够看到在查部首以后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码倒是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并非真正的分别位于“张”字的上下方,如今您看到的连续的“驰、张、弩”三字实际上就是他们在非汇集索引中的排序,是字典正文中的字在非汇集索引中的映射。咱们能够经过这种方式来找到您所须要的字,但它须要两个过程,先找到目录中的结果,而后再翻到您所须要的页码。咱们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非汇集索引”。 第一:汇集索引的约束是惟一性,是否要求字段也是惟一的呢? 分析:若是认为是的朋友,多是受系统默认设置的影响,通常咱们指定一个表的主键,若是这个表以前没有汇集索引,同时创建主键时候没有强制指定使用非汇集索引,SQL会默认在此字段上建立一个汇集索引,而主键都是惟一的,因此理所固然的认为建立汇集索引的字段也须要惟一。 结论:汇集索引能够建立在任何一列你想建立的字段上,这是从理论上讲,实际状况并不能随便指定,不然在性能上会是恶梦。 第二:为何汇集索引能够建立在任何一列上,若是此表没有主键约束,即有可能存在重复行数据呢? 粗一看,这还真是和汇集索引的约束相背,但实际状况真能够建立汇集索引。 分析其缘由是:若是未使用 UNIQUE 属性建立汇集索引,数据库引擎将向表自动添加一个四字节 uniqueifier 列。必要时,数据库引擎 将向行自动添加一个 uniqueifier 值,使每一个键惟一。此列和列值供内部使用,用户不能查看或访问。 第三:是否是汇集索引就必定要比非汇集索引性能优呢? 若是想查询学分在60-90之间的学生的学分以及姓名,在学分上建立汇集索引是不是最优的呢? 答:否。既然只输出两列,咱们能够在学分以及学生姓名上建立联合非汇集索引,此时的索引就造成了覆盖索引,即索引所存储的内容就是最终输出的数据,这种索引在比以学分为汇集索引作查询性能更好。 第四:在数据库中经过什么描述汇集索引与非汇集索引的? 索引是经过二叉树的形式进行描述的,咱们能够这样区分汇集与非汇集索引的区别:汇集索引的叶节点就是最终的数据节点,而非汇集索引的叶节仍然是索引节点,但它有一个指向最终数据的指针。 第五:在主键是建立汇集索引的表在数据插入上为何比主键上建立非汇集索引表速度要慢? 有了上面第四点的认识,咱们分析这个问题就有把握了,在有主键的表中插入数据行,因为有主键惟一性的约束,因此须要保证插入的数据没有重复。咱们来比较下主键为汇集索引和非汇集索引的查找状况:汇集索引因为索引叶节点就是数据页,因此若是想检查主键的惟一性,须要遍历全部数据节点才行,但非汇集索引不一样,因为非汇集索引上已经包含了主键值,因此查找主键惟一性,只须要遍历全部的索引页就行,这比遍历全部数据行减小了很多IO消耗。这就是为何主键上建立非汇集索引比主键上建立汇集索引在插入数据时要快的真正缘由。 如今普通关系数据库用得数据结构是什么类型的数据结构, B+树 [MySQL索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) 索引的优势和缺点, 创建索引的优势 1.大大加快数据的检索速度; 2.建立惟一性索引,保证数据库表中每一行数据的惟一性; 3.加速表和表之间的链接; 4.在使用分组和排序子句进行数据检索时,能够显著减小查询中分组和排序的时间。 索引的缺点 1.索引须要占物理空间。 2.当对表中的数据进行增长、删除和修改的时候,索引也要动态的维护,下降了数据的维护速度。 惟一索引 惟一索引是不容许其中任何两行具备相同索引值的索引。 当现有数据中存在重复的键值时,大多数数据库不容许将新建立的惟一索引与表一块儿保存。数据库还可能防止添加将在表中建立重复键值的新数据。例如,若是在 employee 表中职员的姓 (lname) 上建立了惟一索引,则任何两个员工都不能同姓。 主键索引 数据库表常常有一列或列组合,其值惟一标识表中的每一行。该列称为表的主键。 在数据库关系图中为表定义主键将自动建立主键索引,主键索引是惟一索引的特定类型。该索引要求主键中的每一个值都惟一。当在查询中使用主键索引时,它还容许对数据的快速访问。 汇集索引 在汇集索引中,表中行的物理顺序与键值的逻辑(索引)顺序相同。一个表只能包含一个汇集索引。 若是某索引不是汇集索引,则表中行的物理顺序与键值的逻辑顺序不匹配。与非汇集索引相比,汇集索引一般提供更快的数据访问速度。 关系型数据库和非关系数据库的特色, 简单来讲,关系模型指的就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。 非关系型数据库提出另外一种理念,例如,以键值对存储,且结构不固定,每个元组能够有不同的字段,每一个元组能够根据须要增长一些本身的键值对,这 样就不会局限于固定的结构,能够减小一些时间和空间的开销。使用这种方式,用户能够根据须要去添加本身须要的字段,这样,为了获取用户的不一样信息,不须要 像关系型数据库中,要对多表进行关联查询。仅须要根据id取出相应的value就能够完成查询。但非关系型数据库因为不多的约束,他也不可以提供像SQL 所提供的where这种对于字段属性值状况的查询。而且难以体现设计的完整性。他只适合存储一些较为简单的数据,对于须要进行较复杂查询的数据,SQL数 据库显的更为合适。 关系型数据库的最大特色就是事务的一致性:传统的关系型数据库读写操做都是事务的,具备ACID的特色,这个特性使得关系型数据库能够用于几乎全部对一致性有要求的系统中,如典型的银行系统。 可是,在网页应用中,尤为是SNS应用中,一致性却不是显得那么重要,用户A看到的内容和用户B看到同一用户C内容更新不一致是能够容忍的,或者 说,两我的看到同一好友的数据更新的时间差那么几秒是能够容忍的,所以,关系型数据库的最大特色在这里已经无用武之地,起码不是那么重要了。 相反地,关系型数据库为了维护一致性所付出的巨大代价就是其读写性能比较差,而像微博、facebook这类SNS的应用,对并发读写能力要求极 高,关系型数据库已经没法应付(在读方面,传统上为了克服关系型数据库缺陷,提升性能,都是增长一级memcache来静态化网页,而在SNS中,变化太 快,memchache已经无能为力了),所以,必须用新的一种数据结构存储来代替关系数据库。 关系数据库的另外一个特色就是其具备固定的表结构,所以,其扩展性极差,而在SNS中,系统的升级,功能的增长,每每意味着数据结构巨大变更,这一点关系型数据库也难以应付,须要新的结构化数据存储。 因而,非关系型数据库应运而生,因为不可能用一种数据结构化存储应付全部的新的需求,所以,非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合。 必须强调的是,数据的持久存储,尤为是海量数据的持久存储,仍是须要一种关系数据库这员老将。 非关系型数据库分类: 主要分为如下几类: 面向高性能并发读写的key-value数据库: key-value数据库的主要特色即便具备极高的并发读写性能,Redis,Tokyo Cabinet,Flare就是这类的表明 面向海量数据访问的面向文档数据库: 这类数据库的特色是,能够在海量的数据中快速的查询数据,典型表明为MongoDB以及CouchDB 面向可扩展性的分布式数据库: 这类数据库想解决的问题就是传统数据库存在可扩展性上的缺陷,这类数据库能够适应数据量的增长以及数据结构的变化 [关系型数据库和非关系型数据库](http://my.oschina.net/u/1773689/blog/364548) 乐观锁与悲观锁的区别, 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操做。[1] 悲观锁假定其余用户企图访问或者改变你正在访问、更改的对象的几率是很高的,所以在悲观锁的环境中,在你开始改变此对象以前就将该对象锁住,而且直到你提交了所做的更改以后才释放锁。悲观的缺陷是不管是页锁仍是行锁,加锁的时间可能会很长,这样可能会长时间的限制其余用户的访问,也就是说悲观锁的并发访问性很差。 乐观锁:假设不会发生并发冲突,只在提交操做时检查是否违反数据完整性。[1] 乐观锁不能解决脏读的问题。 乐观锁则认为其余用户企图改变你正在更改的对象的几率是很小的,所以乐观锁直到你准备提交所做的更改时才将对象锁住,当你读取以及改变该对象时并不加锁。可见乐观锁加锁的时间要比悲观锁短,乐观锁能够用较大的锁粒度得到较好的并发访问性能。可是若是第二个用户刚好在第一个用户提交更改以前读取了该对象,那么当他完成了本身的更改进行提交时,数据库就会发现该对象已经变化了,这样,第二个用户不得不从新读取该对象并做出更改。这说明在乐观锁环境中,会增长并发用户读取对象的次数。 从数据库厂商的角度看,使用乐观的页锁是比较好的,尤为在影响不少行的批量操做中能够放比较少的锁,从而下降对资源的需求提升数据库的性能。再考虑汇集索引。在数据库中记录是按照汇集索引的物理顺序存放的。若是使用页锁,当两个用户同时访问更改位于同一数据页上的相邻两行时,其中一个用户必须等待另外一个用户释放锁,这会明显地下降系统的性能。interbase和大多数关系数据库同样,采用的是乐观锁,并且读锁是共享的,写锁是排他的。能够在一个读锁上再放置读锁,但不能再放置写锁;你不能在写锁上再放置任何锁。锁是目前解决多用户并发访问的有效手段。 [乐观锁与悲观锁的区别](http://www.cnblogs.com/Bob-FD/p/3352216.html) 数据库范式 1NF的定义为:符合1NF的关系中的每一个属性都不可再分,1NF是全部关系型数据库的最基本要求, 2NF在1NF的基础之上,消除了非主属性对于码的部分函数依赖。 3NF在2NF的基础之上,消除了非主属性对于码的传递函数依赖。也就是说, 若是存在非主属性对于码的传递函数依赖,则不符合3NF的要求。 BCNF范式在 3NF 的基础上消除主属性对于码的部分与传递函数依赖。 [解释一下关系数据库的第一第二第三范式?](http://www.zhihu.com/question/24696366) 数据库日志类型做用 MySQL日志文件分类 1.错误日志(Error Log) 2.二进制日志(Binary Log & Binary Log Index) 3.通用查询日志(query log) 4.慢查询日志(slow query log) 5.Innodb的在线 redo 日志(innodb redo log) 6.更新日志(update log) innodb和myisam的区别 innodb,汇集索引,支持外键和事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant)),不支持全文索引,不支持计数,统计的时候会遍历, myisam,非汇集索引,不支持外键事务,支持全文索引,支持计数,查询效果较好 你对innodb哪里最熟悉 innodb的索引有哪几种类型 4种,hash,b-tree,spatial,full-text 1. B-Tree索引 最多见的索引类型,基于B-Tree数据结构。B-Tree的基本思想是,全部值(被索引的列)都是排过序的,每一个叶节点到跟节点距离相等。因此B-Tree适合用来查找某一范围内的数据,并且能够直接支持数据排序(ORDER BY)。可是当索引多列时,列的顺序特别重要,须要格外注意。InnoDB和MyISAM都支持B-Tree索引。InnoDB用的是一个变种B+Tree,而MyISAM为了节省空间对索引进行了压缩,从而牺牲了性能。 2. Hash索引 基于hash表。因此这种索引只支持精确查找,不支持范围查找,不支持排序。这意味着范围查找或ORDER BY都要依赖server层的额外工做。目前只有Memory引擎支持显式的hash索引(可是它的hash是nonunique的,冲突太多时也会影响查找性能)。Memory引擎默认的索引类型便是Hash索引,虽然它也支持B-Tree索引。 Hash 索引仅仅能知足"=","IN"和"<=>"查询,不能使用范围查询。 例子: CREATE TABLE testhash ( fname VARCHAR(50) NOT NULL, lname VARCHAR(50) NOT NULL, KEY USING HASH(fname) ) ENGINE =MEMORY; 3. Spatial (R-Tree)(空间)索引 只有MyISAM引擎支持,而且支持的很差。能够忽略。 4. Full-text索引 主要用来查找文本中的关键字,而不是直接与索引中的值相比较。Full-text索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的WHERE语句的参数匹配。你能够对某列分别进行full-text索引和B-Tree索引,二者互不冲突。Full-text索引配合MATCH AGAINST操做使用,而不是通常的WHERE语句加LIKE。 http://segmentfault.com/q/1010000003832312 B TREE 和B+TREE的区别 innodb有全文索引吗 没有,myisam有 union和join JOIN用于按照ON条件联接两个表,主要有四种: INNER JOIN:内部联接两个表中的记录,仅当至少有一个同属于两表的行符合联接条件时,内联接才返回行。我理解的是只要记录不符合ON条件,就不会显示在结果集内。 LEFT JOIN / LEFT OUTER JOIN:外部联接两个表中的记录,并包含左表中的所有记录。若是左表的某记录在右表中没有匹配记录,则在相关联的结果集中右表的全部选择列表列均为空值。理解为即便不符合ON条件,左表中的记录也所有显示出来,且结果集中该类记录的右表字段为空值。 RIGHT JOIN / RIGHT OUTER JOIN:外部联接两个表中的记录,并包含右表中的所有记录。简单说就是和LEFT JOIN反过来。 FULL JOIN / FULL OUTER JOIN: 完整外部联接返回左表和右表中的全部行。就是LEFT JOIN和RIGHT JOIN和合并,左右两表的数据都所有显示。 JOIN的基本语法: Select table1.* FROM table1 JOIN table2 ON table1.id=table2.id UNION运算符 将两个或更多查询的结果集组合为单个结果集,该结果集包含联合查询中的全部查询的所有行。UNION的结果集列名与UNION运算符中第一个Select语句的结果集的列名相同。另外一个Select语句的结果集列名将被忽略。 其中两种不一样的用法是UNION和UNION ALL,区别在于UNION从结果集中删除重复的行。若是使用UNION ALL 将包含全部行而且将不删除重复的行。 相同点:在某些特定的状况下,能够用join实现union all的功能,这种状况是有条件的,当出现这种状况的时候选择union all仍是group by就能够看状况或者看二者的消耗而决定。 http://chengheng1984.blog.163.com/blog/static/17947412201012215738844/ http://www.51testing.com/html/14/446214-249265.html ##海量数据处理: bitmap Map-Reduce原理, BloomFilter原理、 它其实是一个很长的二进制向量和一系列随机映射函数(Hash函数)。布隆过滤器能够用于检索一个元素是否在一个集合中。它的优势是空间效率和查询时间都远远超过通常的算法,缺点是有必定的误识别率和删除困难。Bloom Filter普遍的应用于各类须要查询的场合中,如Orocle的数据库,Google的BitTable也用了此技术。 Bloom Filter特色: 不存在漏报(False Negative),即某个元素在某个集合中,确定能报出来。 可能存在误报(False Positive),即某个元素不在某个集合中,可能也被爆出来。 肯定某个元素是否在某个集合中的代价和总的元素数目无关。 Trie树原理、 单词搜索树 B+树原理, LSM树原理, 大数据处理 ##工具: 编译工具GCC,调试工具GDB,性能优化工具Perf,内存泄露检查工具Valgrind,makefile编写 其余工具: netstat,ps,top,df,fdisk,lsof,ifconfig,uname,kill,tcpdump,ipcs,grep ##其余:安全,加密方式(DES,SHA) 附:[软件开发者面试百问](http://blog.csdn.net/programmer_editor/article/details/4004408) #相关书籍 语言类: 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/。 笔试面试的经典:Cracking the coding interview--问题与解答: http://hawstein.com/posts/ctci-solutions-contents.html LeetCode:http://leetcode.com/ 这里有很多笔试题集锦:http://blog.csdn.net/hackbuteer1 程序员编程艺术:面试和算法心得http://taop.marchtea.com/