腾讯面试准备

来源:互联网 发布:魔兽世界官方数据库 编辑:程序博客网 时间:2024/06/15 18:01

http://cv.qiaobutang.com/post/55b33fcc0cf2802e2482d64a
http://blog.csdn.net/joejames/article/details/37914379

cat /proc/cpuinfo
cat /proc/meminfo
查看硬盘分区信息 df -lh
查看硬盘的型号信息 cat /proc/scsi/scsi

-2. bit notes
《王道-程序员求职宝典》P132
typedef int func( int , int ); // func是一个函数,而不是一个函数指针
void f1(func);//f1参数是一个函数指针,func会自动转换为函数指针
func f2(int); // 错误,其返回函数
func *f3(int); //f3返回一个函数指针

P128:
string str1;
string str2;
typedef string *pstring;
const pstring cstr = &str1; //等价于string *const cstr;
*cstr = “sdf”; //正确
cstr = &str2; //错误,不能给常量赋值

-1. bss段
bss段的大小,记录在段表里里,记录的是所有未初始化变量总共的大小,bss段只在段表里有个记录,但实现并不存在这个段.
每个未初始化的变量的大小放在了符号表里
如果你说的编译成可执行文件以后,
static int g;
static short int h;
int main(){}
装载时,bss段会和数据段合并。 至于你说的,如何找到bss段中的变量,当编译成可执行文件时,全局变量,静态变量,都会被相应的地址所替代。装载器,只要在相应的地址,分配合适的空间就行了
比如
static int a[100];

装载时,只要在a所对应的地址,就400个字节的空闲就行了。这得需要链接成可执行文件时其它的一些辅助信息,release文件一般把符号给去掉了。

一个位于.bss段,而另一个位于.data段,两者的区别在于:全局的未初始化变量存在于.bss段中,具体体现为一个占位符;全局的已初始化变量存于.data段中;而函数内的自动变量都在栈上分配空间。

.bss是不占用.exe文件空间的,其内容由操作系统初始化(清零);

而.data却需要占用,其内容由程序初始化,因此造成了上述情况。

在《Programming ground up》里对.bss的解释为:There is another section called the .bss. This section is like the data section, except that it doesn’t take up space in the executable.
text和data段都在可执行文件中(在嵌入式系统里一般是固化在镜像文件中),由系统从可执行文件中加载;而bss段不在可执行文件中,由系统初始化。

bss段(未手动初始化的数据)并不给该段的数据分配空间,只是记录数据所需空间的大小。
data(已手动初始化的数据)段则为数据分配空间,数据保存在目标文件中。

数据段包含经过初始化的全局变量以及它们的值。BSS段的大小从可执行文件中得到 ,然后链接器得到这个大小的内存块,紧跟在数据段后面。当这个内存区进入程序的地址空间后全部清零。包含数据段和BSS段的整个区段此时通常称为数据区。

bss段并不占用exe文件的大小
//程序1:
int a[100];
int main()
{
int ret = 0;
return ret;
}

// 程序2
int main()
{
int ret = 0;
return ret;
}

// 程序3
int a[100] = { 0 };
int main()
{
int ret = 0;
return ret;
}

vs2010中程序1、程序2、程序3的exe文件大小相同,即占用相同的磁盘空间

//程序3
int a[100] = { 1, 2, 3 };
int main()
{
int ret = 0;
return ret;
}

程序4的大小大于程序1的大小

0、共享內存原理
http://blog.csdn.net/joejames/article/details/37938035
共享内存段被映射进进程空间之后,存在于进程空间的什么位置?共享内存段最大限制是多少?

存在于进程数据段,最大限制是0x2000000Byte

每個進程都有自己獨立的地址空間,當進程需要訪問一個共享內存時,其會將該共享內存映射到自己的地址空間中來,對該
映射后地址空間的修改就是對共享內存的修改,且其修改會被其他共享該內存的進程看到

具體實現如下:
1)創建一個共享內存時,內核會為每一個新的共享內存維持一個結構,該結構中保存了該共享內存的大小,訪問權限,
當前有多少個進程訪問它,以及一個指針,它指向了该内存段使用的页面(物理内存),還包含了一個指向文件系統中的
文件的字段,當進程需要共享訪問該共享內存時,該進程會会先向虚拟内存系统申请各自的vma_struct,并将其插入到
各自任务的红黑树中,該結構中有一個成員,會指向該共享內存對應文件系統上的文件(依據共享內存id找到共享內存的結構體,
從而找到該文件),最後會調用do_mmap函數完成內存映射,將該共享內存映射到進程的地址空間來。

共享内存定义:
共享内存是最快的可用IPC(进程间通信)形式。它允许多个不相关的进程去访问同一部分逻辑内存。共享内存是由IPC为一个进程创建的一个特殊的地址范围,它将出现在进程的地址空间中。其他进程可以把同一段共享内存段“连接到”它们自己的地址空间里去。

所有进程都可以访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会立刻被有访问同一段共享内存的其他进程看到。因此共享内存对于数据的传输是非常高效的。

枪共享内存的原理
共享内存是最有用的进程间通信方式之一,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。

1.netstat、tcpdump、ipcs、ipcrm
ipcs用来显示当前linux系统中共享内存,信号量,消息队列的使用情况
查看共享信息的内存的命令是ipcs [-m|-s|-q]。
默认会列出共享内存、信号量,队列信息,-m列出共享内存,-s列出共享信号量,-q列出共享队列
ipcs -a 显示共享内存,信号量,消息队列的信息

ipcrm用来删除对应的共享内存段,信号量,消息队列
如:
ipcrm -s semid //删除对应的信号量集
ipcrm -m shmid //删除对应的共享内存段
ipcrm -q msqid // 删除对应的消息队列

  1. cpu内存硬盘等等与系统性能调试相关的命令
    uptime: 显示系统性能和状态,负载
    free:显示内存使用情况
    top: 显示cpu、内存、进程的相关信息,如使用情况,占用率等
    vmstat: 对虚拟内存,进程,cpu活动进行监视;

  2. awk sed

  3. 共享内存实现原理
    http://blog.csdn.net/joejames/article/details/37938035

  4. 源文件经过以下几步生成可执行文件:
    预处理-》编译-》汇编-》链接

6.c++进程内存空间分布
代码段,初始化数据段,未初始化数据段(bss段)、栈、堆,文字常量区
内存可以分为以下几段:

文本段:包含实际要执行的代码(机器指令)和常量。它通常是共享的,多个实例之间共享文本段。文本段是不可修改的。
初始化数据段:包含程序已经初始化的全局变量,.data。
未初始化数据段:包含程序未初始化的全局变量,.bbs。该段中的变量在执行之前初始化为0或NULL。
栈:由系统管理,由高地址向低地址扩展。
堆:动态内存,由用户管理。通过malloc/alloc/realloc、new/new[]申请空间,通过free、delete/delete[]释放所申请的空间。由低地址想高地址扩展。

7、同一线程对同一个锁加锁两次,会等待

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;  int count = 0;  void * thread_func_one(void *arg)  {     int i;     for(i=0;i<10;i++){       pthread_mutex_lock( &mutex1);       pthread_mutex_lock( &mutex1);//锁两次,会阻塞到这里     count++;       sleep(1);       pthread_mutex_unlock(&mutex1);       pthread_mutex_unlock(&mutex1);       printf("thread one count value is %d/n",count);     }     return NULL;  }  

8、线程安全和可重入
http://www.mamicode.com/info-detail-1439450.html

线程安全:概念比较直观。一般说来,一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产生正确的结果。

可重入:概念基本没有比较正式的完整解释,但是它比线程安全要求更严格。根据经验,所谓“重入”,常见的情况是,程序执行到某个函数foo()时,收到信号,于是暂停目前正在执行的函数,转到信号处理函数,而这个信号处理函数的执行过程中,又恰恰也会进入到刚刚执行的函数foo(),这样便发生了所谓的重入。此时如果foo()能够正确的运行,而且处理完成后,之前暂停的foo()也能够正确运行,则说明它是可重入的。

接下来就来看看他们两者之间有什么区别和联系。

可重入与线程安全并不等同。一般说来,可重入的函数一定是线程安全的,但反过来不一定成立。
由此可以看出可重入函数是线程安全函数的一种。可重入性就是无论以什么方式多次调用都不会出现问题,不会出现对可能有修改的静态数据的访问,不会出现对全局变量(比如errno)的访问。严格讲可重入要区分线程安全(弱可重入)还是信号安全(强可重入)两点,但是一般说可重入就是指信号安全。这是由于信号安全要求高于线程安全。

   因此如果一个函数中用到了全局或静态变量,那么它不是线程安全的,也不是可重入的;

访问全局或静态变量时使用互斥量或信号量等方式加锁,则可以使它变成线程安全的,但此时它仍然是不可重入的,因为通常加锁方式是针对不同线程的访问,而对同一线程可能出现问题;

   如果将函数中的全局或静态变量去掉,改成函数参数等其他形式,则有可能使函数变成既线程安全,又可重入。 

eg:strtok函数是既不可重入的,也不是线程安全的;加锁的strtok不是可重入的(并发安全但信号不安全,涉及内部静态变量,但线程安全(并发安全);而strtok_r既是可重入的,也是线程安全的。

可重入的判断条件:

要确保函数可重入,需满足一下几个条件:

1、不在函数内部使用静态或全局数据
2、不返回静态或全局数据,所有数据都由函数的调用者提供。 // 有可能在返回的瞬间,信号来了,在处理函数中修改了全局数据。
3、使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。
4、不调用不可重入函数。

9、

void print_exit()    {           printf("the exit pid:%d/n",getpid() );    }    atexit( print_exit );      //注册该进程退出时的回调函数  这里的print_exit 是函数名还是函数指针呢?答案是函数指针,函数名永远都只是一串无用的字符串。

某本书上的规则:函数名在用于非函数调用的时候,都等效于函数指针。

10、fork写时复制
,fork后,子进程会复制父进程的task_struct结构,并为子进程的堆栈分配物理页。理论上来说,子进程应该完整地复制父进程的堆,栈以及数据空间,但是2者共享正文段。

关于写时复制:由于一般 fork后面都接着exec,所以,现在的 fork都在用写时复制的技术,顾名思意,就是,数据段,堆,栈,一开始并不复制,由父,子进程共享,并将这些内存设置为只读。直到父,子进程一方尝试写这些区域,则内核才为需要修改的那片内存拷贝副本。这样做可以提高 fork的效率。

11、线程的堆栈

说一下线程自己的堆栈问题。

是的,生成子线程后,它会获取一部分该进程的堆栈空间,作为其名义上的独立的私有空间。(为何是名义上的呢?)由于,这些线程属于同一个进程,其他线程只要获取了你私有堆栈上某些数据的指针,其他线程便可以自由访问你的名义上的私有空间上的数据变量。(注:而多进程是不可以的,因为不同的进程,相同的虚拟地址,基本不可能映射到相同的物理地址)

12、进程间通信的方式
1)管道和有名管道
2)信号
3)消息队列 消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
4)共享内存 使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥

5)信号量(用于进程间和进程内线程之间的同步)
6)套接字

13、ELF是什么?其大小与程序中全局变量的是否初始化有什么关系(注意.bss段)
Linux ELF ELF = Executable and Linkable Format,可执行连接格式,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储的标准文件格式
ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)

ELF文件大小与全局变量是否初始化的关系:ELF文件大小不包括.bss段的大小,若全局变量被初始化,则ELF文件大小会增加;

BSS(Block Started by Symbol)通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。特点是:可读写的,在程序执行之前BSS段会自动清0。所以,未初始的全局变量在程序执行之前已经成0了。
注意和数据段的区别,BSS存放的是未初始化的全局变量和静态变量,数据段存放的是初始化后的全局变量和静态变量。

14、如何定位内存泄露?
1)使用内存linux下内存泄露检测工具进行检查
如:Valgrind、使用gdb工具处理core文件(https://my.oschina.net/kaixindewo/blog/28526)
检查是否资源泄漏的办法之一:
在任务管理器 进程 查看 选择列 里面选择:内存使用、虚拟内存大小、句柄数、线程数、USER对象、GDI对象
让你的程序(进程)不退出,循环执行主流程很多遍,越多越好,比如1000000次甚至无限循环,记录以上各数值,再隔至少一小时,越长越好,比如一个月,再记录以上各数值。如果以上两组数值的差较大或随时间流逝不断增加,则铁定有对应资源的资源泄漏!

Linux下用top命令。

一般可以有如下几种方式来避免内存泄露:

1) 使用智能指针,这个在C++中较为常见;

2) 使用内存池;

3) 自己封装一层malloc/free等等。当申请内存时,将申请信息放入到一个已分配内存信息链表里面。free时,删除对应的信息表的节点。在程序执行结束前,扫瞄该信息表,若还存在信息节点,那么该节点记录的就是泄露的内存信息。若链表为空,那就是说没有发生内存泄露;

4)使用检测工具检测内存泄露,进而修补程序,这样的工具有比如Valgrind等等。

15、动态链接和静态链接的区别
动态链接是指在生成可执行文件时不将所有程序用到的函数链接到一个文件,因为有许多函数在操作系统带的dll文件中,当程序运行时直接从操作系统中找。 而静态链接就是把所有用到的函数全部链接到exe文件中。
动态链接是只建立一个引用的接口,而真正的代码和数据存放在另外的可执行模块中,在运行时再装入;而静态链接是把所有的代码和数据都复制到本模块中,运行时就不再需要库了。

16、多线程和多进程的区别
从cpu调度,上下文切换,数据共享,多核cup利用率,资源占用回答
多进程 多线程
内存、CPU: 内存占用多,cpu切换复杂,cpu利用率比较低 内存占用少,切换简单,cpu利用高
上下文切换: 切换开销大,速度慢 切换开销小,速度快

在unix中:

同一个进程中的线程间进行切换,差不多仅需要切换线程的上下文(如寄存器状态等)
切换到已经映射好的进程,同样差多仅需要切换进程的上下文和页表切换
切换到尚未建立映射的进程,除了需要切换上下文、页表切换,更重要的是需要建立映射
不同进程的线程之间切换,开销同上一条
在上面所说的开销中,切换进程上下文开销很小几乎可忽略,而建立映射的开销是很大的。所以我认为线程与进程调度的开销差距主要在于是否需要建立映射。

数据共享: 数据是分开的:共享复杂,需要用IPC;同步简单 多线程共享进程数据:共享简单;同步复杂
创建销毁 创建销毁、切换复杂 创建销毁、速度快

编程调试 编程简单,调试简单 编程复杂,调试复杂
分布式 适应于多核、多机分布;如果一台机器不够,扩展到多台机器比较简单 适应于多核分布

17、线程共享的资源和独有的资源
共享的资源:进程代码段(多个线程可以调用同一个函数)、进程公有数据(全局变量等),进程打开的文件描述符,进程的信号处理器,进程的当前目录,进程用户ID和用户组ID。
独有的资源:线程ID,线程的优先级,线程堆栈,线程的寄存器,错误返回码errno,信号的屏蔽码。

由于每个线程所感兴趣的信号不同,所以线程的信号屏蔽码应该由线程自己管理。但所有的线程都共享同样的信号处理器
good: http://www.cnblogs.com/tracylee/archive/2012/10/29/2744228.html

18、linux常见信号,以及处理方式
SIGINT 程序终止信号,ctrl+c: 用于前台进程组终止进程
SIGQUIT 和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号

SIGABRT
调用abort函数生成的信号

SIGKILL 用来立即结束程序

的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号

SIGCHLD
子进程结束时, 父进程会收到这个信号。
如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情 况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程 来接管)。

SIGTERM
程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。

18、进程退出比较
(1)exit和return 的区别:

a.exit是一个函数,有参数。exit执行完后把控制权交给系统

b.return是函数执行完后的返回。renturn执行完后把控制权交给调用函数。

(2)exit和abort的区别:

a.exit是正常终止进程

b.about是异常终止。

exit函数和_exit函数的区别

_exit()函数的作用最为简单:直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;exit() 函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序,也是因为这个原因,有些人认为exit已经不能算是纯粹的系统调用。

exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是”清理I/O缓冲”。

简单的说,_exit终止调用进程,但不关闭文件,不清除输出缓存,也不调用出口函数。

19、如何实现守护进程
守护进程:后台运行,独立于控制终端,它从被执行的时候开始运转,直到整个系统关闭才退出。
实现代码如下:

if( pid = fork() ) // 父进程退出使之成为后台进程
{
exit(0);
}

setsid(); // 进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。

if( pid = fork() ) // 禁止进程重新打开控制终端现在,进程已经成为无终端的会话组长。但它可以重新申
// 请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端
{
exit(0);
}

for( int i = 0; i < NOFILE; i++ )
{
close(i);
}

umask(0);
chdir(“/tmp”);

20、初始i = 0, 两个线程分别执行i++ 100次,最终i的最小和最大为2,200

21、linux的内存管理机制是什么?

22、linux的任务调度机制是什么?

23、标准库函数和系统调用的区别
标准库函数中可能会调用系统调用,也可能没有调用系统调用
系统调用通过int 0x80从用户态进入内核态
函数库中的某些函数调用了系统调用。
函数库中的函数可以没有调用系统调用,也可以调用多个系统调用。
编程人员可以通过函数库调用系统调用。
高级编程也可以直接采用int 0x80进入系统调用,而不必通过函数库作为中介。
如果是在核心编程,也可以通过int 0x80进入系统调用,此时不能使用函数库。因为函数库中的函数是内核访问不到的。

24、系统如何将一个信号通知到进程?

25、volatile是干啥用的,(必须将cpu的寄存器缓存机制回答的很透彻)

volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。

26、哪些函数是高危函数
http://blog.csdn.net/wenqian1991/article/details/38981819
上面那个函数一样,这是strcpy 的硬伤,就是必须为目标字串分配足够的空间,如果目标字串的长度小于源字串的长度,那么在复制操作的时候会出现缓存溢出。在拷贝字符串的时候没有越界检查,这使得 strcpy 成为一个高危函数
C 语言标准库中strcat 函数同 strcpy 函数一样,没有保证dst 有足够的空间容纳操作后的字符串,也使得strcat 成为一个高危函数。

27、static、const、volatile的作用
static:
1)在函数内,static声明的变量在这一函数被调用过程中,其值维持不变,且不同于auto变量,其内存只被分配一次
2)在函数外,static变量具有文件作用域,即只能被当前模块使用;
3)static声明的函数,只能被这一模块其他函数调用,这个函数被限制在声明它的模块的本地范围内使用;
4)在类中static修饰的变量,表示该变量属于这个类,所有该类对象共享该变量;
5)static成员函数表示该函数属于该类,其参数中没有this指针,故只能访问static成员变量;

注意:常量指针和指针常量的区别
const int *p; //常量指针
int *const p; //指针常量

const:
1)如果想要阻止一个变量被修改,则可以将该变量声明为const变量;
2)对于指针来说,可以指定指针本身为常量,或指针指向的数据为常量,或两者同时指定为const;
3)const修饰函数参数,表明该参数是一个输入参数,在函数内部不能被修改;
4)const修改成员函数,表示该成员函数不能修改类的成员变量;
5)const修饰函数返回值,使得其返回值不能成员“左值”;

volatile:
含义:被其修饰的变量,表明该变量可能随时被改变,它的变化可以不用程序内的任何赋值语句就有可能改变的,
在访问这个变量时,不要进行优化,每次访问都去内存地址处重新读取其值,而不从寄存器中读取;

volatile使用的例子:
1)并行设备的硬件寄存器(如状态寄存器)
2)多线程中被共享的变量
3)中断服务程序中会访问到的非自动变量

一个参数可以既是volatile又是const的么?

volatile修饰符告诉complier变量值可以以任何不被程序明确指明的方式改变,最常见的例子就是外部端口的值,它的变化可以不用程序内的任何赋值语句就有可能改变的,这种变量就可以用volatile来修饰,complier不会优化掉它。
const修饰的变量在程序里面是不能改变的,但是可以被程序外的东西修改,就象上面说的外部端口的值,如果仅仅使用const,有可能complier会优化掉这些变量,加上volatile就万无一失了。

const和volatile同时修饰同一个变量

主要要搞清楚编译期和运行期的关系。
编译期就是C编译器将源代码转化为汇编再到机器代码的过程。
运行期就是实际的机器代码在CPU执行 的过程。很多书上说的东西,其实都只是指编译期进行的事情。const 和 volatile 也一样,所谓的 const ,只是告诉编译器要保证在 C的“源代码”里面,没有对该变量进行修改的地方,就是该变量不能而出现在赋值符号左边。实际运行的时候则不是 编译器 所能管的了。同样,volatile的所谓“可能被修改”,是指“在运行期间”可能被修改。也就是告诉编译器,这个变量不是“只”会被这些 C的“源代码”所操纵,其它地方也有操纵它们的地方。所以,C编译器就不能随便对它进行优化了 。所以每次读它要在它内存中读,不要在寄存器中读备份。

3个经典的volatile问题

1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:
int square(volatile int *ptr)
{
return ptr *ptr;
}
下面是答案:
1). 可以是。例如对于只读的状态寄存器而言,如果它仅仅是volatile类型,那么它还是可能被意想不到的改变。但它还是const的时候,程序就不应该试图去修改它
2). 可以是的。
尽管这种情况并不常见,但它还是可以。一个例子就是:
当一个中断服务子程序企图去修改一个指向一个buffer指针的时候。
3). 这段代码可能有点恶作剧的味道。但它很好说明volatile类型参数的含义和作用。
这段代码的目的是用来返指针*ptr所指向的值的平方,
但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不
是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}

28、STL中快排的改良版本:内省式排序
不当的枢轴选择,导致不当的分割,会使快速排序恶化为 O(n2)。David R.Musser于1996年提出一种混合式排序算法:Introspective Sorting(内省式排序),简称IntroSort

基本思想:http://www.cnblogs.com/fengcc/p/5256337.html
0)取头,尾部,中间三个元素的中间值作为pivot;
1)在递归快速排序中,当元素个数小于等于16时,改用插入排序,插入排序在数组基本有序的情况下,效率较高;
2)若规模大于16,则判断递归调用深度是否超过限制(2lgn)。若已经到达最大限制层次的递归调用,则改用堆排序,
否则则继续递归快排;

http://blog.csdn.net/sky453589103/article/details/51116264

1.为什么需要这种算法

因为快排在面对小数组(比如大小为10的数组)且基本有序的情况下,它的表现还没插入排序要好。因为数组的基本有序,使得插入排序不用很多次的执行元素的移动,并且可以避免递归。 在SGI STL中的函数sort使用的排序算法其实就是内省式的排序算法。内省的排序算法是基于快排实现的。假设待排序的数组大小为n,去一个k值,使得k为满足2^k <= n的最大值。k为最大的递归层次、 为什么要设置最大递归层次呢? 因为快排的递归层次过深的时候,很可能会退化成O(n^2)。内省式排序使用k来控制快排的递归深度,当快排的递归深度到达k的时候选择使用heap排序。

  1. 为什么不一开始就使用heap排序

heap排序在平均时间复杂度是O(nlgn),最坏情况也是O(nlgn),看起来要比快排要快。但是实际上,快排是要比heap排序要快,第一个原因是:heap排序虽然和快排在平均情况下的时间复杂度是O(nlgn),但是heap排序的时间常数要比快排的时间常数要大。第二个原因是:据统计,快排的最坏情况在是很少发生的。第三个原因是:快排能够比较好的吻合程序的空间局部性原理,因为它操作的基本都是相邻的元素(虚拟存储器的设计理论基础中就有程序的时间局部性和空间局部性),能够减少内存缺页中断的发生次数。

3.为什么要使用heap排序呢

因为在递归层次太深的时候,就意味着发生最坏情况的概率大大的提升了,这时候因为heap排序的最坏情况下的时间复杂度是O(nlgn)比快排的O(n^2)要好,因此使用heap排序能更好优化排序效率。

29、为什么一般hashtable的桶数会取一个素数
取素数能减少导致冲突的几率
我个人认为更普遍意义的理解,如果不取素数的话是会有一定危险的,危险出现在当假设所选非素数m=x*y,如果需要hash的key正好跟这个约数x存在关系就惨了,最坏情况假设都为x的倍数,那么可以想象hash的结果为:1~y,而不是1~m。但是如果选桶的大小为素数是不会有这个问题。

30、tcp与udp的区别
1)tcp是有连接的,而udp是无连接的,速度tcp较慢,udp较快;
2)tcp是可靠的,udp是不可靠的,TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证;
3)tcp是基于字节流的,udp是基于数据报的;
4)tcp有TCP的流量控制,拥塞控制;
5)tcp一般用于对可靠性比较高的场合,udp用于对丢包没有多大关系的场合,如视频通话等;
6)应用场合 tcp用于传输大量数据,udp用于少量数据

1.基于连接与无连接
2.对系统资源的要求(TCP较多,UDP少)
3.UDP程序结构较简单
4.流模式与数据报模式
5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证

31、UDP中connect操作与TCP中connect操作有着本质区别
1)TCP中调用connect会引起三次握手,client与server建立连结.UDP中调用connect内核仅仅把对端ip&port记录下来,通过调用connect()建立一个端到端的连接,就可以和TCP一样使用send()/recv()传递数据,而不需要每次都指定目标IP和端口号。但是它和TCP不同的是它没有三次握手的过程。
2)UDP中可以多次调用connect,TCP只能调用一次connect
3)UDP多次调用connect有两种用途:1,指定一个新的ip&port连结. 2,断开和之前的ip&port的连结.
指定新连结,直接设置connect第二个参数即可.
断开连结,需要将connect第二个参数中的sin_family设置成 AF_UNSPEC即可.

UDP中使用connect可以提高效率,原因如下:
普通的UDP发送两个报文内核做了如下:#1:建立连结#2:发送报文#3:断开连结#4:建立连结#5:发送报文#6:断开连结
采用connect方式的UDP发送两个报文内核如下处理:#1:建立连结#2:发送报文#3:发送报文
UDP中使用connect的好处:1:会提升效率.前面已经描述了.2:高并发服务中会增加系统稳定性.原因:假设client A 通过非connect的UDP与server B,C通信.B,C提供相同服务.为了负载均衡,我们让A与B,C交替通信.A 与 B通信IPa:PORTa <—-> IPb:PORTb;

A 与 C通信IPa:PORTa’ <—->IPc:PORTc

假设PORTa 与 PORTa’相同了(在大并发情况下会发生这种情况),那么就有可能出现A等待B的报文,却收到了C的报文.导致收报错误.解决方法内就是采用connect的UDP通信方式.在A中创建两个udp,然后分别connect到B,C.

32、epoll哪些触发模式,有啥区别?
水平触发:fd变为就绪时,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作;
边沿触发:它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读;
所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者 遇到EAGAIN错误。
epoll采用基于事件的就绪通知方式。

33、大规模连接上来,并发模型怎么设计
http://www.cnblogs.com/zlcxbb/p/5756981.html
0)后端采用“epoll+线程池”或者“boost+线程池”来提高并发处理能力。
1)增加机群
2)能不能把CDN用到我们的系统?假设能用,如何把静态的图片、JS文件、HTML文件放在用户最近的一个节点?
3)Nginx负载均衡 + 自己设计的负载均衡策略
4)使用memcached和redis作为数据缓存;

34、tcp头多少字节?哪些字段?

35、socket什么情况下可读?
1)socket的接收缓冲区的数据量大于等于低水位标记;
2)监听socket上有新的连接来了;
3)socket对方关闭连接了,读操作将返回0;
4)socket上有未处理的错误;

socket什么情况下可写?
1)socket内核缓冲区可用字节数大于或等于其低水位标记SO_SNDLOWAT;
2)socket上有待处理的错误;
3)连接的写这一半关闭.对于这样的socket的的写操作将产生信号SIGPIPE;
4)socket使用非阻塞connect连接成功或失败(超时)之后。

35、tcp有接收和发送缓冲区,而udp只有接收缓冲区
每个UDP socket都有一个接收缓冲区,没有发送缓冲区,从概念上来说就是只要有数据就发,不管对方是否可以正确接收,所以不缓冲,不需要发送缓冲区。
UDP:当套接口接收缓冲区满时,新来的数据报无法进入接收缓冲区,此数据报就被丢弃。UDP是没有流量控制的;快的发送者可以很容易地就淹没慢的接收者,导致接收方的UDP丢弃数据报。

36、close和shutdown的区别
使用close中止一个连接,但它只是减少描述符的参考数,并不直接关闭连接,只有当描述符的参考数为0时才关闭连接。
shutdown可直接关闭描述符,不考虑描述符的参考数,可选择中止一个方向的连接。
该函数允许你只停止在某个方向上的数据传输,而一个方向上的数据传输继续进行
在多进程中如果一个进程中shutdown(sfd, SHUT_RDWR)后其它的进程将无法进行通信. 如果一个进程close(sfd)将不会
影响到其它进程
下面摘抄一下unix网络编程的叙述:
shutdown(SHUT_RD):在套接口上不能再发出接受请求;进程仍可往套接口发送数据;套接口接受缓冲区中所有数据被丢弃;再接收到的数据被TCP丢弃,对套接口发送缓冲区无影响.
shutdown(SHUT_WR)在套接口上不能在发出发送请求;进程仍可以从套接口接受数据;套接口发送缓冲区的内容被发送到对端,后跟正常的TCP连接终止序列(发送FIN);对套接口接收缓冲区无任何影响.
close(l_onoff=0缺省状态):在套接口上不能在发出发送或接收请求;套接口发送缓冲区中的内容被发送到对端.如果描述字引用计数变为0;在发送完发送缓冲区中的数据后,跟以正常的TCP连接终止序列(发送FIN);套接口接受缓冲区中内容被丢弃
close(l_onoff = 1, l_linger =0):在套接口上不能再发出发送或接受请求,如果描述子引用计数变为0,RST被发送到对端;连接的状态被置为CLOSED(没有TIME_WAIT状态),套接口发送缓冲区和套接口接受缓冲区的数据被丢弃
close(l_onoff =1, l_linger != 0):在套接口上不能在发出发送或接收请求;套接口发送缓冲区中的内容被发送到对端.如果描述字引用计数变为0;在发送完发送缓冲区中的数据后,跟以正常的TCP连接终止序列(发送FIN);套接口接受缓冲区中内容被丢弃;如果在连接变成CLOSED状态前延滞时间到,那么close返回EWOULDBLOCK错误.

37、connect会阻塞,怎么解决?(必考必问)

最通常的方法最有效的是加定时器;也可以采用非阻塞模式。设置非阻塞,返回之后用select检测状态。

38、列举你所知道的tcp选项,并说明其作用。
1.窗口扩大因子TCP Window Scale Option (WSopt)
TCP窗口缩放选项是用来增加TCP接收窗口的大小而超过65536字节。

2.SACK选择确认选项
最大报文段长度(M S S)表示T C P传往另一端的最大块数据的长度。当建立一个连接时,每一方都有用于通告它期望接收的 M S S选项(M S S选项只能出现在S Y N报文段中)。通过MSS,应用数据被分割成TCP认为最适合发送的数据块,由TCP传递给IP的信息单位称为报文段或段(segment)。
T
CP通信时,如果发送序列中间某个数据包丢失,TCP会通过重传最后确认的包开始的后续包,这样原先已经正确传输的包也可能重复发送,急剧降低了TCP性能。为改善这种情况,发展出SACK(Selective Acknowledgment, 选择性确认)技术,使TCP只重新发送丢失的包,不用发送后续所有的包,而且提供相应机制使接收方能告诉发送方哪些数据丢失,哪些数据重发了,哪些数 据已经提前收到等。

3.MSS: Maxitum Segment Size 最大分段大小

39、这样的话,使用库函数也有系统调用的开销,为什么不直接使用系统调用呢?这是因为,读写文件通常是大量的数据(这种大量是相对于底层驱动的系统调用所实现的数据操作单位而言),这时,使用库函数就可以大大减少系统调用的次数。这一结果又缘于缓冲区技术。在用户空间和内核空间,对文件操作都使用了缓冲区,例如用fwrite写文件,都是先将内容写到用户空间缓冲区,当用户空间缓冲区满或者写操作结束时,才将用户缓冲区的内容写到内核缓冲区,同样的道理,当内核缓冲区满或写结束时才将内核缓冲区内容写到文件对应的硬件媒介。

40、死锁
发生必须具备以下四个必要条件:互斥,请求和保持,環路等待,不可剝奪
1) 预防死锁。

  这是一种较简单和直观的事先预防的方法。方法是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法,已被广泛使用。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率和系统吞吐量降低。

2) 避免死锁。

  该方法同样是属于事先预防的策略,但它并不须事先采取各种限制措施去破坏产生死锁的的四个必要条件,而是在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。

3)检测死锁。

  这种方法并不须事先采取任何限制性措施,也不必检查系统是否已经进入不安全区,此方法允许系统在运行过程中发生死锁。但可通过系统所设置的检测机构,及时地检测出死锁的发生,并精确地确定与死锁有关的进程和资源,然后采取适当措施,从系统中将已发生的死锁清除掉。

4)解除死锁。

 这是与检测死锁相配套的一种措施。当检测到系统中已发生死锁时,须将进程从死锁状态中解脱出来。常用的实施方法是撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程,使之转为就绪状态,以继续运行。死锁的检测和解除措施,有可能使系统获得较好的资源利用率和吞吐量,但在实现上难度也最大。
2.检测死锁并且恢复。
3.仔细地对资源进行动态分配,以避免死锁。
4.通过破除死锁四个必要条件之一,来防止死锁产生。

0 0
原创粉丝点击