web请求

  1. 输入 http://www.baidu.com 在浏览器的完整过程,越详细越好。
    浏览器获取输入的域名http://www.baidu.com

浏览器向域名系统DNS请求解析http://www.baidu.com的IP地址

DNS解析出百度服务器的IP地址

浏览器与服务器建立TCP连接(默认端口80)

浏览器发出HTTP请求,请求百度首页

服务器通过HTTP请求把首页文件发给浏览器

TCP连接释放

浏览器解析首页文件,展示web界面

  1. 请描述C/C++程序的内存分区?

其实C和C++的内存分区还是有一定区别的,但此处不作区分:

1)、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其

操作方式类似于数据结构中的栈。 ///栈先进后出,操作系统编译器自动分配释放

2)、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回

收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 ////我们可以将一条链表想象成环环相扣的结点,就如平常所见到的锁链一样。链表内包含很多结点(当然也可以包含零个结点)。其中每个结点的数据空间一般会包含一个数据结构(用于存放各种类型的数据)以及一个指针,该指针一般称为next,用来指向下一个结点的位置。由于下一个结点也是链表类型,所以next的指针也要定义为链表类型。

3)、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的

全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另

一块区域。 - 程序结束后由系统释放。

4)、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放。

5)、程序代码区—存放函数体的二进制代码。

栈区与堆区的区别:

1)堆和栈中的存储内容:栈存局部变量、函数参数等。堆存储使用new、malloc申请的变量等;

2)申请方式:栈内存由系统分配,堆内存由自己申请;

3)申请后系统的响应:栈——只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

堆——首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表 中删除,并将该结点的空间分配给程序;

4)申请大小的限制:Windows下栈的大小一般是2M,堆的容量较大;

5)申请效率的比较:栈由系统自动分配,速度较快。堆使用new、malloc等分配,较慢;

总结:栈区优势在处理效率,堆区优势在于灵活;

内存模型:自由区、静态区、动态区;

根据c/c++对象生命周期不同,c/c++的内存模型有三种不同的内存区域,即:自由存储区,动态区、静态区。

自由存储区:局部非静态变量的存储区域,即平常所说的栈;

动态区: 用new ,malloc分配的内存,即平常所说的堆;

静态区:全局变量,静态变量,字符串常量存在的位置;

注:代码虽然占内存,但不属于c/c++内存模型的一部分;

  1. 快速排序的思想、时间复杂度、实现以及优化方法?

快速排序的三个步骤

(1)选择基准:在待排序列中,按照某种方式挑出一个元素,作为 “基准”(pivot);

(2)分割操作:以该基准在序列中的实际位置,把序列分成两个子序列。此时,在基准左边的元素都比该基准小,在基准右边的元素都比基准大;

(3)递归地对两个序列进行快速排序,直到序列为空或者只有一个元素。

基准的选择:

对于分治算法,当每次划分时,算法若都能分成两个等长的子序列时,那么分治算法效率会达到最大。

即:同一数组,时间复杂度最小的是每次选取的基准都可以将序列分为两个等长的;时间复杂度最大的是每次选择的基准都是当前序列的最大或最小元素;

快排代码实现:

我们一般选择序列的第一个作为基数,那么快排代码如下:

void quicksort(vector &v,int left, int right)

{

if(left < right)//false则递归结束

{

int key=v[left];//基数赋值

int low = left;

int high = right;

while(low < high) //当low=high时,表示一轮分割结束

{

while(low < high && v[high] >= key)//v[low]为基数,从后向前与基数比 较

{

high–;

}

swap(v[low],v[high]);

while(low < high && v[low] <= key)//v[high]为基数,从前向后与基数比 较

{

low++;

}

swap(v[low],v[high]);

}

//分割后,对每一分段重复上述操作

quicksort(v,left,low-1);

quicksort(v,low+1,right);

}

}

注:上述数组或序列v必须是引用类型的形参,因为后续快排结果需要直接反映在原序列中;

优化:

上述快排的基数是序列的第一个元素,这样的对于有序序列,快排时间复杂度会达到最差的o(n^2)。所以,优化方向就是合理的选择基数。

常见的做法“三数取中”法(序列太短还要结合其他排序法,如插入排序、选择排序等),如下:

①当序列区间长度小于 7 时,采用插入排序;

②当序列区间长度小于 40 时,将区间分成2段,得到左端点、右端点和中点,我们对这三个点取中数作为基数;

③当序列区间大于等于 40 时,将区间分成 8 段,得到左三点、中三点和右三点,分别再得到左三点中的中数、中三点中的中数和右三点中的中数,再将得到的三个中数取中数,然后将该值作为基数。

具体代码只是在上一份的代码中将“基数赋值”改为①②③对应的代码即可:

int key=v[left];//基数赋值

if (right-left+1<=7) {

insertion_sort(v,left,right);//插入排序

return;

}else if(right-left+1<=8){

key=SelectPivotOfThree(v,left,right);//三个取中

}else{

//三组三个取中,再三个取中(使用4次SelectPivotOfThree,此处不具体展示)

}

需要调用的函数:

void insertion_sort(vector &unsorted,int left, int right) //插入排序算法

{

for (int i = left+1; i <= right; i++)

{

if (unsorted[i - 1] > unsorted[i])

{

int temp = unsorted[i];

int j = i;

while (j > left && unsorted[j - 1] > temp)

{

unsorted[j] = unsorted[j - 1];

j–;

}

unsorted[j] = temp;

}

}

}

int SelectPivotOfThree(vector &arr,int low,int high)

//三数取中,同时将中值移到序列第一位

{

int mid = low + (high - low)/2;//计算数组中间的元素的下标

//使用三数取中法选择枢轴

if (arr[mid] > arr[high])//目标: arr[mid] <= arr[high]

{

swap(arr[mid],arr[high]);

}

if (arr[low] > arr[high])//目标: arr[low] <= arr[high]

{

swap(arr[low],arr[high]);

}

if (arr[mid] > arr[low]) //目标: arr[low] >= arr[mid]

{

swap(arr[mid],arr[low]);

}

//此时,arr[mid] <= arr[low] <= arr[high]

return arr[low];

//low的位置上保存这三个位置中间的值

//分割时可以直接使用low位置的元素作为枢轴,而不用改变分割函数了

}

这里需要注意的有两点:

①插入排序算法实现代码;

②三数取中函数不仅仅要实现取中,还要将中值移到最低位,从而保证原分割函数依然可用。

  1. 请描述IO多路复用机制?

IO模型有4中:同步阻塞IO、同步非阻塞IO、异步阻塞IO、异步非阻塞IO;IO多路复用属于IO模型中的异步阻塞IO模型,在服务器高性能IO构建中常常用到。

同步异步是表示服务端的,阻塞非阻塞是表示用户端,所以可解释为什么IO多路复用(异步阻塞)常用于服务器端的原因;

文件描述符(FD,又叫文件句柄):描述符就是一个数字,它指向内核中的一个结构体(文件路径,数据区等属性)。具体来源:Linux内核将所有外部设备都看作一个文件来操作,对文件的操作都会调用内核提供的系统命令,返回一个fd(文件描述符)。

下面开始介绍IO多路复用:

(1)I/O多路复用技术通过把多个I/O的阻塞复用到同一个select、poll或epoll的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比,I/O多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程。

(2)select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

(3)I/O多路复用的主要应用场景如下:

服务器需要同时处理多个处于监听状态或者多个连接状态的套接字;

服务器需要同时处理多种网络协议的套接字;

(4)目前支持I/O多路复用的系统调用有 select,poll,epoll,epoll与select的原理比较类似,但epoll作了很多重大改进,现总结如下:

①支持一个进程打开的文件句柄FD个数不受限制(为什么select的句柄数量受限制:select使用位域的方式来传递关心的文件描述符,因为位域就有最大长度,在Linux下是1024,所以有数量限制);

②I/O效率不会随着FD数目的增加而线性下降;

③epoll的API更加简单;

(5)三种接口调用介绍:

①select函数调用格式:

#include <sys/select.h>

#include <sys/time.h>

int select(int maxfdp1,fd_set readset,fd_set writeset,fd_set exceptset,const struct timeval timeout)

//返回值:就绪描述符的数目,超时返回0,出错返回-1

②poll函数调用格式:

include <poll.h>

int poll ( struct pollfd * fds, unsigned int nfds, int timeout);

③epoll函数格式(操作过程包括三个函数):

#include <sys/epoll.h>

int epoll_create(int size);

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

(6)作用:一定程度上替代多线程/多进程,减少资源占用,保证系统运行的高效率;

  1. 使用mysql索引都有哪些原则?索引什么数据结构?B+tree 和 B tree 什么区别?

1、 对于查询频率高的字段创建索引;

2、 对排序、分组、联合查询频率高的字段创建索引;

3、 索引的数目不宜太多

原因: a、每创建一个索引都会占用相应的物理控件;

b、过多的索引会导致insert、update、delete语句的执行效率降低;

4、若在实际中,需要将多个列设置索引时,可以采用多列索引

如:某个表(假设表名为Student),存在多个字段(StudentNo, StudentName, Sex, Address, Phone,BirthDate),其中需要对StudentNo,StudentName字段进行查询,对Sex字段进行分组,对BirthDate 字段进行排序,此时可以创建多列索引index index_name (StudentNo, StudentName, Sex, BirthDate);#index_name为索引名

在上面的语句中只创建了一个索引,但是对4个字段都赋予了索引的功能。

创建多列索引,需要遵循BTree类型,即第一列使用时,才启用索引。

在上面的创建语句中,只有mysql语句在使用到StudentNo字段时,索引才会被启用。

如: select * from Student where StudentNo = 1000; #使用到了StudentNo字段,索引被启用。

以使用explain检测索引是否被启用如:explain select * from Student where StudentNo = 1000;

5、选择唯一性索引

唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。例如,学生表中学号是具有唯 一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。如果使用姓名的话,可能存 在同名现象,从而降低查询速度。

6、尽量使用数据量少的索引

如果索引的值很长,那么查询的速度会受到影响。例如,对一个CHAR(100)类型的字段进行全文检索 需要的时间肯定要比对CHAR(10)类型的字段需要的时间要多。

7、尽量使用前缀来索引

如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文检 索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。

8、删除不再使用或者很少使用的索引.

表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理 员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响B+ tree树索引, B tree,散列

  1. Mysql有哪些存储引擎?请详细列举其区别?

InnoDB: 事务型存储引擎, 并且有较高的并发读取频率

MEMORY: 存储引擎,存放在内存中,数据量小, 速度快

Merge:

ARCHIVE: 归档, 有很好的压缩机制

  1. 设计高并发系统数据库层面该如何设计? 数据库锁有哪些类型?如何实现?

  2. 分库分表: 同样量的数据平均存储在不同数据库相同表(或不同表)中,减轻单表压力,如果还是很大,就可以每个库在分多张表,根据hash取值或者其他逻辑判断将数据存储在哪张表中

  3. 读写分离: 数据库原本就有主从数据库之分,查询在从服务器,增删改在主服务器,

  4. 归档和操作表区分: 建一张归档表,将历史数据放入,需要操作的表数据单独存储

  5. 索引啊之类的创建,对于数据量很大,百万级别以上的单表,如果增删改操作不频繁的话, 可以创建bitMap索引,速度要快得多

  6. 共享锁:要等第一个人操作完,释放锁,才能操作

  7. 更新锁:解决死锁,别人可以读,但不能操作

  8. 排他锁:读写都被禁用

  9. 意向锁(xlock): 对表中部分数据加锁,查询时,可以跳过

  10. 计划锁: 操作时,别的表连接不了这张表,

常见内存分配算法及优缺点如下:
  (1)首次适应算法。使用该算法进行内存分配时,从空闲分区链首开始查找,直至找到一个能满足其大小需求的空闲分区为止。然后再按照作业的大小,从该分区中划出一块内存分配给请求者,余下的空闲分区仍留在空闲分区链中。
  该算法倾向于使用内存中低地址部分的空闲分区,在高地址部分的空闲分区非常少被利用,从而保留了高地址部分的大空闲区。显然为以后到达的大作业分配大的内存空间创造了条件。缺点在于低址部分不断被划分,留下许多难以利用、非常小的空闲区,而每次查找又都从低址部分开始,这无疑会增加查找的开销。
  (2)循环首次适应算法。该算法是由首次适应算法演变而成的。在为进程分配内存空间时,不再每次从链首开始查找,而是从上次找到的空闲分区开始查找,直至找到一个能满足需求的空闲分区,并从中划出一块来分给作业。该算法能使空闲中的内存分区分布得更加均匀,但将会缺乏大的空闲分区。
  (3)最佳适应算法。该算法总是把既能满足需求,又是最小的空闲分区分配给作业。
  为了加速查找,该算法需求将所有的空闲区按其大小排序后,以递增顺序形成一个空白链。这样每次找到的第一个满足需求的空闲区,必然是最优的。孤立地看,该算法似乎是最优的,但事实上并不一定。因为每次分配后剩余的空间一定是最小的,在存储器中将留下许多难以利用的小空闲区。同时每次分配后必须重新排序,这也带来了一定的开销。
  (4)最差适应算法。最差适应算法中,该算法按大小递减的顺序形成空闲区链,分配时直接从空闲区链的第一个空闲分区中分配(不能满足需要则不分配)。非常显然,如果第一个空闲分区不能满足,那么再没有空闲分区能满足需要。这种分配方法初看起来不太合理,但他也有非常强的直观吸引力:在大空闲区中放入程式后,剩下的空闲区常常也非常大,于是还能装下一个较大的新程式。
  最坏适应算法和最佳适应算法的排序正好相反,他的队列指针总是指向最大的空闲区,在进行分配时,总是从最大的空闲区开始查寻。
  该算法克服了最佳适应算法留下的许多小的碎片的不足,但保留大的空闲区的可能性减小了,而且空闲区回收也和最佳适应算法相同复杂。