后台开发面试题

1.c/c++中static的作用

(1)在修饰变量时,static修饰的静态局部变量只执行初始化一次(在程序执行到该变量声明处时被首次初始化,即以后的函数调用不再进行初始化),而且延长了局部变量的生命周期,直到程序运行结束以后才释放,只有运行包含此静态局部变量时候才能访问它。

(2)static修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其他文件中访问,即便是extern外部声明也不可以,这样其他文件中就可以定义相同名字的变量,不会发生冲突。

(3)static修饰一个函数,则这个函数只能在本文件中调用,不能被其他文件调用,static修饰的变量存放在全局数据区的静态变量区,包括全局静态变量和局部静态变量,都在全局数据区分配内存,初始化的时候自动化为0

(4)static修饰的类变量,可以通过类名.变量名直接引用,而不需要new一个类来,static修饰的类方法,可以通过类名.方法名直接引用,而不需要new出一个类来。被static修饰的类变量和类方法属于类的静态资源,是类实例之间共享的,类的静态成员属于整个类而不是某个对象,静态成员只存储一份供所有对象共用,所以在所有对象中都可以共享它,使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存。不能通过类名来调用类的非静态成员函数,因为类的非静态成员必须必须在类实例化对象后才有内存空间,静态成员函数中不能引用使用非静态函数

2.c/c++中const的作用

(1)const修饰普通类型的变量其值不允许修改,const修饰指针变量有三种情况,第一种const修饰指针指向的内容,则内容为不可变量。第二种const修饰指针,则指针为不可变量,const修饰指针和指针指向的内容,则指针和指针指向的内容都为不可变量.

(2)const参数传递和函数返回值,值传递的const修饰传递,一般不需要const修饰,因为函数会自动产生临时变量复制实参值;当const参数为指针时,可以防止指针被意外篡改;自定义类型的参数传递,需要临时对象复制参数,对于临时对象的构造,需要调用构造函数,比较浪费时间,因此采取const对加引用传递的方法,对于一般的Int、double等内置类型,不采用引用的传递方式。对于const修饰内置类型的返回值,修饰与不修饰返回值作用一样;const修饰的自定义类型作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改;const修饰返回的指针或者引用,是否返回一个指向const的指针,取决与我们想让用户干什么。

(3)const修饰类成员函数,其目的是防止成员函数修改成员变量的值,如果我们不想修改一个调用对象成员变量的值,所有的成员函数都应当声明为const成员函数,如果有成员函数想修改对象某一个成员变量的值,可以用mutable关键字修饰这个成员变量。

3.const和static可以同时修饰一个函数吗?

const关键字不能与static关键字同时使用,因为static关键字修饰静态成员函数,静态成员函数不含有this指针,既不能实例化,const成员函数必须具体到某一实例。

4.malloc和new的区别

(1)malloc和free是C++/C语言的标准库函数,new和delete是C++的运算符,它们都可以申请和释放内存;

(2)对于非内部数据类型的对象而言,光用malloc和free无法满足动态对象的要求,对象的创建的同时要自动执行构造函数,对象的消亡之前要自动执行析构函数,由于malloc/free是库函数而不是运算符,不能执行构造函数和析构函数。

(3)new是从自由存储区上为对象动态分配内存空间,而malloc从堆上动态分配内存,自由存储区不仅可以是堆,还可以是静态存储区;

(4)new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符,而malloc内存分配成功则是返回void*,需要通过强制类型转换将void*指针转换成我们需要的类型

(5)new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL,malloc分配内存失败时返回NULL

(6)使用new操作符申请内存时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式的指出所需内存的尺寸

(7)new和delete可以被重载,malloc和free不可以

5.map的底层数据结构是什么?

Hashmap底层数据结构是哈希表,线程不安全,效率高,LinkHashMap底层数据结构由链表和哈希表组成,由链表保证元素有序,由哈希表保证元素唯一,Hashtable底层数据结构是哈希表,线程安全,效率低,TreeMap底层数据结构是红黑树(是一种自平衡的非严格的平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,TreeMap根据比较的返回值是否是0来决定保证元素唯一性,保证元素的排序方式有两种,第一种自然排序,元素具备比较性,第二种比较器排序

6.map和unorder_map的比较

map优点有序性,内部是用红黑树实现的,红黑树具有自动排序的功能,缺点空间占用率高,对于那些有顺序要求的问题,用map会更高效一些。unorder_map内部用哈希表实现的,因此查找速度快一些执行效率高一些,但是unorder_map占用内存更高一些,map的查找、删除、增加等一系列操作时间复杂度稳定都为logN,缺点就是查找、删除、增加等操作平均时间复杂度较慢,unordered_map底层是哈希表,它的查找、删除、添加的速度快,时间复杂度为常数级O(1),取决于哈希函数,极端情况可能为O(n)

7.解决哈希冲突?

当哈希表关键字集合很大时,关键字的值不同的元素可能会映射到哈希表的同一个地址上,这样的现象叫哈希冲突,解决方法如下:(1)开放定址法:当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。(2)再哈希表:当发生哈希冲突时使用另一个哈希函数计算地址值,直到冲突不再发生,这种方法不易产生聚集,但是增加计算时间,同时需要准备许多哈希函数。(3)链地址法:将所有哈希值相同的Key通过链表存储,Key按顺序插入到链表中。(4)建立公共溢出区:采用一个溢出表存储产生冲突的关键字,如果公共溢出区还产生冲突,再采用处理冲突方法处理

8.红黑树和AVL(平衡二叉树)树的定义、特点以及区别

平衡二叉树左右子树都是平衡二叉树,其左右子树高度之差的绝对值不超过1,红黑树是一种二叉查找树,但在每个节点增加一个存储位表示结点的颜色,可以是红或黑,通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因此,红黑树是一种弱平衡二叉树,相对于要求严格的平衡二叉树,它的旋转次数少,插入最多两次旋转,删除最多三次旋转,所以对于搜索,插入,删除操作比较多的情况下,通常使用红黑树。红黑树在查找,插入删除的性能都是O(logN)

9.请介绍一下B+树

B+树是一种多路搜索树,主要为磁盘或其他存取辅助设备而设计的一种平衡查找树,在B+树中,每个节点可以有多个孩子,并且按照关键字大小有序排列,所有记录结点都是按照键值的大小顺序存放在同一层的叶结点中,相比于B树,具有以下特点:每个结点上的指针上限为2d而不是2d+1(d为结点的出度),内结点不存储data,只存储key,叶子结点不存储指针

10.什么是多态?

多态就是不同对象对同一行为会有不同的状态(举例:学生和成人都去买票时,学生会打折,成人不会),实现多态有两个条件:(一)是虚函数重写,重写就是用来设置不同状态的。(二)对象调用虚函数时必须是指针或者引用,用父类的指针指向子类的对象

8.什么是虚函数?什么是重写?

虚函数是带有virtual关键字的成员函数,子类有个和父类完全相同的虚函数,就称子类虚函数重写父类虚函数

9.重载、重写和隐藏

重载:是指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。

隐藏:是指派生类的函数屏蔽了与其同名的基类函数,注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏。

重写(覆盖):是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有函数体不同(花括号内),派生类调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰。

9.多态的原理?

多态是用虚函数表实现的,有虚函数的类都会生成一个虚函数表,这个表在编译时生成,虚函数表是一个存储虚函数地址的数组,以NULL结尾,如果要生成子类虚函数表。要经过三个步骤,第一步将父类虚表内容拷贝到子类虚表上,第二步将子类重写的虚函数地址覆盖掉表中父类的虚函数地址,第三步如果子类有新增加的虚函数,按声明次序加到最后

10.多态如何调用?

满足多态的函数调用,程序运行起来后,根据对象中的虚函数表指针来找实际应该调用的函数,而不满足多态的函数在函数编译时就确定函数地址了

11.inline函数可以是虚函数吗?

不可以,因为inline函数没有地址,无法将它存放到虚函数表中,放在虚函数表中的函数才是虚函数

12.静态成员可以是虚函数吗?

不可以,因为静态成员函数没有this指针,使用::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表

13.构造函数可以是虚函数吗?

不可以,因为对象中的虚函数指针是在对象构造的时候初始化的,从存储空间角度,虚函数对应一个指向虚函数表的指针,可是这个指向虚函数表的指针其实是存储在对象的内存空间的,问题出来了,如果构造函数是虚的,就需要通过虚函数表来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找虚函数表呢?所以构造函数不能是虚函数。

14.析构函数可以是虚函数吗?什么场景下析构函数是虚函数?

可以,最好是将父类的析构函数设置为虚函数,因为这样可以避免内存泄漏的问题,如果是多态的,会先去调用子类的析构函数,然后再去调用父类的析构函数,不然只会去调用父类的析构函数,造成内存泄漏问题。

析构函数的作用与构造函数正好相反,是在对象的生命期结束时,释放系统为对象所分配的空间,既要撤销一个对象,用对象指针来调用一个函数,有以下两种情况:

1.如果是虚函数,会调用派生类中的版本(在有派生类的情况下)

2.如果是非虚函数,会调用指针所指类型的实现版本

析构函数也会遵循以上两种情况,因为析构函数也是函数嘛,不要把它看得太特殊,当对象出了作用域或是我们删除对象指针,析构函数就会被调用,当派生类对象出了作用域,派生类的析构函数会先调用,然后再调用它父类的析构函数,这样能保证分配给对象的内存得到正确释放,但是,如果我们删除一个指向派生类对象的基类指针,而基类析构函数又是非虚的话,那么就会先调用基类的析构函数

15.对象访问普通函数快还是虚函数快?

如果是普通对象,是一样快,如果是指针对象或者是引用对象,调用普通函数更快一些,因为构成了多态,运行时调用虚函数要先到虚函数表中去查找,这样然后才拿到函数的地址,这样就不如直接可以拿到函数地址的普通函数快。

16.虚函数表是在什么阶段生成的?它存放在哪里?

虚函数表是在编译阶段生成的,它一般存放在代码段,也就是常量区

17.虚函数表指针被编译器初始化的过程是怎么理解的?

当类中声明了虚函数时,编译器会在类中生成一个虚函数表,VS编译器是存放在代码段,虚函数表实际上就是一个存放虚函数指针的指针数组,是由编译器自动生成并维护的,虚表是属于类的,不属于某个具体的对象,一个类中只需要一个虚表即可,同一个类中的所有对象使用同一个虚函数表,为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器会在每个对象的头添加一个指针,用来指向虚函数表,并且这个指针的值会自动被设置成指向类的虚函数表,每一个virtual函数的函数指针存放在虚表中,如果是单继承,先将父类的虚表添加到子类的虚表中,然后子类再添加自己新增的虚函数指针,但是在VS编译器中我们通常看不到新添加的虚函数指针,是编译器故意把它们隐藏起来,如果是多继承,在子类新添加的虚函数指针会存放在第一个继承父类的虚函数表中。

18.为什么要引入抽象类和纯虚函数?

为了方便使用多态特性,在很多情况下由基类生成对象是很不合理的,纯虚函数在基类是没有定义的,要求在子类必须加以实现,这种包含了纯虚函数的基类被称为抽象类,不能被实例化,如果子类没有实现纯虚函数,那么它也是一个抽象类

19.虚函数和纯虚函数有什么区别?

从基类的角度出发,如果一个类中声明了虚函数,这个函数是要在类中实现的,它的作用是为了能让这个函数在他的子类中被重写,实现动态多态。纯虚函数,只是一个接口,一个函数声明,并没有在声明他的类中实现,对于子类来说它可以不重写基类中的虚函数,但是他必须要将基类中的纯虚函数实现,虚函数既继承接口的同时也继承了基类的实现,纯虚函数关注的是接口的统一性,实现完全由子类来完成。

20.什么是多态?它有什么作用?

多态就是一个接口多种实现,多态是面向对象的三大特性之一,多态分为静态多态和动态多态,静态多态包含函数重载和泛型编程,静态多态是程序调用函数,编译器决定使用哪个可执行的代码块,动态多态是由继承机制以及虚函数实现的,通过指向子类的父类指针或者引用,访问子类中同名重写成员函数,多态的作用就是把不同子类对象都当做父类来看,可以屏蔽不同子类之间的差异,从而写出通用的代码,做出通用的编程,以适应需求的不断变化,有程序解耦合的作用。

21.TCP和UDP的特点和区别?

TCP全双工,通过三次握手建立连接,保证数据无差错连接,,面向字节流,可能出现黏包问题,TCP首部开销20字节,UDP单双工,UDP数据包括目的端口号和源端口号,不需要建立连接,UDP是面向报文的不会出现黏包问题,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低,首部只有8个字节,TCP连接只能是点到点的,UDP支持一对一,一多,多对一和多对多通信

22.为什么TCP建立连接需要三次,而释放连接则需要四次?

因为TCP是全双工模式,客户端请求关闭连接后,客户端向服务器的连接关闭(一二次挥手),服务端继续传输之前没有传完的数据给客户端,服务端向客户端的连接关闭(三四次挥手),所以TCP释放连接时服务器的ACK和FIN是分开发送的(中间隔着数据传输),而TCP建立连接时服务器的ACK和SYN是一起发送的(第二次握手),所以TCP建立连接需要三次,而释放连接则需要四次

23.为什么TCP连接时可以ACK和SYN一起发送,而释放时ACK和FIN分开发送呢?

因为客户端请求释放时,服务器可能还有数据需要传输给客户端,因此服务端要先响应客户端FIN请求(服务端发送ACK),然后数据传输,传输完成后,服务端再提出FIN请求(服务端发送FIN),而连接时则没有中间的数据传输,因此连接时可以ACK和SYN一起发送

24.四次挥手客户端释放最后为什么不直接关闭要进入等待计时器设置的2MSL等待状态

(1)保证客户端发送的ACK报文段能够到达服务器,从而保证TCP连接能够进行可靠的关闭,如果客户端发送ACK后立刻关闭,那么如果ACK丢失的话,服务端就会一直处于等待关闭确认的状态,超时后再发送关闭请求时,此时的客户端已经关闭,那么服务端就无法进行正常的关闭,确保有足够的时间让服务端收到ACK包,如果服务端没有收到ACK,服务端会重发FIN包,这样正好是2MSL

(2)避免新旧连接混淆,避免跟后边的其他连接混在一起,因为有些路由器会缓存IP数据包,服务端延迟收到的包,就有可能跟新连接的包混在一起,防止已失效的连接请求报文段会出现在本连接中,TIME-WAIT持续2MSL可使本连接持续的时间内所产生的所有报文段都从网络中消失,这样可使下次连接中不会出现旧的连接报文段

25.为什么等待的时间是2MSL?

客户端发出ACK,等待ACK到达对方的超时时间MSL,等待FIN的超时重传也是MSL,所以如果2MSL时间内没有收到FIN,说明对方安全收到ACK。

26.说一下GET和POST的区别

对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据),而对于POST,浏览器先发送header,服务器响应100continue,浏览器再发送data,服务器响应200 ok(返回数据)

27.在浏览器地址栏键入URL的流程

(1)浏览器向DNS服务器请求解析该URL中的域名所对应的IP地址(2)解析出IP地址后,根据该IP地址和默认端口80,和服务器建立TCP连接(3)浏览器发出读取文件(URL域名后面部分对应的文件)的HTTP请求,该请求报文作为TCP三次握手的第三个报文的数据发送给服务器(4)服务器对浏览器请求做出响应,并把对应的html文本发送给浏览器(5)释放TCP连接(6)浏览器将该html文本解析并显示内容

28.进程和线程

进程是系统进行资源的调度和分配的基本单位,实现操作系统的并发,线程是CPU调度和分配的基本单位,实现进程内部的并发,每个进程都有一个自己的地址空间,至少有5种基本状态,它们是:初始态,就绪态,等待态,执行态,终止态。线程有就绪、阻塞和运行三种基本状态,计算机操作系统有两个重要的概念,并发和隔离,并非是为了尽量让硬件利用率高,线程是为了在系统层面做到并发,线程上下文切换效率比进程上下文切换会高很多,这样可以提高并发效率。计算机资源一般是共享的,隔离要能保证崩溃了的这些资源能够被回收,不影响其他代码使用。

区别:

根本区别:进程是资源分配的最下单位,线程是CPU调度和分配的最小单位

包含关系:线程是进程的一部分,所以线程也被称为轻量级进程

地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间

内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间,而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源,同一进程内的线程共享本进程的资源如内存、I/O、CPU等,但是进程之间的资源是独立的

在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销,线程可以看作轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小

线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于共享数据段所以通信机制很方便

所处环境:在操作系统中能同时运行多个进程,而在同一个进程中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)

健壮性方面:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉,所以多进程要比多线程健壮

执行过程:每个独立的进程都有一个程序的入口,可以独立运行,但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制

并发:两者均可并发执行

29.进程之间的通信方式,线程之间的通信方式

进程通信:

(1)管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,需要双方通信时,需要建立起两个管道,而且只能在具有亲缘关系的进程间使用,进程的亲缘关系通常是父子进程关系。通过内存缓冲区实现数据传输

(2)有名管道(FIFO):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。在磁盘上有对应的节点,但没有数据块,换言之,只是拥有一个名字和相应的访问权限,一旦建立,任何进程都可以通过文件名将其打开和进行读写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权,当不再被进程使用时,FIFO在内存中释放,但磁盘节点仍然存在。

管道的实质是一个内核缓冲区,进程以先进先出的方式以缓冲区存取数据:管道一端的进程顺序的将进程数据写入缓冲区,另一端的进程则顺序的读取数据,该缓冲区可以看作一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,读出以后在缓冲区都不复存在了,当缓冲区读空或者写满时,有一定的规则控制相应的读进程或写进程是否进入等待队列,当空的缓冲区有新数据写入或慢的缓冲区有数据读出时,就唤醒等待队列中的进程继续读写

(3)信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问,它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源,因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

(4)消息队列是一个消息的链表,是一系列保存在内核中消息的列表,用户进程可以向消息队列添加消息,也可以向消息队列读取消息,消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息,可以把消息看做是一个记录,具有特定的格式以及特定的优先级,对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息,对消息队列有读权限的进程可以从消息队列中读取消息,消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

(5)信号:信号用于通知接收进程某个事件已经发生。信号可以在任何时候发送给某一进程,而无须知道该进程的状态,如果该进程并未处于执行状态,则该信号就由内核保存起来,直到该进程恢复执行并传递给它为止,如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式,信号可以在用户空间进程和内核之间直接交互,内核也可以利用信号来通知用户空间的进程来通知用户空间发生了哪些系统事件,信号时间有两个来源:(1)硬件来源(2)软件来源

(6)共享内存:共享内存允许两个或多个进程共享一个给定的存储区,这一段存储区可以被两个或两个以上的进程映射至自身的地址空间中,一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存读取金额读出,从而实现了进程间的通信,共享内存就是映射一段能被其他进程所访问的内存,和其他通信机制配合使用,如信号量,来实现进程间的同步和通信。消息队列和管道基本上都是4次拷贝,而共享内存(mmap, shmget)只有两次。

4次: 1,由用户空间缓冲区中将数据拷贝到内核空间缓冲区中
          2,内核空间缓冲区将数据拷贝到内存中
          3,内存将数据拷贝到到内核缓冲区
          4,内核空间缓冲区到用户空间缓冲区.
2次: 1,用户空间到内存
          2,内存到用户空间。

(7)套接字:套接字用于不同设备及其间的进程通信

线程通信:(1)锁机制:包括互斥锁,读写锁、条件变量,互斥锁提供了以排他方式防止数据结构被并发修改的方法,读写锁允许多个线程同时读共享数据,而对写操作是互斥的,条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止,对条件的测试是在互斥锁的保护下进行的,条件变量始终与互斥锁一起使用。(2)信号量机制:包括无名线程信号量和命名线程信号量。(3)信号机制:类似进程间的信号处理,线程间的通信目的主要用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

30.死锁产生的四个必要条件

(1)互斥条件:一个资源每次只能被一个进程使用。(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。(3)不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。(4)循环等待条件:若干进行之间形成一种头尾相接的循环等待资源关系

31.避免死锁的方法

  1. 破坏“请求与保持”条件

方法1:所有的进程在开始运行之前,必须一次性的申请在整个运行过程中所需要的全部资源

缺点:因为某项资源不满足进程无法启动,而其他已经满足了的资源也不会得到利用,严重降低了资源的利用率,造成资源浪费,使进程经常发生饥饿现象

方法2:该方法是第一种方法的改进,允许进程只获得运行初期需要的资源, 便开始运行,在运行过程中逐步释放掉分配到的已经使用完毕的资源,然后再去请求新的资源,这样的话资源利用率会得到提高,也会减少进程的饥饿问题

     2. 破坏“不可剥夺”条件

当一个已经持有了一些资源的进程在提出新的资源请求没有得到满足时,它必须释放已经保持的所有资源,待以后需要使用的时候再重新申请,这就意味着进程已占有的资源会被短暂的释放或者说是被抢占了,该方法实现起来比较复杂,且代价也比较大,释放已经保持的资源很有可能会导致进程之前的工作实效等,反复的申请和释放资源会导致进程的执行被无限的推迟,这不仅会延迟进程的周转周期,还会影响系统的吞吐量

     3. 破坏“循环等待”条件

可以通过定义资源类型的线性顺序来预防,可将每个资源编号,当一个进程占有编号为i的资源时,那么它下一次申请资源只能申请编号大于i的资源,这样虽然避免了循环等待,但是这种方法是比较低效的,资源的执行速度会变慢, 并且可能在没有必要的情况下拒绝资源的访问,比如说,进程c想要申请资源 1,如果资源1并没有被其他进程占有,此时将它分配给进程c是没有问题的,但是为了避免产生循环等待,该申请会被拒绝,这样就降低了资源的利用率

31.说一说用户态和内核态区别

用户态和内核态是操作系统的两种运行级别,两者最大的区别就是特权级不同,用户态拥有最低的特权级,内核态拥有较高的特权级,运行在用户态的程序不能直接访问操作操作系统内核数据结构和程序,内核态和用户态之间的转换方式主要包括:系统调用,异常和中断。

32.SQL优化有哪些方法?

通过建立索引对查询进行优化,避免全表扫描,DB在执行一条sql语句时候,默认的方式是根据搜索条件进行全表扫描,遇到匹配条件的就加入搜索结果集合,如果我们对某一字段增加索引,查询时就会先去索引列表中一次定位到特定值的行数,大大减少遍历匹配的行数,所以能明显增加查询的速度

33.什么是索引?

索引其实就是一种单独的、物理的数据结构,索引文件包含两部分信息,一是数据库每条记录的索引关键字的值,二是其对应的记录号,只要给出索引关键字,就可以在索引文件中查到相应的记录号,然后在数据库中将记录指针迅速移动到对应的记录上,数据库索引可以是:顺序索引,B+树索引,hash索引。索引越多,更新的数据的速度越慢,当修改性能远远大于检索性能时,不应该创建索引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。

34.索引是不是越多越好?

(1)首先数据量小的表不需要建立索引,因为小的表即使建立索引也不会有大的用处,还会增加额外的索引开销
(2)不经常引用的列不要建立索引,因为不常用,即使建立了索引也没有多大意义
(3)经常频繁更新的列不要建立索引,因为肯定会影响插入或更新的效率
(4)索引并不是一劳永逸的,用的时间长了需要进行整理或者重建

35.数据库事务的一致性

事务是由一系列对数据库中数据进行访问与更新的操作所组成的一个程序执行逻辑单元,事务是DBMS中最基础的耽误,事务不可分割,4个基本特征:原子性,一致性,隔离性,持久性

数据库事务的隔离级别有4种,由低到高分别为Read uncommitted(读未提交,就是一个事务可以读取另一个未提交事务的数据。) 、Read committed(读提交,就是一个事务要等另一个事务提交后才能读取数据。) 、Repeatable read(重复读,就是在开始读取数据(事务开启)时,不再允许修改操作) 、Serializable(序列化,Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

) 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读。

36.排序算法及其复n杂度

O(n2):直接插入排序、直接选择排序、冒泡排序

O(nlogN):希尔排序、堆排序、快速排序、归并排序

稳定:直接插入排序、冒泡排序、归并排序

不稳定:直接选择排序、希尔排序、堆排序、快速排序

37.说一说多线程的同步,锁的机制

同步的时候用一个互斥量,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁,对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁,如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用,在这种方式下,每次只有一个线程可以向前执行

38.C++的锁你知道几种

锁包括互斥锁,条件变量,自旋锁和读写锁,生产者消费者问题利用互斥锁和条件变量可以很容易解决,条件变量这里替代信号量的作用

39.如何实现线程池

1.设置一个生成者消费者队列,作为临界资源

2.初始化N个线程,并让其运行起来,加锁去队列取任务运行

3.当任务队列为空的时候,所有线程阻塞

4.当生成者队列来了一个任务后,先对队列加锁,把任务挂在队列上,然后使用条件变量去通知阻塞中的一个线程

40.socket网络编程的步骤

服务端:创建socket——>bind绑定ip和端口信息——>设置最大连接数,监听listen——>接收连接accept

客户端:创建socket——>设置要连接的对方ip和端口信息——>连接服务器connect

41.搜索baidu,会用到计算机网络中的什么层?每层是干什么的

浏览器中输入URL,浏览器要将URL解析为IP地址,解析域名就要用到DNS协议,首先主机会查询DNS的缓存,如果没有就给本地DNS服务器发送查询请求。DNS查询分为两种方式,一种是递归查询,一种是迭代查询。如果是迭代查询,本地的DNS服务器,向根域名服务器发送查询请求,根域名服务器告知该域名的一级域名服务器,然后本地服务器给该一级域名服务器发送查询请求,然后依次类推直到查询到该域名的IP地址。DNS服务器是基于UDP的,因此会用到UDP协议。得到IP地址后,浏览器就要与服务器建立一个http连接。因此要用到http协议,http协议报文格式上面已经提到。http生成一个get请求报文,将该报文传给TCP层处理,所以还会用到TCP协议。如果采用https还会使用https协议先对http数据进行加密。TCP层如果有需要先将HTTP数据包分片,分片依据路径MTU和MSS。TCP的数据包然后会发送给IP层,用到IP协议。IP层通过路由选路,一跳一跳发送到目的地址。当然在一个网段内的寻址是通过以太网协议实现(也可以是其他物理层协议,比如PPP,SLIP),以太网协议需要直到目的IP地址的物理地址,有需要ARP协议。

(1)DNS协议,http协议,https协议属于应用层

应用层是体系结构中的最高层。应用层确定进程之间通信的性质以满足用户的需要。这里的进程就是指正在运行的程序。应用层不仅要提供应用进程所需要的信息交换和远地操作,而且还要作为互相作用的应用进程的用户代理,来完成一些为进行语义上有意义的信息交换所必须的功能。应用层直接为用户的应用进程提供服务。

(2)TCP/UDP属于传输层

传输层的任务就是负责不同主机中两个进程之间的通信。因特网的传输层可使用两种不同协议:即面向连接的传输控制协议TCP,和无连接的用户数据报协议UDP。面向连接的服务能够提供可靠的交付,但无连接服务则不保证提供可靠的交付,它只是“尽最大努力交付”。这两种服务方式都很有用,备有其优缺点。在分组交换网内的各个交换结点机都没有传输层。

(3)IP协议,ARP协议属于网络层

网络层负责为分组交换网上的不同主机提供通信。在发送数据时,网络层将运输层产生的报文段或用户数据报封装成分组或包进行传送。在TCP/IP体系中,分组也叫作IP数据报,或简称为数据报。网络层的另一个任务就是要选择合适的路由,使源主机运输层所传下来的分组能够交付到目的主机。

(4)数据链路层

当发送数据时,数据链路层的任务是将在网络层交下来的IP数据报组装成帧,在两个相邻结点间的链路上传送以帧为单位的数据。每一帧包括数据和必要的控制信息(如同步信息、地址信息、差错控制、以及流量控制信息等)。控制信息使接收端能够知道—个帧从哪个比特开始和到哪个比特结束。控制信息还使接收端能够检测到所收到的帧中有无差错。

(5)物理层

物理层的任务就是透明地传送比特流。在物理层上所传数据的单位是比特。传递信息所利用的一些物理媒体,如双绞线、同轴电缆、光缆等,并不在物理层之内而是在物理层的下面。因此也有人把物理媒体当做第0层。

44.请问海量数据如果去取最大的K个数据

(1)直接全部排序(只适用于内存够的情况)

(2)快速排序的变形——选择一个划分元比它大的放前面,比太小的放后面

(3)最小堆法——先读取前K个数,建立一个最小堆,然后将剩余的所有数字依次与最小堆的堆顶进行比较,如果小于或等于堆顶数据,则继续比较下一个,否则删除堆顶元素,并将新数据插入堆中,重新调整最小堆,当遍历完全部数据后,最小堆的数据即为最大的K个树

(4)分治法——将全部数据分为N份,找到每份数据中最大的K个数,此时再进行排序处理,如果内存不够,每一份再分成M份

(5)Hash法——如果有重复数据先通过Hash法进行去重,再进行其他方法进行处理

46.了解哪些设计模式?

(1)单例模式:单例模式主要解决一个全局使用的类频繁的创建和销毁的问题,单例模式下可以确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,单例模式有三个要素,一是某个类只能有一个实例,二是它必须自行创建这个实例,三是它必须自行向整个系统提供这个实例

(2)工厂模式:工厂模式主要解决接口选择的问题,该模式下定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,使其创建过程延迟到子类进行,tensorflow的模型类就很多用到工厂模式

(3)观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新

(4)装饰器模式:对已经存在的某些类进行装饰,以此来扩展一些功能,从而动态的为一个对象增加新的功能,装饰器模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能,使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀

47.地址解析协议ARP

无论网络层使用什么协议,在实际网络的链路上传送数据帧时,最终必须使用硬件地址,所以需要一种方法来完成IP地址到MAC地址的映射,这就是地址解析协议ARP,每个主机都设有一个ARP高速缓存,存放本局域网上各主机和路由器的IP地址到MAC地址的映射表,称ARP表,使用ARP协议来动态维护此ARP表。

ARP工作在网络层,其工作原理:当主机A欲向本局域网上的某个主机B发送IP数据报时,就先在其ARP高速缓存中查看有无主机B的IP地址,如果有,就可查出其对应的硬件地址,再将此硬件地址写入MAC帧,然后通过局域网将该MAC帧发往此硬件地址,如果没有,就通过使用目的MAC地址为FF-FF-FF-FF-FF-FF的帧来封装并广播ARP请求分组,可以使同一个局域网里的所有主机收到ARP请求,当主机B收到该ARP请求后,就会向主机A发出响应ARP分组,分组中包含主机B的主机IP与MAC地址的映射关系,主机A收到后将此映射写入ARP缓存中,然后按查询到的硬件地址发送MAC帧

注意:ARP是解决同一个局域网上的主机或路由器的IP地址和硬件地址的映射问题。如果所要找的主机和源主机不在同一个局域网上,那么就要通过ARP协议找到一个位于本局域网上的某个路由器的硬件地址,然后把分组发送给这个路由器,让这个路由器把分组转发给下一个网络,剩下的工作就由下一个网络来做,尽管ARP请求分组是广播发送的,但是ARP响应分组是普通的单播,即从一个源地址发送到一个目的地址

48.动态主机配置协议DHCP

动态主机配置协议DHCP常用于给主机动态的分配IP地址,它提供了即插即用联网的机制,这种机制允许一台计算机加入新的网络和获取IP地址而不用手工参与,DHCP是应用层协议,它是基于UDP的

DHCP协议工作原理:它使用客户/服务器方式,需要IP地址的主机在启动时就向DHCP服务器广播发送广播报文,这时该主机就成为DHCP客户,本地网络上所有主机都能收到此广播报文,但只有DHCP服务器才回答此广播报文,DHCP服务器先在其数据库中查找该计算机的配置信息,若找到,则返回找到的信息,若找不到,则从服务器的IP地址池中取一个地址分配给该计算机,DHCP服务器的回答报文叫做提供报文。

49.网际控制报文协议ICMP

为了提高IP数据报交付成功的机会,在网络层使用了网际控制报文协议ICMP来允许主机或路由器报告差错和异常情况,ICMP报文作为IP层数据报的数据,加上数据报的首部,组成IP数据报发送出去,ICMP协议是IP网络层协议,ICMP报文种类有两种,即ICMP差错报文和ICMP询问报文,ICMP差错报告报文用于目标主机或到目标主机路径上的路由器向源主机报告差错和异常情况,五种类型(1)终点不可达(2)源点控制(3)时间超时(4)参数问题(5)改变路由(重定向)。ICMP询问报文有四种类型(1)回送请求和回答报文(2)时间戳请求和回答报文(3)掩码地址请求和回答报文(4)路由器询问和通告报文

ICMP的两个常见的应用的分组网间探测PING(用来测试两个主机之间的连通性)和traceroute(Unix中的名字,Windows中是tracert,可以用来跟踪分组经过的路由)。其中PING使用了ICMP回送请求和回答报文,traceroute使用了ICMP时间超过报文

注意:PING工作在应用层,它直接使用网络层的ICMP协议,而没有使用传输层的TCP和UDP协议,traceroute工作在网络层

50.编译过程

 (1)预处理(Preprocessing)——将所有的“#define”删除,并且展开所有的宏定义,处理所有条件预编译指令,处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置,删除所有的注释,添加行号和文件名标识,保留所有的#pragma编译器指令                                                                                                                                                                                                     (2)编译(Complication)——预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后产生相应的汇编代码文件         (3)汇编(Assembly)——将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令                                 (4)链接(Linking)—— 链接的主要内容就是将各个模块之间相互引用的部分正确的衔接起来。它的工作就是把一些指令对其他             符号地址的引用加以修正。链接过程主要包括了地址和空间分配、符号决议和重定向

51.Linux配置ip地址的命令是什么?

1.临时修改IP地址(重启后失效)——ifconfig 网卡名称 IP地址

2.通过修改网卡配置文件修改IP地址——vim /etc/sysconfig/network-scripts/ifcfg-网卡名称

52.物理地址和虚拟地址

(1)每个进程的4G内存空间只是虚拟内存空间,每次访问内存空间的某个地址,都需要把地址翻译为实际物理内存地址

(2)所有进程共享同一物理内存,每个进程只把自己目前需要的虚拟内存空间映射并存储到物理内存上

(3)进程要知道哪些内存地址上的数据在物理内存上,哪些不在,还有在物理内存上的哪里,需要用页表来记录

(4)页表的每一个表项分两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址

(5)当进程访问某个虚拟地址,去看页表,如果发现对应的数据不在物理,则缺页异常

(6)缺页异常的处理过程,就是把进程需要的数据从磁盘拷贝到物理内存中,如果内存已经满了,没有地方了,那就找一个页覆盖,当然如果被覆盖的页曾经被修改过,需要将此页写回磁盘

53.线程是否拥有资源

线程自己持有的只有状态和计数器,同一个进程内的线程共享这个进程的资源 ,线程不占系统资源但是不代表不占用资源,线程就是一种系统的中断方式,由系统的时间片来确定中断运行的时间,如果有多个线程那么相当与多个程序同时运行,每一个程序都有要有自己的堆和栈,肯定要多占内存。而在线程在创建时cpu的工作模式模式在转换,所以开辟线程cpu占用率会升高或者满载,开辟之后线程与线程间切换也需要消耗cpu占有率的。

54.常见的进程调度算法?

(1)先来服务调度算法——先来先服务调度算法是一种最简单的调度算法,该算法既可用于作业调度,在进程调度中采用先来先服务算法时,则每次调度是从就绪队列中选择一个最先进入该队列的进程,为之分配处理器,使之投入运行,该进程一直运行到完成或发生某事件而阻塞后才放弃处理机

(2)短进程优先调度算法——是从就绪队列选出一个估计运行时间最短的进程,将处理机分配给它,使它立即执行并一直执行到完成,或发生某事件而被阻塞放弃处理机时再重新调度

(3)高优先权优先调度算法——从就绪队列中选择优先权最高的进程,分为1.非抢占式优先权算法——在这种方式下,系统一旦把处理机分配给就绪队列中优先权最高的进程后,该进程便一直运行下去,直至完成,或因发生某事件使该进程放弃处理机时,系统方可再将处理机重新分配给另一优先权最高的进程,这种调度算法主要用于批处理系统中。2.抢占式优先权调度算法——在这种方式下,系统同样是把处理机分配给优先权最高的进程,使之执行,但在其执行期间,只要又出现另一个其优先权更高的进程,进程调度算法就立即停止当前进程的执行,重新将处理机分配给新的优先权最高的进程。

(4)基于时间片的轮转调度算法——1.时间片轮转法,系统将所有的就绪进程按先来先服务的原则排成一个队列,每次调度时,把CPU分配给队首进程,并令其执行一个时间片。2.多级反馈队列调度算法,应设置多个就绪队列,并为每个队列赋予不同的优先级,第一个队列优先级最高,依次次之,该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权越高的队列中,为每个进程所规定的执行时间片就越小;当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度,当轮到该进程执行时,如果能在该时间片内完成,便可准备撤离系统,如果在一个时间片内尚未完成,调度程序便将该进程转入第二队列的末尾;仅当第一队列空闲时候,调度程序才调度第二队列中的进程运行

55.什么是堆,堆排序实现原理?

堆就是利用完全二叉树的结构来维护的一维数组。堆是一种完全二叉树(不是平衡二叉树,也不是二分搜索树),完全二叉树除了最底层,每一层都是满的,这使得堆可以利用数组来表示,i结点的父结点下标就为(i-1)/2,它的左右子结点下标分别为2i+1和2i+2,最大堆和最小堆,特点是父节点的值大于(小于)两个小节点的值。

堆排序的基本思想是:以最大堆为例,先根据序列还原构造一棵完全二叉树,将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点,将其与末尾元素进行交换,此时末尾为最大值,然后将剩余n-1个元素重新构造成一个大顶堆,这样根节点会是n个元素中的第二大值,将其与n-1的元素互换,将剩下的n-2个元素继续建堆,如此反复执行,便能得到一个有序的升序序列了(反之,降序则建立小顶堆),建立最大堆时是从最后一个非叶子节点开始从下往上调整的

总结堆排序的基本思路:(1)将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;(2)将堆顶元素与末尾元素交换,将最大(小)元素“沉”到数组末端;(3)重新调整结构,使其满足最大堆或最小堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序

56.C++的浅拷贝和深拷贝

在使用一个对象对另一个对象初始化或赋值时,若对象包含指针成员变量,则需要手动的编写拷贝构造函数实现深拷贝,调用编译器内部默认的拷贝构造函数只能实现浅拷贝操作

一、浅拷贝问题
(1)如果类中叧包含简单数据成员,没有指向堆的指针, 可以使用编译器提供的默认复制构造函数

(2)如果类中包含指向堆中数据的指针或引用,浅拷贝将出现 严重问题

①浅拷贝直接复制两个对象间的指针成员,导致两个指针指向堆中同一坑内存区域

② 一个对象的修改将导致另一个对象的修改

③ 一个对象超出作用域,将导致内存释放,使得另一个对象的指针无效,对其访问将导致程序异常。
二、深拷贝问题

拷贝的时候先开辟出和源对象大小一样的空间,当他遇到指针的时候,他会知道new出来一块新的内存,然后把原来指针指向的值拿过来,这样指针和源对象的指针就指向了不同的内存位置,并且里面的内容是一样的,这样就不会出现重复释放同一块内存的错误。

57.C++的vector和list的区别

1.vector数据结构
vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。
因此能高效的进行随机存取,时间复杂度为o(1);
但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。
另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。

2.list数据结构
list是由双向链表实现的,因此内存空间是不连续的。
只能通过指针访问数据,所以list的随机存取非常没有效率,随机存取时间复杂度为o(n),插入和删除操作的时间复杂度为O(1)
但由于链表的特点,能高效地进行插入和删除,它不支持随机访问。

58.C++的vector的释放内存的几种方式

vector与其他容器不同,其内存空间只会增长,不会减小

待释放vector: vector myvector

1.swap方法

vector().swap(myvector)

2. clear + shrink_to_fit

myvector.clear();

myvector.shrink_to_fit();

59.内存泄漏
当我们用new或者malloc申请了内存,但是没有用delete或者free及时的释放了内存,结果导致一直占据该内存,内存泄漏形象的比喻是“操作系统可提供给所有进程的存储空间被某个进程榨干”,最终结果是程序运行时间越长,占用存储空间越来越多,最终用尽全部存储空间,整个系统崩溃。

程序结束后,会释放其申请的所有内存,这样是可以解决问题,但是你的程序还是有问题的,比如写了一个函数,申请了一块内存,但是没有释放,每调用一次你的函数就会白白浪费一些内存,如果你的程序不停的在运行,就会有很多内存被浪费,最后可能你的程序会因为用掉内存太多而被操作系统杀死

解决方法:智能指针——一种类似指针的数据类型,将对象存储在智能指针中,可以不需要处理内存泄漏的问题,它会帮你调用对象的析构函数自动撤销对象(主要是智能指针自己的析构函数用了delete ptr,delete会自动调用指针对象的析构函数,前提该内存是在堆上,如果是在栈上就会出错),释放内存,你要做的就是在析构函数中释放掉数据成员的资源

60.C++的智能指针

智能指针(smart pointer)其实不是一个指针。它就是用来帮助我们管理指针,维护其生命周期的类。因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

三种智能指针,shared_ptr、unique_ptr、weak_ptr。

unique_prt 只允许基础指针的一个所有者,可以移到新所有者(具有移动语义),但不会复制或共享(即我们无法得到指向同一个对象的两个unique_ptr),无法进行复制构造,无法进行复制赋值操作,既无法使两个unique_ptr指向同一个对象,但是可以进行移动构造和移动赋值操作(所有权转让),保存指向某个对象的指针,当它本身被删除释放的时候,会使用给定的删除器释放它指向的对象,我们知道auto_ptr通过复制构造或者通过=赋值后,原来的auto_ptr对象就报废了.所有权转移到新的对象中去了.而通过shared_ptr可以让多个智能指针对象同时拥有某一块内存的访问权.但假如我们不希望多个内存块被多个智能指针对象共享,同时又不会像auto_ptr那样不知不觉的就让原来的auto_ptr对象失效,可咋整呢? 就是同时只能有一个智能指针对象指向某块内存,1.无法进行复制构造与赋值操作. 2.可以进行移动构造和移动赋值操作

shared_ptr维护了一个指向control block的指针对象,来记录引用个数。采用引用计数的智能指针。 shared_ptr基于“引用计数”模型实现,多个shared_ptr可指向同一个动态对象,并维护了一个共享的引用计数器,记录了引用同一对象的shared_ptr实例的数量。当最后一个指向动态对象的shared_ptr销毁时,会自动销毁其所指对象(通过delete操作符)。使用计数机制来表明资源被几个指针共享,可以通过成员函数use_count()来查看资源的所有者个数,拷贝构造时候,计数器会加1,当我们调用release()时,当前指针会释放资源所有权,计数减1,当计数等于0时,资源会被释放,会有死锁问题,引入weak_ptr,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。

weak_ptr用于避免shared_ptr相互指向产生的环形结构,造成的内存泄漏。weak_ptr count是弱引用个数;弱引用个数不影响shared count和对象本身,shared count为0时则直接销毁。“循环引用”简单来说就是:两个对象互相使用一个shared_ptr成员变量指向对方的会造成循环引用。导致引用计数失效。weak_ptr用于配合shared_ptr使用,并不影响动态对象的生命周期,即其存在与否并不影响对象的引用计数器。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前需要检查weak_ptr是否为空指针。构造和析构不会引起引用的增加或减少,协助shared_ptr

61.阻塞和非阻塞的区别

  • 阻塞是指调用结果返回之前,当前线程被挂起。
  • 非阻塞是指在不能立刻得到结果之前,该调用不会阻塞当前线程。
  • 阻塞非阻塞着重在于服务端程序在等待结果时的状态

62.同步和异步区别

  • 同步是指客户端发出请求后,在没有得到想要结果前,一直阻塞
  • 异步是指客户端发出请求后,马上返回但是没有结果。等服务端运行结束后通过回调再通知客户端
  • 同步异步的区别着重在于客户端在等待结果时的状态

63.VS为什么能进行断点单步调试,原理是啥

调试断点依赖于父进程和子进程之间的通信,打断点实际上就是在被调试的程序中,改变断点附件程序的代码,这个断点使得被调试的程序暂时停止,然后发送信号给父进程也就是调式器进程,然后父进程能够得到子进程的变量和状态,达到调试的目的。

硬件中断:CPU有一个单独的执行序列,会一条指令一条指令的顺序执行,要处理类似I/O或者硬件时钟这样的异步事件,CPU就要用到中断,硬件中断通常是一个专门的电信号,连接到一个特殊的响应电路上,这个电路会感知中断的到来,然后让CPU停止当前的执行流,保存当前的状态,然后跳转到一个预定义的地址处去执行,这个地址上有一个中断处理例程,当中断处理例程完成它的工作后,CPU就从之前停止的地方恢复执行

软件中断:CPU支持特殊指令来模拟一个中断,当执行到这个指令之后,CPU将其当做一个中断,停止当前的正常的执行流,保存状态然后跳转到一个处理例程中执行,这种'陷阱'让许多现代操作系统得以有效的完成很多复杂的任务——任务调度、虚拟内存、内存保护、调试等。

64.STL组件中,有sort函数,它同了哪些排序算法

纵观STL的container,关系型container例如map和set利用RB树自动排序,不需要用到sort,stack和queue和priority_queue都有特定的出入口,不允许用户进行排序,剩下的vector,list和deque,list的迭代器属于bidirectional-iterator,剩下的vector和deque适合用sort算法,数据量大的时候,STL是sort算法采用快速排序,分段递归排序,一旦分段后的数据量小于某个门槛,为避免quick sort的递归调用带来过大的额外负担,就改用插入排序,还会改用堆排序

65.SQL标准的四种隔离级别

(1)READ UNCOMMITED(未提交读)

事务中的修改,即使没有提交,对其他事务也都是可见的,事务1修改了数据A后未提交,事务2可以读到被事务1修改的数据A,但是事务1可能最终不提交,那么事务2读到的就是错误的数据(脏数据)

(2)READ COMMITED(提交了可读)

大多数数据库系统的默认隔离级别都是READ COMMITED(但是MySQL不是,MySQL默认是可重复读),一旦事务开始时,只能看到已经提交的事务所做的修改,换句话说,一个事务从开始到提交之前,所做的任何修改对其他事务都是不可见的,这个级别有时候也叫做不可重复的,因为两次执行同样的查询,可能会得到不一样的结果。事务1读取了数据A后未提交,事务2修改了数据A且提交,然后事务1再去读数据A会发现前后两次读结果不同,这就是不可重复读(因为重复读结果不一样)

(3)REPEATABLE READ(可重复读)

可重复读解决了脏读问题,该级别保证了在同一个事务中多次读取同样的记录的结果是一致的,但是理论上,可重复读隔离级别还是无法解决另一个幻读的问题,所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行,Innodb和Xtradb存储引擎通过多版并发控制解决了幻读,可重复读是MySQL默认的事务隔离级别,事务1读取了数据A后未提交,事务2修改了数据A且提交,然后事务1再去读数据A会发现前后两次读结果相同,这就是可重复读,就是在开始读取数据(事务开启)时,不再允许修改操作——MySQL默认级别

(2)SERIALIZABLE(可串行化)

可串行化是最高的隔离级别,它通过强制事务串行,避免了前面说的幻读问题,简单是来说,可串行化会在读的每一行数据上都加锁,所以可能导致大量的超时和锁征用问题,实际应用中也很少用到这个隔离级别,只有在非常确保数据的一致性而且可以接受没有并发的情况,才考虑用该级别,每一行数据加锁,导致所有事务都必须串行执行,但是代价很大,

65.Mysql的两种引擎的区别

(1)Innodb引擎

Innodb引擎提供了对数据库ACID事务的支持,并且实现了SQL标准的四种隔离级别,该引擎提供了行级锁和外键约束,它的设计目标是处理大容量数据库系统,它本身其实就是基于MySQL后台的完整数据库系统,MySQL运行时Innodb会在内存中建立缓冲池,用于缓冲数据和索引,但是该引擎不支持FULLTEXT类型的索引,而且它没有保存表的行数,当SELECT COUNT(*) FROM TABLE时需要扫描全表,当需要使用数据库事务时,该引擎当然是首选。由于锁的粒度更小,写操作不会锁定全表,所以在并发较高时,使用Innodb引擎会提高效率,但是使用行级锁也不是绝对的,如果在执行一个SQL语句时MySQL不难确定要扫描的范围,Innodb表同样会锁全表。

(2)MyISAM引擎

MyISAM引擎是MySQL默认的引擎,但是它没有提供对数据库事务的支持,也不支持行级锁和外键,因此当INSERT(插入)或UPDATE(更新)数据时即写操作需要锁定整个表,效率便会更低一些,不过和Innodb不同,MyISAM中存储了表的行数,于是SELECT COUNT(*) FROM TABLE时只需要直接读取已经保存好的值而不需要进行全表扫描,如果表的读操作远远多于写操作且不需要数据库事务的支持,那么MyISAM也是很好的选择

区别:

1. MyISAM是非事务安全的,而Innodb是事务安全的

2. MyISAM锁的粒度是表级别的,而Innodb支持行级锁

3. MyISAM支持全文类型索引,而Innodb不支持全文索引

4. MyISAM相对简单,效率上要优于Innodb,小型应用可以考虑使用MyISAM

5. MyISAM表保存文件形式,跨平台使用更加方便

应用场景:

1. MyISAM管理非事务表,提高高速存储和检索以及全文搜索能力,如果在应用中执行大量select操作,应该选择MyISAM

2. Innodb用于事务处理,具有ACID事务支持等特性,如果在应用中执行大量insert和update操作,应该选择Innodb

 

你可能感兴趣的:(后台开发面试题)