bytedance面试题1
1. 请问TCP是如何实现可靠传输的
【Question】
【Answer】
- 1、应用数据被分割成TCP认为最适合发送的数据块。 - 2、超时重传:当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。 - 3、TCP给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。 - 4、校验和:TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段。 - 5、TCP的接收端会丢弃重复的数据。 - 6、流量控制:TCP连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的我数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP使用的流量控制协议是可变大小的滑动窗口协议。 - 7、拥塞控制:当网络拥塞时,减少数据的发送。
2. HTTP协议与Header相关问题
【Question】
HTTP协议与Header
- 常见的HTTP Method有哪些?GET/POST区别?
- Header中常见的key/value对有哪些?
- 能知晓HTTP协议是应用成协议,其网络传输层的协议是什么?
- Header中能存放二进制数据么?
- 你认为Header中,最重要的那个Key/Value对是什么?
【Answer】
开放问题,考查候选人对HTTP协议与Header的理解与应用 1. 能知晓GET/POST等区别 2. 能知晓Header中的常见key,例如UA、cookie、refer、host等 3. 能知晓是tcp协议 4. 不能,这里考察其对HTTP协议本身的理解 5. content-length,这里考察其对HTTP协议本身的理解
3. 进程和线程的联系和区分
【Question】
- 进程和线程有什么区别?
- 一个进程中有哪些数据段?同一个进程下的不同线程间有哪些数据段可以共享访问?
【Answer】
- 区别和联系 - 线程是CPU调度的基本单位,同一个进程中可以有多个线程,但至少有一个线程 - 进程是操作系统分配资源的最小单位 - 一个进程中有哪些数据段 - 代码段(Text Segment):保存代码 - 静态数据段(Data Segment):静态数据 - 未初始化数据段(BSS) - 栈(Stack):向下增长 - 堆(Heap):向上增长 - .rodata - 同一个进程下的不同线程间有哪些数据段是共享的? - 除Stack外均共享
4. 简述HTTPS 密钥的交换过程
【Question】 https协议下传输的是加密数据,涉及到cs两端秘钥的协商和交互,简要描述下https秘钥交换过程
【Answer】
1. 客户端发起ssl请求,服务端返回CA证书 2. 客户端解析证书验证真伪,获取用于非对称加密的公钥 3. 利用公钥加密发送对称加密的私钥 4. 利用对称加密传输数据
5. TCP四次挥手为什么要有TIME_WAIT状态?
【Question】 TCP四次挥手为什么要有TIME_WAIT状态?为什么?
【Answer】
* 保证TCP协议全双工连接能够可靠关闭,直接关闭的话,如果服务器没有收到ACK,会重复发FIN。 * 保证这次连接的重复数据从网络中消失,如果上次的socket和这次的socket处理的程序一样,就会导致这次连接把上次的数据加进来了。
6. 进程通信的几种方式
【Question】 描述 Linux 进程通信的几种方式,以及各种方式的使用场景。
【Answer】
1. 普通管道(pipe):管道是一种单工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系或者兄弟进程之间。 2. 命名管道(FIFO):命名管道也是半双工的通信方式,但是它允许无亲缘关系的进程间的通信。 3. 信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 4. 消息队列(message queue):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓存区大小受限等缺点。 5. 信号(signal):信号是一种比较复杂的通信方式,用于通知接受进程某个事件已经发生。 6. 共享内存(shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。 7. 套接字(socket):套接字也是一种进程间通信的机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
7. 简述TCP拥塞处理机制
【Question】 TCP/IP的拥塞控制的含义是什么,有什么拥塞控制的方法?
【Answer】
* 拥塞控制:防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。 * 对比流量控制:拥塞控制是一个全局的过程,涉及到所有的主机、路由器、以及降低网络相关的所有因素。流量控制往往指点对点通信量的控制。是端对端的问题。 * 几种拥塞控制的方法: 慢开始,拥塞避免,快重传,快恢复。 1. 慢开始:主机开发发送数据报时,如果立即将大量的数据注入到网络中,可能会出现网络的拥塞。慢启动算法就是在主机刚开始发送数据报的时候先探测一下网络的状况,如果网络状况良好,发送方每发送一次文段都能正确的接受确认报文段。那么就从小到大的增加拥塞窗口的大小,即增加发送窗口的大小。 2. 拥塞避免:是让cwnd缓慢的增加而不是加倍的增长,每经历过一次往返时间就使cwnd增加1,而不是加倍,这样使cwnd缓慢的增长,比慢启动要慢的多。 3. 快重传:快重传算法要求首先接收方收到一个失序的报文段后立刻发出重复确认,而不要等待自己发送数据时才进行捎带确认。 4. 快恢复:当发送方连续接到三个确认时,就执行乘法减小算法,把慢开始门限减半,但是接下来并不执行慢开始算法。而是把cwnd设置为ssthresh的一半,然后执行拥塞避免算法,使拥塞窗口缓慢增大。
8. tcp的三次握手和4次挥手
【Question】 描述tcp建连的三次握手和断开的4次挥手的整个过程,中间状态
【Answer】
三次握手:sync+sync/ack+ack 四次挥手:fin+ack+fin+ack,知识点tcp是双工网络,半断开状态,time-wait的出现
9. TCP和UDP对比
【Question】 描述TCP和UDP的区别和各自的应用场景
【Answer】
1. TCP是面向连接且可靠的,UDP是面向非连接且非可靠 2. TCP是面向字节流的,UDP是面向报文流 3. TCP的传输效率低,UDP传输效率高 4. TCP有流量控制,拥塞控制等,UDP没有 5. TCP适用于对可靠性要求比较高,但对效率要求低的场景,而UDP适用于对可靠性要求比较低,但对效率要求比较高的场景 6. TCP协议应用: HTTP/FTP/TELNET/SMTP UDP协议应用: DNS/SNMP
10. 简述 select, poll, epoll 的区别
【Question】
- 描述IO多路复用机制select/poll/epoll 的区别
- 描述epoll的两种触发方式以及有何不同
【Answer】
* IO多路复用都是一个进程可以监控多个文件描述符,一旦某个描述符就绪,能够通知程序进行相应的读写操作。 * select:调用过程 a 将fd_set从用户空间拷贝到内核空间 b: 遍历fd_set 将就绪的fd拷贝到用户空间;缺点是 a select支持的fd数目是通过FD_SETSIZE指定,存在数目限制 b: select调用时需要在用户和内核空间进行fd_set的拷贝,开销比较大 c: select调用时需要在内核中遍历fd_set,IO效率低 * poll:select的增强版,用链表来实现fd_set, 故没有fd数目的限制,但效率依旧不高 * epoll:每次调用epoll_ctl函数进行注册时候,会将fd_set拷贝到内核空间,避免每次调用时重复拷贝;无需遍历fd_set, epoll维护一个就绪列表,一旦某个fd就绪,就把fd插入到链表中,epoll_wait只需遍历该链接即可;epoll没有fd数据限制,它所支持的fd上限是最大可以打开文件的数目 * epoll支持两种触发方式:水平触发(LT)和边缘触发(ET),LT和ET最大的区别就是epoll_wait检测fd就绪后,应用程序可以不立即处理,下次调用epoll_wait会再次通知此事件;而ET则不支持,一旦通知之后就再通知。
11. 请描述从输入URL到页面加载完成的过程
【Question】
从浏览器输入http://www.toutiao.com/敲下回车键开始,到页面完整显示出来,整个过程发生了什么?
可以尽你所知做详尽描述。
【Answer】
这个问题可以讨论的点很多,知识面广的了解的点很多。 可以就候选人擅长在个别点深入讨论。 1. 浏览器如何向网卡发送数据 可以讨论的点:从浏览器到浏览器内核、HTTP、DNS、HTTP-DNS、Socket、网络协议 2. 数据如何从本机网卡发送到服务器 可以讨论的点:从内核到网络适配器(NIC)、有线或Wi-Fi接入、运营商网络路由、主干网传输、IDC、服务器 3. 服务器接收到数据后会进行哪些处理 可以讨论的点:负载均衡(硬件、软件)、反向代理、web server、后端语言框架、数据存储(持久、缓存)、CDN 4. 服务器返回数据后浏览器如何处理 可以讨论的点:从01到字符(编解码)、外链资源加载、JS执行、CSS渲染、从内存到LCD显示
12. 请描述TCP和UDP的区别和各自的应用场景
【Question】
【Answer】
1. TCP、UDP的基本概念 UDP:面向报文 面向报文的传输方式应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。 因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片,降低效率,若太短,会使IP太短。 TCP面向字节流 虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传输的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。 2. TCP、UDP的区别 可靠性:TCP三次握手四次挥手是可靠的,UDP不可靠 连接性:TCP面向连接,UDP无连接 报文:TCP面向字节流,UDP面向报文(保留报文的边界) 效率:TCP传输效率低,UDP传输效率高 双工性:TCP全双工,UDP一对一、一对多、多对一、多对多 流量控制:TCP有滑动窗口 拥塞控制:TCP有慢开始、拥塞避免、快重传、快恢复 传输速度:TCP慢,UDP快 应用场合:TCP对效率要求相对较低,对准确性要求较高或者是要求有连接的场景;UDP对效率要求较高,对准确性要求较低 3. 具体的应用场景 TCP使用场景:当对网络通讯质量有要求的时候,比如整个数据要准确无误的传送给对方,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 应用: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP QQ文件传输 UDP使用场景:当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快。 应用: QQ语音 视频 游戏 4. TCP、UDP优缺点 TCP的优点:可靠,稳定。TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。 TCP的缺点:慢,效率低,占用系统资源高,易被攻击。TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。 UDP的优点:快,UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击。 UDP的缺点:不可靠,不稳定。因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。
13. 请谈一谈TCP建立连接和断开连接的过程
【Question】
描述TCP从建连到断连的过程以及状态变化
TIME_WAIT状态的设计意义
【Answer】
1. 建连三次握手和断连四次挥手的过程,注明SYN/FIN/ACK 2. TCP交互的整个过程中总共有11个状态: CLOSED/LISTEN/SYNC_SENT/SYNC_RECV/ESTABLISHED/FIN_WIAT_1/FIN_WAIT_2/CLOSING/TIME_WAIT/CLOSE_WAIT/LAST_ACK 3. TIME_WAIT 是主动关闭端出现的状态,等待2MSL的作用是: a 确保对端最后的ack能收到 b 让关闭的连接上的包消逝
14. 打开网页过程分析
【Question】
- 打开网页浏览器并访问https://toutiao.com的过程中,涉及哪些网络协议?请描述过程
- 如果发现输入网址后无法正常访问,应该如何排查原因和解决?涉及哪些网络协议?
【Answer】
- 描述过程即可,几个主要过程 1. DNS解析,涉及内容:DNS解析;涉及传输层协议:UDP;(可进一步考察对DNS缓存、UDP传输过程的了解和知识深度) 2. HTTP传输,涉及传输层协议:TCP;能分析出TCP建连和传输过程更佳;能进一步说出keepalive更好 - 从网络连接、DNS解析等阶段分析 1. 检查网络连接:涉及网络层IP协议、链路层ARP等;可使用命令`ping`测试连接是否通畅,面试者需要理解网络分层,网络层等是上层的TCP、UDP等传输的基础 2. 检查DNS解析:分析DNS本地缓存、路由器缓存和服务器缓存是否有异常;DNS服务器异常;DNS解析哪些环节可能出问题;可使用`nslookup` `dig`等工具帮助排查 3. 防火墙:可发散考察面试者的知识广度,如需要实现防火墙可以有哪些做法(e.g. iptables) 4. 检查网站的服务状况:尝试更换其它网址尝试是否有异常,如果只是单个站点不可用,可能是服务端挂了;如果是间歇性不可用(需要考虑到负载均衡),可能是部分后端挂了
15. C++指针和引用的差别
【Question】
【Answer】
1、非空区别。不存存指向空值的引用,指针可以指向NULL。 2、合法性区别。使用引用之前不需要测试它的全法性,使用指针前需要判断是否为NULL。 3、可修改区别。指针可以被重新赋值以指向另一个不同的对象,引用必须初始化时指定,并且不能再改变。
16. 请回答TCP如果两次握手会出什么问题?那三次握手又会造成什么问题?请简述好的解决方法
【Question】 TCP如果两次握手会出什么问题?那三次握手又会造成什么问题?有什么好的解决方法没?
【Answer】
* 两次握手:客户端发送的连接请求可能在网络中滞留了,如果没有三次握手,可能会再次创建一个连接。 * 三次握手:引起SYN flood(一种DoS(拒绝服务攻击)是DDoS(分布式拒绝服务攻击)的方式之一,这是一种利用TCP协议缺陷,发送大量伪造的TCP连接请求,从而使得被攻击方资源耗尽(CPU满负荷或内存不足)的攻击方式。) * 不断发送同步报文段会因为传输控制模块TCB【处于半连接状态】从而消耗服务器资源 1. 【处理连接和半连接】定时释放监控系中无效的连接 2. Syn cache技术【处理半连接状态】,接受到的SYN先不创建TCB,而是用一个hash表来表示,当前连接,如果接收到ACK然后再创建TCB 3. Syn cookie技术【处理连接】通过一个cookie值来确定当前连接是否合法,合法就连接,一般的验证方法是,服务器接受到一个syn包,服务器通过syn产生一个cookie数据作为初始化序列,接收到ACK包时,序列-1就是得到的cookie,然后进行相应的验证。
17. 简述DNS解析的过程
【Question】
简述DNS解析的过程,递归查询和迭代查询是什么?
如果某天你打开浏览器尝试打开douyin.com,浏览器提示你:找不到服务器 IP 地址,有可能是哪里出现了问题?为什么?可以用哪些命令和工具排查?
【Answer】
1. 客户端向本地DNS服务器发出递归查询请求
2. 本地DNS服务器向根域名服务器发出迭代查询请求,得到顶级域名服务器的地址
3. 本地DNS服务器向顶级域服务器发出请求,得到权威域名服务器的地址
4. 本地DNS服务器向顶级域服务器发出请求,得到域名对应ip地址
5. 本地DNS服务器返回域名的ip地址给客户端 可能出现问题的点:
1. 网络连接
2. 浏览器DNS缓存
3. 系统DNS缓存
4. 路由器DNS缓存
5. ISP DNS缓存
排查方法: 1. 检查网络连接:ping等 2. 检查DNS解析:dig
18. 用户态和内核态的区别
【Question】
【Answer】
内核态与用户态是操作系统的两种运行级别,intel cpu提供Ring0-Ring3三种级别的运行模式。Ring0级别最高,Ring3最低。其中特权级0(Ring0)是留给操作系统代码,设备驱动程序代码使用的,它们工作于系统核心态;而特权极3(Ring3)则给普通的用户程序使用,它们工作在用户态。运行于处理器核心态的代码不受任何的限制,可以自由地访问任何有效地址,进行直接端口访问。而运行于用户态的代码则要受到处理器的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TSS)中I/O许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问(此时处理器状态和控制标志寄存器EFLAGS中的IOPL通常为0,指明当前可以进行直接I/O的最低特权级别是Ring0)。以上的讨论只限于保护模式操作系统,象DOS这种模式操作系统则没有这些概念,其中的所有代码都可被看作运行在核心态。 * 当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级) 内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。 * 在内核态下CPU可执行任何指令,在用户态下CPU只能执行非特权指令。当CPU处于内核态,可以随意进入用户态;而当CPU处于用户态时,用户从用户态切换到内核态只有在系统调用和中断两种情况下发生,一般程序一开始都是运行于用户态,当程序需要使用系统资源时,就必须通过调用软中断进入内核态。 * Linux使用了Ring3级别运行用户态,Ring0作为内核态,没有使用Ring1和Ring2。Ring3状态不能访问Ring0的地址空间,包括代码和数据。Linux进程的4GB地址空间,3G-4G部分大家是共享的,是内核态的地址空间,这里存放在整个内核的代码和所有的内核模块,以及内核所维护的数据。用户运行一个程序,该程序所创建的进程开始是运行在用户态的,如果要执行文件操作,网络数据发送等操作,必须通过 write,send等系统调用,这些系统调用会调用内核中的代码来完成操作,这时,必须切换到Ring0,然后进入3GB-4GB中的内核地址空间去执行这些代码完成操作,完成后,切换回Ring3,回到用户态。这样,用户态的程序就不能随意操作内核地址空间,具有一定的安全保护作用。 处理器模式从Ring3向Ring0的切换发生在控制权转移时,有以下两种情况:访问调用门的长转移指令CALL,访问中断门或陷阱门的INT指令。具体的转移细节由于涉及复杂的保护检查和堆栈切换,不再赘述,请参阅相关资料。现代的操作系统通常使用中断门来提供系统服务,通过执行一条陷入指令来完成模式切换,在INTEL X86上这条指令是INT,如在WIN9X下是INT30(保护模式回调),在LINUX下是INT80,在WINNT/2000下是INT2E。用户模式的服务程序(如系统DLL)通过执行一个INTXX来请求系统服务,然后处理器模式将切换到核心态,工作于核心态的相应的系统代码将服务于此次请求并将结果传给用户程序。
19. Java-线程同步、锁
【Question】
- Java中线程同步有哪些方法?
- 这些锁是怎么实现的?
- 用这些同步方法描述两种生产者-消费者过程。
【Answer】
1. - synchronized、wait、notify - Lock、ReentrantLock、ReentrantReadWriteLock - Condition - Semaphore - CyclicBarrier - CountDownLatch - Phaser - Exchanger 2. CAS、CLH队列、AQS、LockSupport 3. https://github.com/chaodewen/java-test/tree/master/src/main/java/algorithm/pc
20. 请说明TreeMap、HashMap、HashTable的实现区别
【Question】 哈希表是中非常高效,复杂度为O(1)的数据结构,在Java开发中,我们最常见到最频繁使用的就是HashMap和HashTable,那么区别在哪里? ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现,ConcurrentHashMap实现原理?
【Answer】
* HashMap :先说HashMap,HashMap是线程不安全的,在并发环境下,可能会形成环状链表(扩容时可能造成),导致get操作时,cpu空转,所以,在并发环境中使用HashMap是非常危险的。 * HashTable : HashTable和HashMap的实现原理几乎一样,差别是: 1. HashTable不允许key和value为null; 2. HashTable是线程安全的。但是HashTable线程安全的策略实现代价却太大了,简单粗暴,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。 * HashTable性能差主要是由于所有操作需要竞争同一把锁,而如果容器中有多把锁,每一把锁锁一段数据,这样在多线程访问时不同段的数据时,就不会存在锁竞争了,这样便可以有效地提高并发效率。这就是ConcurrentHashMap所采用的"分段锁"思想。ConcurrentHashMap采用了非常精妙的"分段锁"策略,ConcurrentHashMap的主干是个Segment数组。 Segment继承了ReentrantLock,所以它就是一种可重入锁(ReentrantLock)。在ConcurrentHashMap,一个Segment就是一个子哈希表,Segment里维护了一个HashEntry数组,并发环境下,对于不同Segment的数据进行操作是不用考虑锁竞争的。
21. 进程间通信-共享内存
【Question】
1. 同一台机器最快的IPC方式?
- 共享内存与进程地址空间有关吗?在用户态还是内核态?
- 多进程如何找到这块内存?
- 共享内存有什么问题?如何解决?
【Answer】
1. 共享内存 2. 无关;内核态 3. 设置Key 4. 需要同步;例如使用信号量操作
22. 请简述堆与栈的区别?
【Question】 c语言堆和栈的区别
【Answer】
1. 栈: 由编译器自动分配释放,存放函数的参数值,局部变量等值。 如 函数的参数,在函数内定义的变量等。 存放方式类似于数据结构中的栈。 栈的大小是固定的一块连续的内存区域,若申请空间超过总空间会出现overflow异常。 2. 堆: 一般由程序员分配释放,若程序员不释放,则可能会引起内存泄漏。 如 调用malloc,realloc,calloc等。 存放方式类似于数据结构中的链表。(操作系统的空闲内存的存储方式) 堆的大小受限于计算机系统中有效的虚拟内存。 3. 注意全局变量存放在静态区中,不属于堆或栈
23. 进程调度策略
【Question】
- 你知道哪些操作系统的进程调度策略?
- 假设操作系统的进程优先级被分为0-9级,如果需要基于这些优先级实现一个O(1)的调度器,你会怎么做?工作流程是怎么样的?
- 完全公平调度器(CFS)最核心的数据结构是什么?为什么采用这种数据结构?
【Answer】
一些调度算法: 1. FCFS:先来先服务 2. 最短作业优先:每次选择所需处理时间最短的进程运行 3. 最短剩余时间优先:总是选择预期剩余时间最短的进程 4. 高回复率优先/最高响应比优先:根据等待时间W和需要服务的时间S计算响应比R=(w+s)/s,优先服务响应比高的 5. 轮转调度(round robin):每个进程分配一个时间片,时间片用完还未结束则挂起 6. 优先级调度:每个进程分配一个优先级,优先服务高优先级的进程 实现一个O(1)的调度器,清晰合理即可,参考:基于10个等级,使用两组各10个链表维护,每次从一组选优先级大的轮转执行,并放入另一组,一轮结束后交换两组链表 CFS调度器:内部使用红黑树维护进程,优点:自平衡、高效、公平性好
24. DNS解析
【Question】
1. 简述DNS解析的过程,递归查询和迭代查询是什么?
- 如果某天你打开浏览器尝试打开douyin.com,浏览器提示你:找不到服务器 IP 地址,有可能是哪里出现了问题?为什么?可以用哪些命令和工具排查?
【Answer】
解析过程: 1. 客户端向本地DNS服务器发出递归查询请求 2. 本地DNS服务器向根域名服务器发出迭代查询请求,得到顶级域名服务器的地址 3. 本地DNS服务器向顶级域服务器发出请求,得到权威域名服务器的地址 4. 本地DNS服务器向顶级域服务器发出请求,得到域名对应ip地址 5. 本地DNS服务器返回域名的ip地址给客户端 可能出现问题的点: 1. 网络连接 2. 浏览器DNS缓存 3. 系统DNS缓存 4. 路由器DNS缓存 5. ISP DNS缓存 排查方法: 1. 检查网络连接:ping等 2. 检查DNS解析:dig
25. HTTP协议中有那些请求方式?
【Question】 什么是http的请求方法,http请求方法有哪些?请解释这些方法的含义
【Answer】
http请求方法 **GET、HEAD、POST、PUT、DELETE、CONNECT、OPTIONS、TRACE 1 GET 请求指定的页面信息,并返回实体主体。 2 HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头 3 POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 4 PUT 从客户端向服务器传送的数据取代指定的文档的内容。 5 DELETE 请求服务器删除指定的页面。 6 CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 7 OPTIONS 允许客户端查看服务器的性能。 8 TRACE 回显服务器收到的请求,主要用于测试或诊断。
26. 请问如何实现C++智能指针
【Question】 如果需要实现一个智能指针?你会怎么做?
【Answer】
考察点: 1. 能否使用模板实现泛型 2. 是否知道使用引用计数来维护指针 3. 内存的分配和释放 参考: ```c++ template class SmartPtr { public: SmartPtr(T *ptr) { p = ptr; count = 0; if (ptr != NULL) { count++; } } SmartPtr(const SmartPtr &sp) : p(sp.p) { count = sp.count + 1; } ~SmartPtr() { if (--count == 0) delete p; } SmartPtr &operator=(const SmartPtr &sp) { sp.count++; if ((--count == 0) && p != NULL) { delete p; } p = sp.p; return *this; } T &operator*() { return *p; } T *operator->() { return p; } private: T *p; size_t count; }; ```
27. 请回答操作系统内存管理中,什么是段页式管理? 请问在段页式分配中,CPU每次从内存中取一次数据需要几次访问内存
【Question】 操作系统内存管理,什么是段页式管理?在段页式分配中,CPU每次从内存中取一次数据需要几次访问内存?
【Answer】
* 段页式存储管理:在段页式存储中,每个分段又被分成若干个固定大小的页。段页式存储组织是分段式和分页式结合的存储组织方法,这样可充分利用分段管理和分页管理的优点。 1. 用分段方法来分配和管理虚拟存储器。程序的地址空间按逻辑单位分成基本独立的段,而每一段有自己的段名,再把每段分成固定大小的若干页。 2. 用分页方法来分配和管理实存。即把整个主存分成与上述页大小相等的存储块,可装入作业的任何一页。程序对内存的调入或调出是按页进行的。但它又可按段实现共享和保护。 3. 逻辑地址结构。一个逻辑地址用三个参数表示:段号S;页号P;页内地址d。 4. 段表、页表、段表地址寄存器。为了进行地址转换,系统为每个作业建立一个段表,并且要为该作业段表中的每一个段建立一个页表。系统中有一个段表地址寄存器来指出作业的段表起始地址和段表长度。 - 取一次数据需要访问3次内存 - 在段页式存储管理方式中,取一次数据:首先要从内存中查找段表;再查找该段对应的页表;最后通过得到的物理地址访问内存获得数据。
28. C++中const和#define的区别
【Question】
【Answer】
1. const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符去你的中可能会产生意料不到的错误(边际效应) 2. 有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行高度。在C++ 程序 中只使用const常量而不使用宏常量,即const常量完全取代宏常量
29. c的static有什么用处?static全局变量、static局部变量、static函数
【Question】 c的static有什么用处,与不加static的有什么区别?
static全局变量、static局部变量、static函数
【Answer】
static全局变量只能在声明的文件中被使用,不能在其他文件中被使用 static局部变量只被初始化一次(在全局变量区),下一次使用是上一次结果值 static函数与static全局变量类似,只能在声明的文件中被使用
30. Python的GIL概念?
【Question】
- 什么是GIL,他的存在有什么影响?出现的原因?消灭GIL的阻力?
- GIL在CPU密集和IO密集型场景的分析?(两个线程并行分别跑一百万循环,和一个线程串行跑二百万循环谁快)
【Answer】
1. Global Interpreter Lock, CPython版本解释器上特有的全局锁,保证了同一进程中不同线程的内存访问互斥。虽然 降低了并行效率,但是对于开发者而言不需要额外的代码来保证自己代码线程安全。 2. CPython解析器的实现的内存管理不是线程安全的。 3. 大量的开源库使用并且强依赖了这一特性,改动之后大量源码将无法兼容新版本解释器。 4. 在跑空循环或者说CPU密集的情况下,优于线程调度的额外开销,加上GIL导致两个线程并行速度会慢于单线程串行;在IO密集场景下, 由于CPU在IO等待时间可以调度到处理其他线程的计算,因此性能好好于单线程。
31. 请分别介绍HTTP keep-alive 和 TCP keep-alive的机制。
【Question】
分别介绍下HTTP keep-alive 和 TCP keep-alive 机制
【Answer】
HTTP Keep-Alive
Connection: Keep-Alive 使得对同一个服务器的多个请求可以在同一连接上完成。
Keep-Alive 是一个通用消息头,允许消息发送者暗示连接的状态,还可以用来设置超时时长和最大请求数。
含有 Keep-Alive 首部的响应示例:
HTTP/1.1 200 OK Connection: Keep-Alive Content-Encoding: gzip Content-Type: text/html; charset=utf-8 Date: Thu, 11 Aug 2016 15:23:13 GMT Keep-Alive: timeout=5, max=1000 Last-Modified: Mon, 25 Jul 2016 04:32:39 GMT Server: Apache (body)
TCP Keep-Alive
作用 https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html
- 防止对端节点异常挂掉(没来得及正常关闭TCP连接);
- 防止网络异常断开,通信双方都没有感知,比如中间代理;
只有在一定时间内没有数据包传输时,才会发送keep-alive包
net.ipv4.tcp_keepalive_time = 7200 // 单位秒,开始探活前的空闲idle时间
net.ipv4.tcp_keepalive_intvl = 75 // 单位秒,探测间隔
net.ipv4.tcp_keepalive_probes = 9 // 探测次数
32. 复制粘贴的过程中操作系统做了什么
【Question】 如题
【Answer】
几种可能的思路。 1. 操作系统启动的时候有一个进程维护剪贴板相关内容,然后操作系统暴露两个API去Set和Get剪切板, 对这两个API的响应最终是由这个进程完成的。进程维护了一块自己的内存,每次剪切板都将这块内存 内容读取出来并返回给调用方。 2. 没有这样一个进程,操作系统暴露接口实际上是对一块剪切板内存的维护,调用方先申请一块全局内存 将剪切板内存加锁,然后将内容复制到全局内存,再拷贝到剪切板内存,释放全局锁。 总体,有一种合理的思路,且细节合理即可。
33. 操作系统-作业调度
【Question】
1.先来先服务(FCFS)有什么缺陷?
2.如何改进?改进算法有什么缺陷或者实现难点? 3.短作业优先如何预测作业时间?
【Answer】
1.阻塞 2. - 短作业优先(SJF)- 必须知道作业时间、长作业饥饿、重要作业无法优先处理 - 轮转时间片(RR)- 时间片长度选择、平均等待时间长、上下文切换资源消耗多 - 多级反馈队列 (MLFQ)- 关于每级队列的实现有多种说法(缺陷未知) 3. - 按照代码量预测 - 根据代码中的操作类型预测(系统进程VS用户进程、根据旧操作预测) - 动态预测:实时更新作业时间的均值作为预测值 - 指数平均、平滑因子等相比均值更加高级的数学方法
34. 同步IO和异步IO的差异
【Question】 描述下同步IO和异步IO的差异
【Answer】
同步异步是针对应用程序和内核的交互而言的。 同步是指用户进程触发IO操作并等待或轮询检查IO操作是否就绪,接着阻塞的完成IO操作 异步是指用户进程触发IO操作之后就做其它的事情,当IO操作完成之后会得到通知(如读取网络数据,此时数据已经由内核拷贝到用户缓冲区,用户进程可以直接使用)。 Reactor模式用于同步IO,Proactor模式用于异步IO
35. 操作系统-页面置换算法LRU、FIFO
【Question】
- 1.系统为某进程分配了4个页框,该进程已访问的页号序列为2,0,2,9,3,4,2,8,2,4,8,4,5。若进程要访问的下一页的页号为7,依据LRU算法,应淘汰页的页号是几号?
- 2.假设某一虚拟存储系统采用先进先出(FIFO)页面淘汰算法,有一个进程在内存中占3页(开始时内存为空),当访问如下页面序列号后1,2,3,1,2,4,2,3,5,3,4,5,6会产生几次缺页
【Answer】
1. 答案为2,可以采用书中常规的解法思路,也可以采用便捷法。对页号序列从后往前计数,直到数到4(页框数)个不同的数字为止,这个停止的数字就是要淘汰的页号(最近最久未使用的页),题中为页号2。 2. 答案为6次 **访问1,缺页,调入1,内存中1** **访问2,缺页,调入2,内存中1,2** **访问3,缺页,调入3,内存中1,2,3** 访问1,不缺页,内存中1,2,3 访问2,不缺页,内存中1,2,3 **访问4,缺页,调入4,淘汰1,内存中4,2,3** 访问2,不缺页,内存中4,2,3 访问3,不缺页,内存中4,2,3 **访问5,缺页,调入5,淘汰2,内存中4,5,3** 访问3,不缺页,内存中4,5,3 访问4,不缺页,内存中4,5,3 访问5,不缺页,内存中4,5,3 **访问6,缺页,调入6,淘汰3,内存中4,5,6**
36. 动、静态语言,强、弱类型语言分类
【Question】
- Python属于静态类型语言还是动态类型语言?属于强类型语言还是弱类型语言?
- 强弱类型的区别,动静态语言的区别?感性认识和理性认识。
- 谈一谈给静态类型语言和动态类型语言写单元测试感受,以及必要性理解。
【Answer】
1. Python属于强类型,动态类型检查的语言。 2. 感性认知上,动态类型检查还是静态类型检查区别于是在编译时还是运行时报出会导致程序终止运行的一些错误。 强类型和弱类型:程序会不会在运行时出现无法控制的行为(类似于数组越界,缓冲区溢出)之后仍然继续运行。(不能回答 是否允许隐式类型转换) 理性认识上,能用严格定义说明上述两个区别最好。 3. 如果用Python写过工程代码并且单元测试良好,一般能理解对于动态类型语言(或者说脚本语言)语言的灵活性保证了开发效率, 但是为了保证可维护性和可读性,测试样例以及足够的文档说明都非常必要。测试用力可以很好的充当类型检测工具。 静态语言测试用例构造更多的是去验证代码功能的完备性。
37. 操作系统-文件描述符
【Question】
- 当一个新创建的进程打开第一个文件时返回的文件描述符是什么?- 3
- 每个fd描述什么?- 0:STDIN、1:STDOUT、2:STDERR
- 系统中同时打开着许多文件,因此存在着许多文件描述符,这些描述符可以相同吗?
- 进程根据fd如何找到文件?如何获取文件的字节数、时间戳等属性?
【Answer】
1. 新进程创建时会有标准输入(0)、标准输出(1)和标准错误(2)三个描述符,打开文件时会使用尽可能小的fd,所以答案是3 2. 0:STDIN、1:STDOUT、2:STDERR 3. 每个进程维护自己的文件描述符表,其中fd值在进程中不重复即可,所以不同表之间fd可能相同 4. 在fd表中每个fd对应一个系统文件表中的文件偏移量,根据偏移量可以读取;系统文件表中每个文件存储一个inode指针,对应inode表中存储的文件属性 
38. c++11中auto关键字有什么用?
【Question】 c++11中auto关键字有什么用?使用auto声明的变量必须初始化吗?为什么?
【Answer】
- 用途:可用于变量类型推导,简化代码 - 必须初始化,否则会报编译错误,因为C++需要在编译时确定/推导出变量类型,进而确定内存分配
39. java中单例模式有哪些实现方式,线程安全性如何保证。
【Question】
【Answer】
懒汉(加大锁、双重检验锁+volatile),饿汉, 静态内部类、枚举等写法等 具体写法参考(http://cantellow.iteye.com/blog/838473)
40. 命令行前台运行一个程序的时候,ctrl-c的时候发生了什么? 信号的常用方式和原理
【Question】 一个常见的场景,考察候选人对信号的理解。
【Answer】
1. ctrl-c给前台运行的进程发送了SIGINT信号, 默认情况下,进程退出。 2. 信号是一种用来通知进程发生了某些异步事件的机制。 可以用于进程间的通信,也可以用于内核通知进程发生了某些内部事件。 3. 内核在进程即将从内核态返回时处理信号,意味着发送给进程的信号并不会立即得到处理。 4. 可以通过signal系统调用来指定信号的处理函数。 更多细节见:http://www.cnblogs.com/taobataoma/archive/2007/08/30/875743.html
41. 什么是C++的多态?
【Question】
- C++中泛型如何实现
- C++中模板是在编译还是运行时实现泛型的?
- 尝试实现一个简单的动态数组,要求(1)能支持不同数据类型 (2)实现push_back、pop_back、动态数组间的赋值功能
【Answer】
C++的模板是编译时泛型,编译时,编译器根据模板使用推导出类型,并生成具体代码(实例化模板) 实现动态数组:使用模板支持泛型;需要把握到内存的申请和回收,包括扩容时的具体细节、缩容后是否释放,并给出对应做法的理由。
42. java中volatile的作用是什么
【Question】
【Answer】
主要有两个作用 (1)抑制重排序,包括编辑器级别的和CPU指令级别的。 (2)保证内存可见性。
43. 设计一个内存池
【Question】 设计一个内存池,满足内存申请、内存释放以及内存碎片管理等基本功能。
【Answer】
可以使用内存块+链表的方式,首先将一整块的内存分割成不同大小的内存块单元,例如8K、16K、32K、64K、128K。用hash链表将内存块串联起来。要点如下: 1. 申请内存:从满足条件的最小内存块链上摘下一块内存来分配; 2. 回收内存:内存释放时,将内存块清空并挂在对应大小的内存链上; 3. 大块内存申请:当申请大块内存无法满足时,需要考虑从小块内存链上合并内存碎片; 4. 小块内存申请:如果可选内存块远大于申请内存量,需要考虑将大块内存拆成小块内存之后再提供; 内存链表有很多方式,上述hash链表是其中一种,也可以使用堆来实现。另外对性能、安全等方面也可以考察。
44. 请简述HTTP 499出现的原因及处理办法
【Question】
1、 HTTP状态码出现499, 是什么含义?
2、 哪几种情况会导致499? 分别如何处理?
【Answer】
1、 499一般是nginx 返回的,表示“客户端提前关闭了连接”。 也就是说一次请求还没有处理完,nginx 就发现客户端关闭连接了。
2、 有如下几种原因:
1)、 客户端关闭过快。 例如客户端在几毫秒就关闭了连接。 这种一般是用户在刷新页面或者刷新view导致的。 这种情况下,应该与客户端同学一起来分析,看是否能从客户端进行优化,减少这种情况的发生。
2)、 服务端处理时间过长。 例如服务端处理了5秒,客户端只等了2秒,就会导致499。 这种情况下,服务端应进行优化。 如果无法优化,则应该跟客户端协商,调大超时时间。
45. 请回答在操作系统内存管理中,请求页面置换策略有哪些方式?每种方式具体的置换算法包括?
【Question】 操作系统内存管理中,请求页面置换策略有哪些方式?每种方式具体的置换算法包括?
【Answer】
* 页面置换策略:全局置换,局部置换; * 全局:在整个内存空间置换 * 局部:在本进程中进行置换 1. 全局置换指的是进程缺页时,可能置换的是内存中所有可换出的物理页面。即要换进的是A进程的页面,出去的可以是B进程的页面,因此分配给进程的页面总数是动态变化的。 2. 局部置换只置换本进程内的物理页面。一个进程占用的物理页面总数是限定的,当需要置换时,即总数已经用完,新进来一个页面,本进程就需要出去一个老的页面。所谓,朋友圈就那么大,有人进来自然需要有人出去。但是需要注意的是,如果分配给你的总数还没用完,自然是不用置换的,那是最初的红利时期,竞争还不激烈,先到先得。 * 全局:(1)工作集算法(2)缺页率置换算法 * 局部:(1)最优算法(OPT)(2)FIFO先进先出算法(3)LRU最近最久未使用(4)时钟算法 * 最优置换算法(OPT)是指,其所选择的被淘汰页面,将是以后永不使用的,或许是在最长(未来)时间内不再被访问的页面。 * LRU是最近最少使用页面置换算法(Least Recently Used),也就是首先淘汰最长时间未被使用的页面! * LFU是最近最不常用页面置换算法(Least Frequently Used),也就是淘汰一定时期内被访问次数最少的页! * 缺页率置换算法 cpu需要访问的页不在内存中. 因为有缺页,所以就要调入页面,如果内存已慢,就要运用置换算法. 所以所谓的缺页率是(置换的次数+内存的物理块数)/页数.
46. 请用代码实现JAVA类型擦除
【Question】
判断以下代码返回值
public class Example {
public static void main(String[] args) {
List<Long> longArrayList = new ArrayList<>();
List<Integer> integerArrayList = new ArrayList<>();
List<Boolean> booleanLinkedList = new LinkedList<>();
longArrayList.add((long)10);
longArrayList.add((long)20);
integerArrayList.add(10);
integerArrayList.add(20);
booleanLinkedList.add(true);
booleanLinkedList.add(false);
System.out.println(longArrayList.getClass() == integerArrayList.getClass());
System.out.println(longArrayList.getClass() == booleanLinkedList.getClass());
}
}
【Answer】
返回结果
true false
47. 简述用户态、内核态的概念、区别和目的
【Question】
【Answer】
1. 用户态和内核态的主要区别是权限不同,原因是处于性能和安全的考虑,用户程序不应该拥有执行一些关键操作,或者访问关键资源的权限。 2. 用户态到内核态转换的方法分为:系统调用(实际上也是异常),异常,中断三种。
48. 如何理解 Python 的装饰器?
【Question】
讲讲什么是装饰器
【Answer】
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
装饰器的简单写法参考 https://www.cnblogs.com/cicaday/p/python-decorator.html
49. python文件读写异常思考
【Question】
f = open('/Users/michael/test.txt', 'r')
for line in f.readlines():
print(line.strip())
f.close() 上面这串代码有哪些问题?该如何改进?
【Answer】
* 没有判断文件不存在的情况 * readlines是一次性读入全部文件,如果文件过大会导致内存爆掉,用readline或read(size) * 没考虑到IO异常时有可能不能关闭文件 * 没有考虑编码问题,文件写入可能是乱码(decode,encode和import codecs模块来解决)
50. 如何排查HTTP接口夯住问题
【Question】
客户端报case某HTTP接口请求夯住,客户端视角表现为响应超时,如何排查。
【Answer】
1. 首先检车客户端请求资源类型。如果是静态资源检查cdn状态。 2. 如果是动态资源先检查服务端运行状态。 3. 服务端运行状态ok,排查上层代理。先是本机的请求代理,本机的网络状态,再是外层nginx状态 4. 排查客户端状态,是不是正确接受响应。 5. 如果都ok,考虑动态加速服务商的状态。
51. 多线程:同步交叉打印数组
【Question】 有两个数组,任意长度。 有两个线程分别读取数组a和数组b,线程1循环打印数组a中的数字,线程2循环打印数组b中的数,要求交叉,要求第一个数组先输出。
int a[n] = {1,2,3,4}
int b[m] = {'a', 'b', 'c'}
以上述a、b数组为例,最终打印的结果类似于下面:
1a2b3c4a1b2c3a4b1c…
【Answer】
52. GO基础语法考察题 - 闭包
【Question】
go func calc(base int) (func(int) int, func(int) int)
{
add := func(i int) int { base += i return base }
sub := func(i int) int { base -= i return base }
return add, sub
}
func main() {
f1, f2 := calc(10)
fmt.Println(f1(1), f2(2))
fmt.Println(f1(3), f2(4))
fmt.Println(f1(5), f2(6))
fmt.Println(f1(7), f2(8))
}
【Answer】
11 9 12 8 13 7 14 6
53. 写一个宏,返回两个参数的最大值。
【Question】 写一个宏,返回两个参数的最大值。#define MAX(x,y)
调用的时候有什么需要注意的?
【Answer】
#define MAX(x,y) ((x)>(y)?(x):(y)) 避免传入自操作的参数,如 x++ ,会导致可能被自操作两次。传入的若是函数也都可能会被执行两次。 注意 (x>y?x:y) 或 (x)>(y)?(x):(y) 等 都是不对的
54. -C++基础语法考察题:多态、虚函数、析构函数
【Question】
读程序,写出运行后的输出
#include <iostream>
using namespace std;
class A{
public:
virtual void F(){cout << 1 << endl;}
void CallF(){F();}
virtual ~A(){CallF(); F();}
};
class B : public A{
public:
void F(){cout << 2 << endl;}
~B(){}
};
class C : public B{
public:
void F(){cout << 3 << endl;}
void CallF(){F(); A::CallF();}
~C(){CallF();}
};
int main(){
A * p = new C();
p->CallF();
delete p;
return 0;
}
【Answer】
考点1: CallF()不是虚函数,用A的指针类型调用,执行A::CallF()
考点2: 父类的virtual关键字,子类默认继承
考点3: F()是虚函数,尽管在类内函数中被调用,仍然会通过虚函数表找到多态入口
考点4: delete p时会先执行 C::~C()的原因是A的析构函数为虚函数,析构函数的执行顺序,先执行子类,逐层向上执行父类
考点5: 可以追问析构函数不是虚函数的坏处——多态使用时,释放对象时往往只能delete公共父类的指针,此时执行不了子类的析构函数,导致内存泄漏
考点6: 在A::CallF()这里,还是在this下执行,所以虚表仍然有效
考点7: 在析构函数中调用虚函数,由于其子类已经被释放,虚函数表里面不再挂了子类的函数入口,所以此时会执行本类的函数
本题答案:
3
3
3
1
1
55. python 的深拷贝和浅拷贝
【Question】
为什么?以及如何解决?
a = {"key": 1}
b = a
b["key"] = 2
print b
print a
【Answer】
使用copy.deepcopy达到深拷贝的目的
import copy a = {"key": 1} b = copy.deepcopy(a) b["key"] = 2 print b print a
56. 考虑对Linux fork的理解
【Question】 1、请问下面的程序一共输出多少个“-”?,执行一次下面的程序,总共生成了多少个进程?
#include
#include
#include
int main(void)
{
int i;
for(i=0; i<2; i++){
fork();
write(1, "-", 1);
}
wait(NULL);
wait(NULL);
return 0;
}
2、进阶:将write(1, "-", 1)替换成printf("-"),共输出多少个-?
【Answer】
1、主进程main主体代码fork(),write(),fork(),write,所以主进程会输出两次,然后会生成子进程p1和p2 * p1执行write(),fork(),write(),所以会输出两次,然后会生成子进程p11 * p2执行write(),所以会输出一次 * p11执行write(),所以会输出一次 所以总共输出六次,总共有四个进程,main, p1, p2, p11 2、改成printf("-")后,由于printf是带缓冲的(这里是行缓冲),然后并没有输出\n,数据量也小(没有到缓冲区满的程度) * 执行流跟上面一样,p1生成前,main并没有调用printf,所以main还是打印两次,p1也打印两次。 * p2生成时,main调用过一次printf,所以缓冲区会被复制到p2中,p2还会再调一次write,所以会打印两次。 * p11生成时,p1调用过一次printf,所以缓冲区会被复制到p11中,p11还会再调一次write,所以会打印两次。 所以共输出8个-
57. Java多线程同步问题
【Question】 阅读以下Code,回答问题
class A { private int i = 0;
private void foo() {
......
new Thread(new Runnable() {
@Override
public void run() {
......
i = 1;
......
}
}).start();
......
System.out.print(i);
......
} }
其中…..为任意code。那么请问:
- System.out.print(i)打印出的值是多少?
- 如果要确保打印出i的值一定是1,那么可以加写什么样的Code?
【Answer】
1. 打印出来的值是不确定的。依赖于打印的时候子线程里i=1是否执行了,并且值在主线程是否可以立刻读到。 2. 可以使用Object.wait/notify来确保主线程等待子线程i=1结束以后再执行。或者Semaphore也可以。另外最好能知道使用volatile。
58. 32位机最大支持多大物理内存,为什么?
【Question】
【Answer】
考察对内存寻址的理解, 原因是32位机最大的寻址范围是 2^32 = 4G
59. 请说明TCP三次握手和四次挥手流程和状态变化及timewait的设计意义
【Question】 TCP三次握手和四次挥手流程和状态变化,timewait设计意义
【Answer】
60. 进程间通信
【Question】
进程间通信被称为IPC,请问:
问题一:IPC方式有哪些?
问题二:最快IPC的方式是什么?
问题三:semophore方式,两个进程如何能同时访问同一个semophore?如何避免与其他进程冲突?
【Answer】
答案一:pipe popen named_pipe message_queue semophore signal shared_memory socket
答案二:shared_memory
答案三:ftok(filename, proj_id)函数,只要保证filename指向的inode节点是一个,proj_id是一个,则返回的sem_id就是同一个。如果inode变了,则会有问题。
61. 如何创建不可变类
【Question】
Java如何创建不可变类
【Answer】
- 将类声明为final,所以它不能被继承
- 将所有的成员声明为私有的,这样就不允许直接访问这些成员
- 对变量不要提供setter方法
- 将所有可变的成员声明为final,这样只能对它们赋值一次
- 通过构造器初始化所有成员,进行深拷贝(deep copy)
- 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝
62. 计算机组成原理 - 二进制
【Question】
阐述一下对 原码/反码/补码 的理解,相互之间的转换方法
【Answer】
1. 原码 * 原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制: * [+1]原 = 0000 0001 * [-1]原 = 1000 0001 * 第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是: * [1111 1111 , 0111 1111] * [-127 , 127] * 原码是人脑最容易理解和计算的表示方式 2. 反码 * 反码的表示方法是: * 正数的反码是其本身 * 负数的反码是在其原码的基础上, 符号位不变,其余各个位取反. * [+1] = [00000001]原 = [00000001]反 * [-1] = [10000001]原 = [11111110]反 * 可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算 3. 补码 * 补码的表示方法是: * 正数的补码就是其本身 * 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1。 (即在反码的基础上+1) * [+1] = [00000001]原 = [00000001]反 = [00000001]补 * [-1] = [10000001]原 = [11111110]反 = [11111111]补 * 对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值.
63. 请问如何在Python里面怎么执行一行shell命令?
【Question】
考察候选人对python里面常见系统调用的区别的了解,
os.system()
os.popen()
以及subprocess.checkout(),
subprocess.Popen()的异同
【Answer】
面试官参考:https://docs.python.org/2/library/subprocess.html
简要信息:
os.system
该函数返回命令执行结果的返回值,system()函数在执行过程中进行了以下三步操作:
- 1.fork一个子进程;
- 2.在子进程中调用exec函数去执行命令;
- 3.在父进程中调用wait(阻塞)去等待子进程结束。
- 对于fork失败,system()函数返回-1。
- 由于使用该函数经常会莫名其妙地出现错误,但是直接执行命令并没有问题,所以一般建议不要使用。
popen()
- 创建一个管道,通过fork一个子进程,然后该子进程执行命令(非阻塞)。
- 返回值在标准IO流中,该管道用于父子进程间通信。父进程要么从管道读信息,要么向管道写信息,
- 至于是读还是写取决于父进程调用popen时传递的参数(w或r)
subprocess
- subprocess模块是在2.4版本中新增的,官方文档中描述为可以用来替换以下函数:
- os.system、os.spawn、os.popen、popen2,
- class subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None,
- preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None,
- universal_newlines=False, startupinfo=None, creationflags=0)
64. Python 对象构造
【Question】
1. __new__和__init__有啥区别?
2. 利用__new__实现一个单例模式的类:
class Singleton(object):
def __new__(cls, *args, **kwargs):
pass
【Answer】
1. __new__创建对象实例,__init__初始化对象实例。
2. 利用__new__实现一个单例模式的类:
class Singleton(object):
def __new__(cls, *args, **kwargs):
obj = getattr(cls, "__obj", None)
if obj is None:
obj = object.__new__(cls, *args, **kwargs)
setattr(cls, "__obj", obj)
return obj
def main():
o1 = Singleton()
o2 = Singleton()
print(o1 == o2)
if __name__ == "__main__":
main()
65. 请简述IM软件中发送图片显示进度的feature如何实现 以微信为例,请问发送图片的进度条有什么规律,简单画一下曲线,并说明原因
【Question】
- 简述如何实现这一功能
- 以微信为例,发送图片的进度条有什么规律,简单画一下曲线,说明原因
【Answer】
1. 先考虑协议,如果要现应用层做,应用层不能使用HTTP协议,如果直接依赖传输层协议。将图片序列化,计算需要的TCP包数量,拿到每个包传递成功的ack就可以回调更新进度。 2. 曲线的特点:建立链接的时候进度一直为0。所以曲线开始一段是一直在横坐标上的。 因为发送是段落式的,所以进度应该是段落式而不是平滑的,微信可能为了现实进度条平滑做了处理。 TCP链接刚建立的时候,可能因为网络不稳定,导致发送开始时速度偏慢。后来速度慢慢稳定变快。
66. 什么是局部性原理?有哪些运用?
【Question】 局部性原理非常简单, 但在计算机体系和软件工程中都有非常多的运用,考察候选人的知识广度和举一反三的能力
【Answer】
1. 时间局部性:如果程序中的某条指令一旦执行,则短时间内该指令可能再次被执行;如果某数据被访问,则短时间内该数据可能再次被访问。 2. 空间局部性:一旦程序访问了某个存储单元,则不久之后。其附近的存储单元也很可能被访问。 从计算机体系的角度,cache-memory-disk这样的层次结构,本身就运用了局部性原理,将数据分为block,最近使用的数据以及其相邻的数据被存放在更快的存储中。 从工程的角度看,我们常常用用到的db cache、LRU等,本身也利用了时间局部性的原理,将最近访问到的数据缓存起来,大概率短时间内还可以再用到,减少对其他存储的压力。
67. 请简述内存颠簸的含义以及解决方案
【Question】
内存颠簸的含义以及解决策略
【Answer】
颠簸本质上是指频繁的页调度行为。页面在内存和辅存间频繁交换的现象。会导致整个系统的效率急剧下降,这种现象称为颠簸(抖动)。 - 内存颠簸的解决策略包括: - 如果是因为页面替换策略失误,可以修改替换算法来解决这个问题; - 如果是因为进程太多,无法同时将所有频繁访问的页面调入内存,则要降低进程的数量; - 否则,增加物理内存容量。
68. 什么是死锁?其条件是什么?怎样避免死锁?
【Question】
什么是死锁?其条件是什么?怎样避免死锁?
【Answer】
什么是死锁?其条件是什么?怎样避免死锁?
69. 请问Golang Receiver 与 Point Receiver 有什么区别?
【Question】
判断以下代码返回值,并解释原因
package main
import (
"fmt"
)
type Detail struct {
x int32
}
type Item struct {
v int32
pv *int32
d Detail
pd *Detail
}
func (i Item)Do() {
i.v = 11
*(i.pv) = 21
i.d.x = 31
i.pd.x = 41
}
func (i *Item)PtrDo() {
i.v = 9
*(i.pv) = 19
i.d.x = 29
i.pd.x = 39
}
func main() {
a := int32(10)
b := int32(20)
c := int32(30)
d := int32(40)
i := Item {a,&b,Detail{c},&Detail{d}}
fmt.Println("orig",i.v,*(i.pv),i.d.x,i.pd.x)
i.Do()
fmt.Println("do",i.v,*(i.pv),i.d.x,i.pd.x)
i.PtrDo()
fmt.Println("ptrdo",i.v,*(i.pv),i.d.x,i.pd.x)
}
【Answer】
返回结果
orig 10 20 30 40 do 10 21 30 41 ptrdo 9 19 29 39
70. HTTP的长连接和短连接
【Question】
HTTP的长连接和短连接
【Answer】
长连接:建立SOCKET连接后发送后接收完数据后,保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据。HTTP1.1规定了默认保持长连接(持久连接)
短连接:建立SOCKET连接后发送后接收完数据后马上断开连接,一般银行都使用短连接
71. golang中如何实现producer-consumer模式
【Question】 考察对channel的理解和使用,包括对阻塞队列和非阻塞队列的选择和权衡,以及在超时方面是否有控制和考虑等
【Answer】
72. 实现一个 isEqual 函数用于比较两个对象是否相等
【Question】 实现一个 isEqual 函数用于比较两个对象是否相等,函数接受两个对象参数 ,如果两个对象“相等”则返回 true,否则返回 false。 判断规则:如果两个对象有相同的属性,以及它们的属性有相同的值,那么这认为两个对象就相等。 例如:
var stooge = {name: 'moe', luckyNumbers: [13, 27, 34]};
var clone = {name: 'moe', luckyNumbers: [13, 27, 34]};
stooge == clone;
=> false
isEqual(stooge, clone);
=> true
【Answer】
```javascript var isEqual = function (x, y) { // === 的使用 // If both x and y are null or undefined and exactly the same if (x === y) { return true; } // 优化和鲁棒性考虑 // If they are not strictly equal, they both need to be Objects if (!(x instanceof Object) || !(y instanceof Object)) { return false; } // 优化考虑 // They must have the exact same prototype chain,the closest we can do is // test the constructor. if (x.constructor !== y.constructor) { return false; } // 对象属性的枚举 for (var p in x) { // Inherited properties were tested using x.constructor === y.constructor if (x.hasOwnProperty(p)) { // Allows comparing x[ p ] and y[ p ] when set to undefined if (!y.hasOwnProperty(p)) { return false; } // === 的使用 // If they have the same strict value or identity then they are equal if (x[ p ] === y[ p ]) { continue; } // 数据类型的判断 // Numbers, Strings, Functions, Booleans must be strictly equal if (typeof (x[ p ]) !== 'object') { return false; } // 递归调用 // 属性为对象或数组时需要递归调用比较 // Objects and Arrays must be tested recursively if (!isEqual(x[ p ], y[ p ])) { return false; } } } // 全面性的考虑 for (p in y) { // allows x[ p ] to be set to undefined if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) { return false; } } return true; };
73. C++基础语法考察题 - 指针
【Question】
#include
using namespace std;
class taikoo
{
public:
int base;
public:
taikoo(int base)
{
this->base = base;
}
virtual void getSomeSugar(int * val)
{
cout<base;
cout<<*val<getSomeSugar(&val);
}
int main()
{
int val = 100;
const int * p1 = &val;
int * const p2 = &val;
int const * p3 = &val;
cout<<*p1<<","<<*p2<<","<<*p3<<","<getSomeSugar(p1);
pt->getSomeSugar(p2);
pt->getSomeSugar(p3);
pt->getSomeSugar(&val);
funcCall(pt, *p1);
funcCall(pt, *p2);
funcCall(pt, *p3);
funcCall(pt, &val);
}
【Answer】
这道题考察了候选人对于 1. 指针 & 指针常量 & 常量指针的基本理解 1. 考察了类型转化的理解 1. 考察了const的使用习惯和规则
74. C++ 智能指针
【Question】
C++ STL里的四种智能指针
【Answer】
C++ 标准模板库 STL(Standard Template Library) 一共给我们提供了四种智能指针:auto_ptr、unique_ptr、shared_ptr 和 weak_ptr,其中 auto_ptr 是 C++98 提出的,C++11 已将其摒弃,并提出了 unique_ptr 替代 auto_ptr。虽然 auto_ptr 已被摒弃,但在实际项目中仍可使用,但建议使用更加安全的 unique_ptr。shared_ptr 和 weak_ptr 则是 C+11 从准标准库 Boost 中引入的两种智能指针。
unique_ptr
unique_ptr 由 C++11 引入,旨在替代不安全的 auto_ptr。unique_ptr 是一种定义在头文件
中的智能指针。它持有对对象的独有权——两个unique_ptr 不能指向一个对象,即 unique_ptr 不共享它所管理的对象。它无法复制到其他 unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL)算法。只能移动 unique_ptr,即对资源管理权限可以实现转移。这意味着,内存资源所有权可以转移到另一个 unique_ptr,并且原始 unique_ptr 不再拥有此资源。</p>
auto_ptr
auto_ptr 同样是 STL 智能指针家族的成员之一,由 C++98 引入,定义在头文件<memory>。其功能和用法类似于 unique_ptr,由 new expression 获得对象,在 auto_ptr 对象销毁时,他所管理的对象也会自动被 delete 掉。
shared_ptr
shared_ptr 是一个标准的共享所有权的智能指针,允许多个指针指向同一个对象,定义在 memory 文件中,命名空间为 std。shared_ptr最初实现于Boost库中,后由 C++11 引入到 C++ STL。shared_ptr 利用引用计数的方式实现了对所管理的对象的所有权的分享,即允许多个 shared_ptr 共同管理同一个对象。像 shared_ptr 这种智能指针,《Effective C++》称之为“引用计数型智能指针”(reference-counting smart pointer,RCSP)。
shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销:
(1)shared_ptr 对象除了包括一个所拥有对象的指针外,还必须包括一个引用计数代理对象的指针;
(2)时间上的开销主要在初始化和拷贝操作上, * 和 -> 操作符重载的开销跟 auto_ptr 是一样;
(3)开销并不是我们不使用 shared_ptr 的理由,,永远不要进行不成熟的优化,直到性能分析器告诉你这一点。
weak_ptr
weak_ptr 被设计为与 shared_ptr 共同工作,可以从一个 shared_ptr 或者另一个 weak_ptr 对象构造而来。weak_ptr 是为了配合 shared_ptr 而引入的一种智能指针,它更像是 shared_ptr 的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载 operator* 和 operator-> ,因此取名为 weak,表明其是功能较弱的智能指针。它的最大作用在于协助 shared_ptr 工作,可获得资源的观测权,像旁观者那样观测资源的使用情况。观察者意味着 weak_ptr 只对 shared_ptr 进行引用,而不改变其引用计数,当被观察的 shared_ptr 失效后,相应的 weak_ptr 也相应失效。
</pre> </details> --- ### 75. 请简单介绍下 Golang 中 make 和 new 的区别 【Question】
--- ### 76. java内存结构?string类型存储在哪个区?GC算法?哪些方法可以触发GC?讲讲ZGC? 【Question】【Answer】
new(T) 是为一个 T 类型的新值分配空间, 并将此空间初始化为 T 的零值, 并返回这块内存空间的地址, 也就是 T 类型的指针 *T, 该指针指向 T 类型值占用的那块内存. make(T) 返回的是初始化之后的 T, 且只能用于 slice, map, channel 三种类型. make(T, args) 返回初始化之后 T 类型的值, 且此新值并不是 T 类型的零值, 也不是 T 类型的指针 *T, 而是 T 类型值经过初始化之后的引用.
【Answer】
- 栈、存储局部变量表、操作栈、动态链接、方法出口等信息。局部变量表存放了编译期可知的各种基本数据类型和对象引用类型。
- 堆、对象实例
- 程序计数器,线程私有,可以看作是当前线程所执行的字节码的行号指示器。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
- 方法区,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 本地方法区,本地方法栈则是为虚拟机使用到的Native 方法服务
描述认证流程
【Answer】
参考答案:
SSL单向验证总共有四步
第一步,客户端向服务器端发起Client Hello,请求内容包括:
客户端支持的SSL/TLS协议版本列表;
客户端支持的对称加密算法列表;
客户端生成的随机数A;
第二步,服务器端回应客户端Server Hello,回应内容包括:
双方都支持的SSL/TLS协议版本;
双方都支持的对称加密算法;
服务器秘钥库中的证书;
服务器端生成的随机数B;
第三步,客户端收到服务器端回应,客户端检查服务器端证书是否合法,验证内容如下:
服务器端证书是否过期;
服务器端证书是否被吊销;
服务器端证书是否可信;
服务器端证书域名和客户端请求域名是否一致。
验证通过后,客户端回应服务器端,回应内容包括:
客户端生成一个“随机数C”,“随机数C”也被称为”pre-master-key”,然后使用服务器端证书中的公钥加密“随机数C”,将加密后的“随机数C”发送给服务器端;
第四步,服务器端使用秘钥库中的私钥解密加密后的“随机数C”得到“随机数C”,此时客户端和服务器端都拿到了随机数A、随机数B、随机数C,双发通过这3个随机数使用相同的秘钥交换算法计算得到相同的对称加密秘钥,这个对称加密秘钥就作为客户端和服务器端数据传输时对称加密使用的秘钥。
服务器端和客户端,握手结束,之后就可以用对称加密传输数据了。
SSL双向验证
SSL单向验证过程中,客户端会验证自己访问的服务器端,服务器端对客户端不做验证。如果服务器端验证客户端,则需要开启服务器端验证,这就是双向验证。
SSL双向验证和单向验证的不同之处在于:
第二步中服务器端第一次回应客户端的Server Hello消息中,会要求客户端提供客户端证书;
第三步中客户端验证完服务器端证书后,回应的内容中,会增加两个信息:
客户端证书;
客户端证书验证消息(CertificateVerify message):客户端将之前所有收到的和发送的消息组合起来,并用hash算法得到一个hash值,然后用客户端密钥库的私钥对这个hash进行签名,这个签名就是CertificateVerify message;
服务器端收到客户端证书后,会做如下处理:
确认客户端发送的证书是有效合法的;
用客户端证书中的公钥验证收到信息中的签名,以确定这个证书是客户端发出的;
服务器端和客户端,握手结束,之后就可以用对称加密传输数据了。
【Answer】
1)应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
2)TCP、UDP
3)是否有连接,是否可靠,报文/字节流、拥塞
4)请求响应应答
5)所以TIME_WAIT 是这么一种状态:TCP 四次握手结束后,连接双方都不再交换消息,但主动关闭的一方保持这个连接在一段时间内不可用。可靠关闭
写两个goroutine。交替打印1和2。要求:不能sleep。不能用有长度的chan,chan最多一个。
- 题目1 1和2顺序没有要求的情况
- 题目2 1和2顺序有要求,必须先打印1
// 修改下边的代码,实现12交替打印。各打印10次
func print12() {
go func() {
fmt.Println(1)
}()
go func() {
fmt.Println(2)
}()
}
【Answer】
思路:搞一个长度为0的chan,两个相互阻塞。一个打印后,进chan,当前只有另一个可以出chan。如此交替就可以,需要注意,在两个goroutine启动后需要初始化塞进去一个。
题目1答案
func print12() { ch := make(chan int) go func() { for i := 0; i < 10; i++ { <-ch fmt.Println(1) ch <- 0 } }() go func() { for i := 0; i < 10; i++ { <-ch fmt.Println(2) ch <- 0 } }() ch <- 0 }
题目2答案,以先打印1为例
func print12() { ch := make(chan int) go func() { for i := 0; i < 10; i++ { <-ch fmt.Println(1) ch <- 1 } }() go func() { for i := 0; i < 10; i++ { if i == 0 { // 判断第一次是否先打印了1 if <-ch == 0 { ch <- 1 } else { fmt.Println(2) ch <- 1 continue } } <-ch fmt.Println(2) // 2最后打印,所以到9后就可以跳出 if i == 9 { break } ch <- 1 } }() ch <- 0 }
或者(解法更简洁)
func print12() { ch := make(chan int) go func() { fmt.Print(1) ch <- 0 for i := 1; i < 10; i++ { <-ch fmt.Print(1) ch <- 0 } }() go func() { for i := 0; i < 9; i++ { <-ch fmt.Print(2) ch <- 0 } <-ch fmt.Print(2) }() }
【Answer】
### 首先需要明确这段代码运行在什么线程下? #### 1. 主线程下 > sleep 1, print 2, sleep 2, print 1, print 3 原因:主队列是穿行队列,block运行顺序是入队顺序 #### 2. 自线程下 > print 2, sleep 2, print 1, print 3 原因,子线程下外面的sleep 1对里面入队没影响,其他同上
【Answer】
1. app buffer->clib buffer -> page cache -> io queue -> driver -> disk cache -> disk 2. app buffer->clib buffer 用户态,后面是内核态 (3.0) 3. fwrite写到page cache层,如果fwrite之后进程挂掉数据会丢失, 如果想到直接写到磁盘,可以再打开文件的时候用o_direct参数 (3.5) 可以根据回答再发散问一下
【Answer】
int (*p)[10] 是指针,指向一个有10个int的数组 int *p[10] 是数组,每个元素是指向int的指针 int *f(int i) 是返回int指针的函数,是一个函数的声明 int (*f)(int i) 是指向函数的指针,定义了一个指针
代码-1
#include<stdio.h>
#include<math.h>
int main(int argc,char *argv[]){
printf("%lf",exp(2));
return 0;
}
代码-2
#include<stdio.h>
#include<math.h>
int main(int argc,char *argv[]){
int i = 2;
printf("%lf",exp(i));
return 0;
}
问题-1:
[R]➜ tmp gcc --static code-1.c // 编译通过
[R]➜ tmp gcc --static code-2.c // 编译出错
/tmp/ccBLcbCw.o: In function `main':
code-2.c:(.text+0x20): undefined reference to `exp'
原因是什么?如何解决?
问题-2:
[R]➜ tmp gcc --static -lm code-2.c
/tmp/cc3XLM0K.o: In function `main':
code-2.c:(.text+0x20): undefined reference to `exp'
collect2: error: ld returned 1 exit status
加上-lm为什么还会报错?如何解决?
【Answer】
问题-1:
[R]➜ tmp gcc --static code-1.c -S -o code-1.s [R]➜ tmp cat --static code-1.s .... 代码片段 ..... .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $32, %rsp movl %edi, -4(%rbp) movq %rsi, -16(%rbp) movq .LC0(%rip), %rax movq %rax, -24(%rbp) movsd -24(%rbp), %xmm0 leaq .LC1(%rip), %rdi movl $1, %eax call printf@PLT movl $0, %eax leave
根据汇编代码可以看出,当掺入exp函数时静态值时。没有call exp指令,而是在编译阶段直接计算好了。(xmm0-xmm7为浮点型计算寄存器:参考https://en.wikibooks.org/wiki/X86_Assembly/SSE )。所以不需要额外链接其他库。
而当传入的值是变量的时候汇编指令就调用了call exp
pxor %xmm0, %xmm0 cvtsi2sd -4(%rbp), %xmm0 call exp@PLT leaq .LC0(%rip), %rdi movl $1, %eax
这时候就需要需要exp的符号表了。需要链接动态库了。
因此需要增加 -lm 来解决。
问题二:
链接过程中,需要进行符号解析,并且是按照顺序解析;如果库链接在前,就可能出现库中的符号不会被需要,链接器不会把它加到未解析的符号集合中,那么后面引用这个符号的目标文件就不能解析该引用,导致最后链接失败。因此链接库的一般准则是将它们放在命令行的结尾。
问题一:golang/python怎么进行异常处理?
问题二:各自的优缺点?局限性?
问题三:发生异常后,如何进行善后,例如保证资源得到释放?(资源:例如从连接池获取到的一个数据库连接)
【Answer】
答案一:go使用error类型;python使用Exception类型
答案二:
go优点:完善。缺点:麻烦。
python优点:方便。缺点:发生异常时不知道函数的具体异常位置,异常处理时需要自行检查代码执行到哪儿了,释放资源时就需要额外一次判断;如果raise的姿势不对时,会导致调用信息丢失;假设输入无误,直接通过异常来处理,出错时调用方或者用户无法知道具体哪里出了问题。
答案三:go用defer,python用finally。
【Answer】
1. 进程的堆栈内核在创建进程的时候,在创建task_struct的同时,会为进程创建相应的堆栈。每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存在于内核空间。当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;当进程在内核空间时,cpu堆栈指针寄存器里面的内容是内核栈空间地址,使用内核栈。 2. 进程用户栈和内核栈的切换当进程因为中断或者系统调用而陷入内核态之行时,进程所使用的堆栈也要从用户栈转到内核栈。进程陷入内核态后,先把用户态堆栈的地址保存在内核栈之中,然后设置堆栈指针寄存器的内容为内核栈的地址,这样就完成了用户栈向内核栈的转换;当进程从内核态恢复到用户态之行时,在内核态之行的最后将保存在内核栈里面的用户栈的地址恢复到堆栈指针寄存器即可。这样就实现了内核栈和用户栈的互转。那么,我们知道从内核转到用户态时用户栈的地址是在陷入内核的时候保存在内核栈里面的,但是在陷入内核的时候,我们是如何知道内核栈的地址的呢?关键在进程从用户态转到内核态的时候,进程的内核栈总是空的。这是因为,当进程在用户态运行时,使用的是用户栈,当进程陷入到内核态时,内核栈保存进程在内核态运行的相关信心,但是一旦进程返回到用户态后,内核栈中保存的信息无效,会全部恢复,因此每次进程从用户态陷入内核的时候得到的内核栈都是空的。所以在进程陷入内核的时候,直接把内核栈的栈顶地址给堆栈指针寄存器就可以了。
【Answer】
范围查询、非叶子节点只存键不存值,因而一次IO可以捞取更多的索引
多路树可以减少树的层数
生产环境中B+树的高度总是3-4层
在MySQL中我们的InnoDB页的大小默认是16k,一个高度为3的B+树可以存放:1170*1170*16=21902400条这样的记录
多态的概念与实现原理
虚函数和纯虚函数的区别
【Answer】
什么是多态?
程序运行时,父类指针可以根据具体指向的子类对象,来执行不同的函数,表现为多态
C++ 多态的实现原理?
- 当类中存在虚函数时,编译器会在类中自动生成一个虚函数表
- 虚函数表是一个存储类成员函数指针的数据结构
- 虚函数表由编译器自动生成和维护
- virtual 修饰的成员函数会被编译器放入虚函数表中
- 存在虚函数时,编译器会为对象自动生成一个指向虚函数表的指针(通常称之为 vptr 指针)
虚函数和纯虚函数区别?
首先:强调一个概念
定义一个函数为虚函数,不代表函数没有被实现。
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。
定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
1、虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数;
2、友元函数是虚函数?友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题;
3、析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。
个人认为纯虚函数的引入,是出于两个目的:
1、为了安全,因为避免任何需要明确但是因为不小心而导致的未知的结果,提醒子类去做应做的实现。
2、为了效率,不是程序执行的效率,而是为了编码的效率。
【Answer】
```cpp template ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value) { ForwardIt it; typename std::iterator_traits::difference_type count, step; count = std::distance(first, last); while (count > 0) { it = first; step = count / 2; std::advance(it, step); if (*it < value) { first = ++it; count -= step + 1; } else count = step; } return first; } ```
【Answer】
>1. 观察者(observer) 模式广泛用于客户端Javascript编程中。所有的浏览器事件都是该模式的例子。它的另一个名字也称为自定义事件(custom events),与那些由浏览器触发的事件相比,自定义事件表示是由你编程实现的事件。此外,该模式的另一个别名也称为订阅/发布(subscriber/publisher)模式。 >1. subscribers 一个数组 >1. subscribe() 将订阅者添加到subscribers数组中 >1. unsubscribe() 从subscribers数组中删除订阅者 >1. publish() 循环遍历subscribers数组中的每一个元素,并且调用他们注册时所提供的方法 >1.所有这三种方法都需要一个topic参数,因为发布者可能触发多个事件(比如同时发布一本杂志和一份报纸)而用户可能仅选择订阅其中一种,而不是另外一种。 ```javascript var pubsub = {}; (function (q) { var topics = {}, // 回调函数存放的数组 subUid = -1; // 发布方法 q.publish = function (topic, args) { if (!topics[topic]) { return false; } setTimeout(function () { var subscribers = topics[topic], len = subscribers ? subscribers.length : 0; while (len--) { subscribers[len].func(topic, args); } }, 0); return true; }; //订阅方法 q.subscribe = function (topic, func) { if (!topics[topic]) { topics[topic] = []; } var token = (++subUid).toString(); topics[topic].push({ token: token, func: func }); return token; }; //退订方法 q.unsubscribe = function (token) { for (var m in topics) { if (topics[m]) { for (var i = 0, j = topics[m].length; i < j; i++) { if (topics[m][i].token === token) { topics[m].splice(i, 1); return token; } } } } return false; }; }(pubsub)); ```
【Answer】
"这道题考察了候选人对于 1. 指针 & 指针常量 & 常量指针的基本理解 2. 考察了类型转化的理解 3. 考察了const的使用习惯和规则"
【Answer】
1. Reactor模式适用于同步IO,读写操作由应用程序完成,读写过程中阻塞;Proactor模式适用于异步IO,读写操作由内核负责,应用程序不阻塞 2. Reactor实现相对简单,对于耗时短的处理场景处理高效,几乎所有的linux下IO模式都使用此种模式;Proactor实现逻辑复杂, 使用较少 3. Reactor处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理;Proactor性能更高,能够处理耗时长的并发场景;
TCP怎么保证错误重传
【Answer】
1. 当报文使用TCP传输时,重传计时器启动,收到ACK时计时器停止
2. 重传计时器有重传超时值(RTO)
3. 当报文发送之后,在RTO时间内发送方未收到ACK应答,将重发数据,并将RTO值扩大2倍。如此持续下去,每次重传RTO都翻倍,直到收到ACK报文或重传次数达到3次(不再重传)
python是否支持多线程?谈下对GIL的理解。
【Answer】
GIL 是python的全局解释器锁,同一进程中假如有多个线程运行,一个线程在运行python程序的时候会霸占python解释器(加了一把锁即GIL),使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。如果线程运行过程中遇到耗时操作,则解释器锁解开,使其他线程运行。所以在多线程中,线程的运行仍是有先后顺序的,并不是同时进行。
多进程中因为每个进程都能被系统分配资源,相当于每个进程有了一个python解释器,所以多进程可以实现多个进程的同时运行,缺点是进程系统资源开销大。
什么是进程(Process)和线程(Thread)
【Answer】
进程:已运行程序的实体
- 系统进行资源分配和调度的独立单位
- 进程需要系统分配资源:CPU使用时间、存储器、文件、I/O设备
线程:进程的一个实体
- CPU进行调度和分派的基本单元
- 线程只需分配较少资源(如程序计数器,寄存器和栈),可与其他的线程共享进程全部资源
对于一个给定的Java类名字符串进行长度压缩,返回一个长度不超过N的“最佳表示”,具体规则如下:
1. Java的类名使用英文标点“点号”分割的多个部分组成,每一部分满足正则^[a-zA-Z][a-zA-Z0-9]+。
2. 类名中的“点”不能省略,最靠右侧的一部分不能省略。
3. 其余部分均可使用每一部分的第一个字符省略表示。
由此我们可以定义类名的“最简表示”,如“org.elasticsearch.rest.action.document.RestMultiTermVectorsAction”的最简表示为“o.e.r.a.d.RestMultiTermVectorsAction”
4. 如果一个类名的“最简表示”,长度依然大于N,则最佳表示不存在,返回“”(空字符串)。
5. 如果一个类名的“最简表示”,长度小于N,那么为了展示更多信息,我们需要从右向左,将被省略的部分逐个展开,使其在长度不超过N的情况下,尽量展示更多信息。
举例:对于类名“org.elasticsearch.rest.action.document.RestMultiTermVectorsAction”,约定长度N=50。
最简表示“o.e.r.a.d.RestMultiTermVectorsAction”,长度为36,36<50
尝试展开右起第二段“document”,表示为“o.e.r.a.document.RestMultiTermVectorsAction”,长度为43,43<50
继续尝试展开右起第三段“action”,表示为“o.e.r.action.document.RestMultiTermVectorsAction”,长度为48,48<50
继续尝试展开右起第四段“rest”,表示为“o.e.rest.action.document.RestMultiTermVectorsAction”,长度为51,51>50
故类名“org.elasticsearch.rest.action.document.RestMultiTermVectorsAction”在N=50的情况下,最佳表示为“o.e.r.action.document.RestMultiTermVectorsAction”
对于数据规模,限定如下:
N为uint32,每个类名最多包含2^8-1个部分,每个部分最长2^8-1个字符。
【Answer】
参考解答:
def ans(name: str, length: int): tokens = name.split(".") short_tokens = list() for token in tokens[:-1]: short_tokens.append(token[0]) short_tokens.append(tokens[-1]) ret = ".".join(short_tokens) if len(ret) > length: return "" for n, token in enumerate(tokens[-2::-1]): short_tokens[-2 - n] = token tmp = ".".join(short_tokens) if len(tmp) <= length: ret = tmp else: break return ret
双亲委派的双是什么意思
好处有什么
【Answer】
双是一个典型的翻译误解,没有双,只有单线
好处:
- 优先级机制
- 防止核心类被篡改
【Answer】
Java线程池的工作流程为:线程池刚被创建时,只是向系统申请一个用于执行线程队列和管理线程池的线程资源。在调用execute()添加一个任务时,线程池会按照以下流程执行任务。- 如果正在运行的线程数量少于corePoolSize(用户定义的核心线程数),线程池就会立刻创建线程并执行该线程任务- 如果正在运行的线程数量大于等于corePoolSize,该任务就将被放入阻塞队列中在阻塞队列已满且正在运行的线程数量少于maximumPoolSize时,线程池会创建非核心线程立刻执行该线程任务- 在阻塞队列已满且正在运行的线程数量大于等于maximumPoolSize时,线程池将拒绝执行该线程任务并抛出RejectException异常- 在线程任务执行完毕后,该任务将被从线程池队列中移除,线程池将从队列中取下一个线程任务继续执行- 在非核心线程处于空闲状态的时间超过keepAliveTime时间时,正在运行的线程数量超过corePoolSize,该非核心线程将会被认定为空闲线程并停止。因此,在线程池中所有线程任务都执行完毕后,线程池会收缩到corePoolSize大小。
【Answer】
静态链接:在程序执行之前完成所有的组装工作,生成一个可执行的目标文件(EXE文件)动态链接:在程序已经为了执行被装入内存之后完成链接工作,并且在内存中一般只保留该编译单元的一份拷贝。优缺点:静态链接的缺点很明显,一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖。另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。动态链接的优点,就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副本,而是这多个程序在执行时共享同一份副本;另一个优点是,更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。缺点,因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。原理:静态链接:把所有文件压缩打包成可执行文件动态链接:动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
- 标记清楚算法
- 复制算法,以及为什么可用?
- 复制-整理算法(Mark-Compact)
- 分代算法
【Answer】
- 标记清除算法,放到连续空间中,会导致空间碎片;分配大对象时,可能空间不够导致需要触发GC;
- 大部分对象稍纵即逝,不需要复制;空间浪费问题,可改进,放不下后需要分配担保;
- 只复制一部分
- 青年、老年
判断以下代码的输出是什么
a = 10
b = 20
c = 30
def do():
print(a)
print(b)
print(c)
c += 1
print(c)
do()
【Answer】
上述代码是有语法错误的。会下面的出现报错
UnboundLocalError: local variable 'c' referenced before assignment
可以修改成这样
a = 10 b = 20 c = 30 def do(): global c print(a) print(b) print(c) c += 1 print(c) do()
那么输出就是
10 20 30 31
【Answer】
- 1、应用数据被分割成TCP认为最适合发送的数据块。 - 2、超时重传:当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。 - 3、TCP给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。 - 4、校验和:TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段。 - 5、TCP的接收端会丢弃重复的数据。 - 6、流量控制:TCP连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的我数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP使用的流量控制协议是可变大小的滑动窗口协议。 - 7、拥塞控制:当网络拥塞时,减少数据的发送。
【Answer】
开放问题,考查候选人对HTTP协议与Header的理解与应用 1. 能知晓GET/POST等区别 2. 能知晓Header中的常见key,例如UA、cookie、refer、host等 3. 能知晓是tcp协议 4. 不能,这里考察其对HTTP协议本身的理解 5. content-length,这里考察其对HTTP协议本身的理解
【Answer】
- 区别和联系 - 线程是CPU调度的基本单位,同一个进程中可以有多个线程,但至少有一个线程 - 进程是操作系统分配资源的最小单位 - 一个进程中有哪些数据段 - 代码段(Text Segment):保存代码 - 静态数据段(Data Segment):静态数据 - 未初始化数据段(BSS) - 栈(Stack):向下增长 - 堆(Heap):向上增长 - .rodata - 同一个进程下的不同线程间有哪些数据段是共享的? - 除Stack外均共享
【Answer】
1. 客户端发起ssl请求,服务端返回CA证书 2. 客户端解析证书验证真伪,获取用于非对称加密的公钥 3. 利用公钥加密发送对称加密的私钥 4. 利用对称加密传输数据
【Answer】
* 保证TCP协议全双工连接能够可靠关闭,直接关闭的话,如果服务器没有收到ACK,会重复发FIN。 * 保证这次连接的重复数据从网络中消失,如果上次的socket和这次的socket处理的程序一样,就会导致这次连接把上次的数据加进来了。
【Answer】
1. 普通管道(pipe):管道是一种单工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系或者兄弟进程之间。 2. 命名管道(FIFO):命名管道也是半双工的通信方式,但是它允许无亲缘关系的进程间的通信。 3. 信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 4. 消息队列(message queue):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓存区大小受限等缺点。 5. 信号(signal):信号是一种比较复杂的通信方式,用于通知接受进程某个事件已经发生。 6. 共享内存(shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。 7. 套接字(socket):套接字也是一种进程间通信的机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
【Answer】
* 拥塞控制:防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。 * 对比流量控制:拥塞控制是一个全局的过程,涉及到所有的主机、路由器、以及降低网络相关的所有因素。流量控制往往指点对点通信量的控制。是端对端的问题。 * 几种拥塞控制的方法: 慢开始,拥塞避免,快重传,快恢复。 1. 慢开始:主机开发发送数据报时,如果立即将大量的数据注入到网络中,可能会出现网络的拥塞。慢启动算法就是在主机刚开始发送数据报的时候先探测一下网络的状况,如果网络状况良好,发送方每发送一次文段都能正确的接受确认报文段。那么就从小到大的增加拥塞窗口的大小,即增加发送窗口的大小。 2. 拥塞避免:是让cwnd缓慢的增加而不是加倍的增长,每经历过一次往返时间就使cwnd增加1,而不是加倍,这样使cwnd缓慢的增长,比慢启动要慢的多。 3. 快重传:快重传算法要求首先接收方收到一个失序的报文段后立刻发出重复确认,而不要等待自己发送数据时才进行捎带确认。 4. 快恢复:当发送方连续接到三个确认时,就执行乘法减小算法,把慢开始门限减半,但是接下来并不执行慢开始算法。而是把cwnd设置为ssthresh的一半,然后执行拥塞避免算法,使拥塞窗口缓慢增大。
【Answer】
三次握手:sync+sync/ack+ack 四次挥手:fin+ack+fin+ack,知识点tcp是双工网络,半断开状态,time-wait的出现
【Answer】
1. TCP是面向连接且可靠的,UDP是面向非连接且非可靠 2. TCP是面向字节流的,UDP是面向报文流 3. TCP的传输效率低,UDP传输效率高 4. TCP有流量控制,拥塞控制等,UDP没有 5. TCP适用于对可靠性要求比较高,但对效率要求低的场景,而UDP适用于对可靠性要求比较低,但对效率要求比较高的场景 6. TCP协议应用: HTTP/FTP/TELNET/SMTP UDP协议应用: DNS/SNMP
【Answer】
* IO多路复用都是一个进程可以监控多个文件描述符,一旦某个描述符就绪,能够通知程序进行相应的读写操作。 * select:调用过程 a 将fd_set从用户空间拷贝到内核空间 b: 遍历fd_set 将就绪的fd拷贝到用户空间;缺点是 a select支持的fd数目是通过FD_SETSIZE指定,存在数目限制 b: select调用时需要在用户和内核空间进行fd_set的拷贝,开销比较大 c: select调用时需要在内核中遍历fd_set,IO效率低 * poll:select的增强版,用链表来实现fd_set, 故没有fd数目的限制,但效率依旧不高 * epoll:每次调用epoll_ctl函数进行注册时候,会将fd_set拷贝到内核空间,避免每次调用时重复拷贝;无需遍历fd_set, epoll维护一个就绪列表,一旦某个fd就绪,就把fd插入到链表中,epoll_wait只需遍历该链接即可;epoll没有fd数据限制,它所支持的fd上限是最大可以打开文件的数目 * epoll支持两种触发方式:水平触发(LT)和边缘触发(ET),LT和ET最大的区别就是epoll_wait检测fd就绪后,应用程序可以不立即处理,下次调用epoll_wait会再次通知此事件;而ET则不支持,一旦通知之后就再通知。
【Answer】
这个问题可以讨论的点很多,知识面广的了解的点很多。 可以就候选人擅长在个别点深入讨论。 1. 浏览器如何向网卡发送数据 可以讨论的点:从浏览器到浏览器内核、HTTP、DNS、HTTP-DNS、Socket、网络协议 2. 数据如何从本机网卡发送到服务器 可以讨论的点:从内核到网络适配器(NIC)、有线或Wi-Fi接入、运营商网络路由、主干网传输、IDC、服务器 3. 服务器接收到数据后会进行哪些处理 可以讨论的点:负载均衡(硬件、软件)、反向代理、web server、后端语言框架、数据存储(持久、缓存)、CDN 4. 服务器返回数据后浏览器如何处理 可以讨论的点:从01到字符(编解码)、外链资源加载、JS执行、CSS渲染、从内存到LCD显示
【Answer】
1. TCP、UDP的基本概念 UDP:面向报文 面向报文的传输方式应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。 因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片,降低效率,若太短,会使IP太短。 TCP面向字节流 虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传输的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。 2. TCP、UDP的区别 可靠性:TCP三次握手四次挥手是可靠的,UDP不可靠 连接性:TCP面向连接,UDP无连接 报文:TCP面向字节流,UDP面向报文(保留报文的边界) 效率:TCP传输效率低,UDP传输效率高 双工性:TCP全双工,UDP一对一、一对多、多对一、多对多 流量控制:TCP有滑动窗口 拥塞控制:TCP有慢开始、拥塞避免、快重传、快恢复 传输速度:TCP慢,UDP快 应用场合:TCP对效率要求相对较低,对准确性要求较高或者是要求有连接的场景;UDP对效率要求较高,对准确性要求较低 3. 具体的应用场景 TCP使用场景:当对网络通讯质量有要求的时候,比如整个数据要准确无误的传送给对方,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 应用: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP QQ文件传输 UDP使用场景:当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快。 应用: QQ语音 视频 游戏 4. TCP、UDP优缺点 TCP的优点:可靠,稳定。TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。 TCP的缺点:慢,效率低,占用系统资源高,易被攻击。TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。 UDP的优点:快,UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击。 UDP的缺点:不可靠,不稳定。因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。
【Answer】
1. 建连三次握手和断连四次挥手的过程,注明SYN/FIN/ACK 2. TCP交互的整个过程中总共有11个状态: CLOSED/LISTEN/SYNC_SENT/SYNC_RECV/ESTABLISHED/FIN_WIAT_1/FIN_WAIT_2/CLOSING/TIME_WAIT/CLOSE_WAIT/LAST_ACK 3. TIME_WAIT 是主动关闭端出现的状态,等待2MSL的作用是: a 确保对端最后的ack能收到 b 让关闭的连接上的包消逝
【Answer】
- 描述过程即可,几个主要过程 1. DNS解析,涉及内容:DNS解析;涉及传输层协议:UDP;(可进一步考察对DNS缓存、UDP传输过程的了解和知识深度) 2. HTTP传输,涉及传输层协议:TCP;能分析出TCP建连和传输过程更佳;能进一步说出keepalive更好 - 从网络连接、DNS解析等阶段分析 1. 检查网络连接:涉及网络层IP协议、链路层ARP等;可使用命令`ping`测试连接是否通畅,面试者需要理解网络分层,网络层等是上层的TCP、UDP等传输的基础 2. 检查DNS解析:分析DNS本地缓存、路由器缓存和服务器缓存是否有异常;DNS服务器异常;DNS解析哪些环节可能出问题;可使用`nslookup` `dig`等工具帮助排查 3. 防火墙:可发散考察面试者的知识广度,如需要实现防火墙可以有哪些做法(e.g. iptables) 4. 检查网站的服务状况:尝试更换其它网址尝试是否有异常,如果只是单个站点不可用,可能是服务端挂了;如果是间歇性不可用(需要考虑到负载均衡),可能是部分后端挂了
【Answer】
1、非空区别。不存存指向空值的引用,指针可以指向NULL。 2、合法性区别。使用引用之前不需要测试它的全法性,使用指针前需要判断是否为NULL。 3、可修改区别。指针可以被重新赋值以指向另一个不同的对象,引用必须初始化时指定,并且不能再改变。
【Answer】
* 两次握手:客户端发送的连接请求可能在网络中滞留了,如果没有三次握手,可能会再次创建一个连接。 * 三次握手:引起SYN flood(一种DoS(拒绝服务攻击)是DDoS(分布式拒绝服务攻击)的方式之一,这是一种利用TCP协议缺陷,发送大量伪造的TCP连接请求,从而使得被攻击方资源耗尽(CPU满负荷或内存不足)的攻击方式。) * 不断发送同步报文段会因为传输控制模块TCB【处于半连接状态】从而消耗服务器资源 1. 【处理连接和半连接】定时释放监控系中无效的连接 2. Syn cache技术【处理半连接状态】,接受到的SYN先不创建TCB,而是用一个hash表来表示,当前连接,如果接收到ACK然后再创建TCB 3. Syn cookie技术【处理连接】通过一个cookie值来确定当前连接是否合法,合法就连接,一般的验证方法是,服务器接受到一个syn包,服务器通过syn产生一个cookie数据作为初始化序列,接收到ACK包时,序列-1就是得到的cookie,然后进行相应的验证。
简述DNS解析的过程,递归查询和迭代查询是什么?
如果某天你打开浏览器尝试打开douyin.com,浏览器提示你:找不到服务器 IP 地址,有可能是哪里出现了问题?为什么?可以用哪些命令和工具排查?
【Answer】
1. 客户端向本地DNS服务器发出递归查询请求
2. 本地DNS服务器向根域名服务器发出迭代查询请求,得到顶级域名服务器的地址
3. 本地DNS服务器向顶级域服务器发出请求,得到权威域名服务器的地址
4. 本地DNS服务器向顶级域服务器发出请求,得到域名对应ip地址
5. 本地DNS服务器返回域名的ip地址给客户端 可能出现问题的点:
1. 网络连接
2. 浏览器DNS缓存
3. 系统DNS缓存
4. 路由器DNS缓存
5. ISP DNS缓存
排查方法: 1. 检查网络连接:ping等 2. 检查DNS解析:dig
【Answer】
内核态与用户态是操作系统的两种运行级别,intel cpu提供Ring0-Ring3三种级别的运行模式。Ring0级别最高,Ring3最低。其中特权级0(Ring0)是留给操作系统代码,设备驱动程序代码使用的,它们工作于系统核心态;而特权极3(Ring3)则给普通的用户程序使用,它们工作在用户态。运行于处理器核心态的代码不受任何的限制,可以自由地访问任何有效地址,进行直接端口访问。而运行于用户态的代码则要受到处理器的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TSS)中I/O许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问(此时处理器状态和控制标志寄存器EFLAGS中的IOPL通常为0,指明当前可以进行直接I/O的最低特权级别是Ring0)。以上的讨论只限于保护模式操作系统,象DOS这种模式操作系统则没有这些概念,其中的所有代码都可被看作运行在核心态。 * 当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级) 内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。 * 在内核态下CPU可执行任何指令,在用户态下CPU只能执行非特权指令。当CPU处于内核态,可以随意进入用户态;而当CPU处于用户态时,用户从用户态切换到内核态只有在系统调用和中断两种情况下发生,一般程序一开始都是运行于用户态,当程序需要使用系统资源时,就必须通过调用软中断进入内核态。 * Linux使用了Ring3级别运行用户态,Ring0作为内核态,没有使用Ring1和Ring2。Ring3状态不能访问Ring0的地址空间,包括代码和数据。Linux进程的4GB地址空间,3G-4G部分大家是共享的,是内核态的地址空间,这里存放在整个内核的代码和所有的内核模块,以及内核所维护的数据。用户运行一个程序,该程序所创建的进程开始是运行在用户态的,如果要执行文件操作,网络数据发送等操作,必须通过 write,send等系统调用,这些系统调用会调用内核中的代码来完成操作,这时,必须切换到Ring0,然后进入3GB-4GB中的内核地址空间去执行这些代码完成操作,完成后,切换回Ring3,回到用户态。这样,用户态的程序就不能随意操作内核地址空间,具有一定的安全保护作用。 处理器模式从Ring3向Ring0的切换发生在控制权转移时,有以下两种情况:访问调用门的长转移指令CALL,访问中断门或陷阱门的INT指令。具体的转移细节由于涉及复杂的保护检查和堆栈切换,不再赘述,请参阅相关资料。现代的操作系统通常使用中断门来提供系统服务,通过执行一条陷入指令来完成模式切换,在INTEL X86上这条指令是INT,如在WIN9X下是INT30(保护模式回调),在LINUX下是INT80,在WINNT/2000下是INT2E。用户模式的服务程序(如系统DLL)通过执行一个INTXX来请求系统服务,然后处理器模式将切换到核心态,工作于核心态的相应的系统代码将服务于此次请求并将结果传给用户程序。
【Answer】
1. - synchronized、wait、notify - Lock、ReentrantLock、ReentrantReadWriteLock - Condition - Semaphore - CyclicBarrier - CountDownLatch - Phaser - Exchanger 2. CAS、CLH队列、AQS、LockSupport 3. https://github.com/chaodewen/java-test/tree/master/src/main/java/algorithm/pc
【Answer】
* HashMap :先说HashMap,HashMap是线程不安全的,在并发环境下,可能会形成环状链表(扩容时可能造成),导致get操作时,cpu空转,所以,在并发环境中使用HashMap是非常危险的。 * HashTable : HashTable和HashMap的实现原理几乎一样,差别是: 1. HashTable不允许key和value为null; 2. HashTable是线程安全的。但是HashTable线程安全的策略实现代价却太大了,简单粗暴,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。 * HashTable性能差主要是由于所有操作需要竞争同一把锁,而如果容器中有多把锁,每一把锁锁一段数据,这样在多线程访问时不同段的数据时,就不会存在锁竞争了,这样便可以有效地提高并发效率。这就是ConcurrentHashMap所采用的"分段锁"思想。ConcurrentHashMap采用了非常精妙的"分段锁"策略,ConcurrentHashMap的主干是个Segment数组。 Segment继承了ReentrantLock,所以它就是一种可重入锁(ReentrantLock)。在ConcurrentHashMap,一个Segment就是一个子哈希表,Segment里维护了一个HashEntry数组,并发环境下,对于不同Segment的数据进行操作是不用考虑锁竞争的。
【Answer】
1. 共享内存 2. 无关;内核态 3. 设置Key 4. 需要同步;例如使用信号量操作
【Answer】
1. 栈: 由编译器自动分配释放,存放函数的参数值,局部变量等值。 如 函数的参数,在函数内定义的变量等。 存放方式类似于数据结构中的栈。 栈的大小是固定的一块连续的内存区域,若申请空间超过总空间会出现overflow异常。 2. 堆: 一般由程序员分配释放,若程序员不释放,则可能会引起内存泄漏。 如 调用malloc,realloc,calloc等。 存放方式类似于数据结构中的链表。(操作系统的空闲内存的存储方式) 堆的大小受限于计算机系统中有效的虚拟内存。 3. 注意全局变量存放在静态区中,不属于堆或栈
【Answer】
一些调度算法: 1. FCFS:先来先服务 2. 最短作业优先:每次选择所需处理时间最短的进程运行 3. 最短剩余时间优先:总是选择预期剩余时间最短的进程 4. 高回复率优先/最高响应比优先:根据等待时间W和需要服务的时间S计算响应比R=(w+s)/s,优先服务响应比高的 5. 轮转调度(round robin):每个进程分配一个时间片,时间片用完还未结束则挂起 6. 优先级调度:每个进程分配一个优先级,优先服务高优先级的进程 实现一个O(1)的调度器,清晰合理即可,参考:基于10个等级,使用两组各10个链表维护,每次从一组选优先级大的轮转执行,并放入另一组,一轮结束后交换两组链表 CFS调度器:内部使用红黑树维护进程,优点:自平衡、高效、公平性好
【Answer】
解析过程: 1. 客户端向本地DNS服务器发出递归查询请求 2. 本地DNS服务器向根域名服务器发出迭代查询请求,得到顶级域名服务器的地址 3. 本地DNS服务器向顶级域服务器发出请求,得到权威域名服务器的地址 4. 本地DNS服务器向顶级域服务器发出请求,得到域名对应ip地址 5. 本地DNS服务器返回域名的ip地址给客户端 可能出现问题的点: 1. 网络连接 2. 浏览器DNS缓存 3. 系统DNS缓存 4. 路由器DNS缓存 5. ISP DNS缓存 排查方法: 1. 检查网络连接:ping等 2. 检查DNS解析:dig
【Answer】
http请求方法 **GET、HEAD、POST、PUT、DELETE、CONNECT、OPTIONS、TRACE 1 GET 请求指定的页面信息,并返回实体主体。 2 HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头 3 POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 4 PUT 从客户端向服务器传送的数据取代指定的文档的内容。 5 DELETE 请求服务器删除指定的页面。 6 CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 7 OPTIONS 允许客户端查看服务器的性能。 8 TRACE 回显服务器收到的请求,主要用于测试或诊断。
【Answer】
考察点: 1. 能否使用模板实现泛型 2. 是否知道使用引用计数来维护指针 3. 内存的分配和释放 参考: ```c++ template class SmartPtr { public: SmartPtr(T *ptr) { p = ptr; count = 0; if (ptr != NULL) { count++; } } SmartPtr(const SmartPtr &sp) : p(sp.p) { count = sp.count + 1; } ~SmartPtr() { if (--count == 0) delete p; } SmartPtr &operator=(const SmartPtr &sp) { sp.count++; if ((--count == 0) && p != NULL) { delete p; } p = sp.p; return *this; } T &operator*() { return *p; } T *operator->() { return p; } private: T *p; size_t count; }; ```
【Answer】
* 段页式存储管理:在段页式存储中,每个分段又被分成若干个固定大小的页。段页式存储组织是分段式和分页式结合的存储组织方法,这样可充分利用分段管理和分页管理的优点。 1. 用分段方法来分配和管理虚拟存储器。程序的地址空间按逻辑单位分成基本独立的段,而每一段有自己的段名,再把每段分成固定大小的若干页。 2. 用分页方法来分配和管理实存。即把整个主存分成与上述页大小相等的存储块,可装入作业的任何一页。程序对内存的调入或调出是按页进行的。但它又可按段实现共享和保护。 3. 逻辑地址结构。一个逻辑地址用三个参数表示:段号S;页号P;页内地址d。 4. 段表、页表、段表地址寄存器。为了进行地址转换,系统为每个作业建立一个段表,并且要为该作业段表中的每一个段建立一个页表。系统中有一个段表地址寄存器来指出作业的段表起始地址和段表长度。 - 取一次数据需要访问3次内存 - 在段页式存储管理方式中,取一次数据:首先要从内存中查找段表;再查找该段对应的页表;最后通过得到的物理地址访问内存获得数据。
【Answer】
1. const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符去你的中可能会产生意料不到的错误(边际效应) 2. 有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行高度。在C++ 程序 中只使用const常量而不使用宏常量,即const常量完全取代宏常量
【Answer】
static全局变量只能在声明的文件中被使用,不能在其他文件中被使用 static局部变量只被初始化一次(在全局变量区),下一次使用是上一次结果值 static函数与static全局变量类似,只能在声明的文件中被使用
【Answer】
1. Global Interpreter Lock, CPython版本解释器上特有的全局锁,保证了同一进程中不同线程的内存访问互斥。虽然 降低了并行效率,但是对于开发者而言不需要额外的代码来保证自己代码线程安全。 2. CPython解析器的实现的内存管理不是线程安全的。 3. 大量的开源库使用并且强依赖了这一特性,改动之后大量源码将无法兼容新版本解释器。 4. 在跑空循环或者说CPU密集的情况下,优于线程调度的额外开销,加上GIL导致两个线程并行速度会慢于单线程串行;在IO密集场景下, 由于CPU在IO等待时间可以调度到处理其他线程的计算,因此性能好好于单线程。
分别介绍下HTTP keep-alive 和 TCP keep-alive 机制
【Answer】
HTTP Keep-Alive
Connection: Keep-Alive 使得对同一个服务器的多个请求可以在同一连接上完成。
Keep-Alive 是一个通用消息头,允许消息发送者暗示连接的状态,还可以用来设置超时时长和最大请求数。
含有 Keep-Alive 首部的响应示例:
HTTP/1.1 200 OK Connection: Keep-Alive Content-Encoding: gzip Content-Type: text/html; charset=utf-8 Date: Thu, 11 Aug 2016 15:23:13 GMT Keep-Alive: timeout=5, max=1000 Last-Modified: Mon, 25 Jul 2016 04:32:39 GMT Server: Apache (body)
TCP Keep-Alive
作用 https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html
- 防止对端节点异常挂掉(没来得及正常关闭TCP连接);
- 防止网络异常断开,通信双方都没有感知,比如中间代理;
只有在一定时间内没有数据包传输时,才会发送keep-alive包
net.ipv4.tcp_keepalive_time = 7200 // 单位秒,开始探活前的空闲idle时间
net.ipv4.tcp_keepalive_intvl = 75 // 单位秒,探测间隔
net.ipv4.tcp_keepalive_probes = 9 // 探测次数
【Answer】
几种可能的思路。 1. 操作系统启动的时候有一个进程维护剪贴板相关内容,然后操作系统暴露两个API去Set和Get剪切板, 对这两个API的响应最终是由这个进程完成的。进程维护了一块自己的内存,每次剪切板都将这块内存 内容读取出来并返回给调用方。 2. 没有这样一个进程,操作系统暴露接口实际上是对一块剪切板内存的维护,调用方先申请一块全局内存 将剪切板内存加锁,然后将内容复制到全局内存,再拷贝到剪切板内存,释放全局锁。 总体,有一种合理的思路,且细节合理即可。
【Answer】
1.阻塞 2. - 短作业优先(SJF)- 必须知道作业时间、长作业饥饿、重要作业无法优先处理 - 轮转时间片(RR)- 时间片长度选择、平均等待时间长、上下文切换资源消耗多 - 多级反馈队列 (MLFQ)- 关于每级队列的实现有多种说法(缺陷未知) 3. - 按照代码量预测 - 根据代码中的操作类型预测(系统进程VS用户进程、根据旧操作预测) - 动态预测:实时更新作业时间的均值作为预测值 - 指数平均、平滑因子等相比均值更加高级的数学方法
【Answer】
同步异步是针对应用程序和内核的交互而言的。 同步是指用户进程触发IO操作并等待或轮询检查IO操作是否就绪,接着阻塞的完成IO操作 异步是指用户进程触发IO操作之后就做其它的事情,当IO操作完成之后会得到通知(如读取网络数据,此时数据已经由内核拷贝到用户缓冲区,用户进程可以直接使用)。 Reactor模式用于同步IO,Proactor模式用于异步IO
【Answer】
1. 答案为2,可以采用书中常规的解法思路,也可以采用便捷法。对页号序列从后往前计数,直到数到4(页框数)个不同的数字为止,这个停止的数字就是要淘汰的页号(最近最久未使用的页),题中为页号2。 2. 答案为6次 **访问1,缺页,调入1,内存中1** **访问2,缺页,调入2,内存中1,2** **访问3,缺页,调入3,内存中1,2,3** 访问1,不缺页,内存中1,2,3 访问2,不缺页,内存中1,2,3 **访问4,缺页,调入4,淘汰1,内存中4,2,3** 访问2,不缺页,内存中4,2,3 访问3,不缺页,内存中4,2,3 **访问5,缺页,调入5,淘汰2,内存中4,5,3** 访问3,不缺页,内存中4,5,3 访问4,不缺页,内存中4,5,3 访问5,不缺页,内存中4,5,3 **访问6,缺页,调入6,淘汰3,内存中4,5,6**
【Answer】
1. Python属于强类型,动态类型检查的语言。 2. 感性认知上,动态类型检查还是静态类型检查区别于是在编译时还是运行时报出会导致程序终止运行的一些错误。 强类型和弱类型:程序会不会在运行时出现无法控制的行为(类似于数组越界,缓冲区溢出)之后仍然继续运行。(不能回答 是否允许隐式类型转换) 理性认识上,能用严格定义说明上述两个区别最好。 3. 如果用Python写过工程代码并且单元测试良好,一般能理解对于动态类型语言(或者说脚本语言)语言的灵活性保证了开发效率, 但是为了保证可维护性和可读性,测试样例以及足够的文档说明都非常必要。测试用力可以很好的充当类型检测工具。 静态语言测试用例构造更多的是去验证代码功能的完备性。
【Answer】
1. 新进程创建时会有标准输入(0)、标准输出(1)和标准错误(2)三个描述符,打开文件时会使用尽可能小的fd,所以答案是3 2. 0:STDIN、1:STDOUT、2:STDERR 3. 每个进程维护自己的文件描述符表,其中fd值在进程中不重复即可,所以不同表之间fd可能相同 4. 在fd表中每个fd对应一个系统文件表中的文件偏移量,根据偏移量可以读取;系统文件表中每个文件存储一个inode指针,对应inode表中存储的文件属性 
【Answer】
- 用途:可用于变量类型推导,简化代码 - 必须初始化,否则会报编译错误,因为C++需要在编译时确定/推导出变量类型,进而确定内存分配
【Answer】
懒汉(加大锁、双重检验锁+volatile),饿汉, 静态内部类、枚举等写法等 具体写法参考(http://cantellow.iteye.com/blog/838473)
【Answer】
1. ctrl-c给前台运行的进程发送了SIGINT信号, 默认情况下,进程退出。 2. 信号是一种用来通知进程发生了某些异步事件的机制。 可以用于进程间的通信,也可以用于内核通知进程发生了某些内部事件。 3. 内核在进程即将从内核态返回时处理信号,意味着发送给进程的信号并不会立即得到处理。 4. 可以通过signal系统调用来指定信号的处理函数。 更多细节见:http://www.cnblogs.com/taobataoma/archive/2007/08/30/875743.html
【Answer】
C++的模板是编译时泛型,编译时,编译器根据模板使用推导出类型,并生成具体代码(实例化模板) 实现动态数组:使用模板支持泛型;需要把握到内存的申请和回收,包括扩容时的具体细节、缩容后是否释放,并给出对应做法的理由。
【Answer】
主要有两个作用 (1)抑制重排序,包括编辑器级别的和CPU指令级别的。 (2)保证内存可见性。
【Answer】
可以使用内存块+链表的方式,首先将一整块的内存分割成不同大小的内存块单元,例如8K、16K、32K、64K、128K。用hash链表将内存块串联起来。要点如下: 1. 申请内存:从满足条件的最小内存块链上摘下一块内存来分配; 2. 回收内存:内存释放时,将内存块清空并挂在对应大小的内存链上; 3. 大块内存申请:当申请大块内存无法满足时,需要考虑从小块内存链上合并内存碎片; 4. 小块内存申请:如果可选内存块远大于申请内存量,需要考虑将大块内存拆成小块内存之后再提供; 内存链表有很多方式,上述hash链表是其中一种,也可以使用堆来实现。另外对性能、安全等方面也可以考察。
1、 HTTP状态码出现499, 是什么含义?
2、 哪几种情况会导致499? 分别如何处理?
【Answer】
1、 499一般是nginx 返回的,表示“客户端提前关闭了连接”。 也就是说一次请求还没有处理完,nginx 就发现客户端关闭连接了。
2、 有如下几种原因:
1)、 客户端关闭过快。 例如客户端在几毫秒就关闭了连接。 这种一般是用户在刷新页面或者刷新view导致的。 这种情况下,应该与客户端同学一起来分析,看是否能从客户端进行优化,减少这种情况的发生。
2)、 服务端处理时间过长。 例如服务端处理了5秒,客户端只等了2秒,就会导致499。 这种情况下,服务端应进行优化。 如果无法优化,则应该跟客户端协商,调大超时时间。
【Answer】
* 页面置换策略:全局置换,局部置换; * 全局:在整个内存空间置换 * 局部:在本进程中进行置换 1. 全局置换指的是进程缺页时,可能置换的是内存中所有可换出的物理页面。即要换进的是A进程的页面,出去的可以是B进程的页面,因此分配给进程的页面总数是动态变化的。 2. 局部置换只置换本进程内的物理页面。一个进程占用的物理页面总数是限定的,当需要置换时,即总数已经用完,新进来一个页面,本进程就需要出去一个老的页面。所谓,朋友圈就那么大,有人进来自然需要有人出去。但是需要注意的是,如果分配给你的总数还没用完,自然是不用置换的,那是最初的红利时期,竞争还不激烈,先到先得。 * 全局:(1)工作集算法(2)缺页率置换算法 * 局部:(1)最优算法(OPT)(2)FIFO先进先出算法(3)LRU最近最久未使用(4)时钟算法 * 最优置换算法(OPT)是指,其所选择的被淘汰页面,将是以后永不使用的,或许是在最长(未来)时间内不再被访问的页面。 * LRU是最近最少使用页面置换算法(Least Recently Used),也就是首先淘汰最长时间未被使用的页面! * LFU是最近最不常用页面置换算法(Least Frequently Used),也就是淘汰一定时期内被访问次数最少的页! * 缺页率置换算法 cpu需要访问的页不在内存中. 因为有缺页,所以就要调入页面,如果内存已慢,就要运用置换算法. 所以所谓的缺页率是(置换的次数+内存的物理块数)/页数.
判断以下代码返回值
public class Example {
public static void main(String[] args) {
List<Long> longArrayList = new ArrayList<>();
List<Integer> integerArrayList = new ArrayList<>();
List<Boolean> booleanLinkedList = new LinkedList<>();
longArrayList.add((long)10);
longArrayList.add((long)20);
integerArrayList.add(10);
integerArrayList.add(20);
booleanLinkedList.add(true);
booleanLinkedList.add(false);
System.out.println(longArrayList.getClass() == integerArrayList.getClass());
System.out.println(longArrayList.getClass() == booleanLinkedList.getClass());
}
}
【Answer】
返回结果
true false
【Answer】
1. 用户态和内核态的主要区别是权限不同,原因是处于性能和安全的考虑,用户程序不应该拥有执行一些关键操作,或者访问关键资源的权限。 2. 用户态到内核态转换的方法分为:系统调用(实际上也是异常),异常,中断三种。
讲讲什么是装饰器
【Answer】
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
装饰器的简单写法参考 https://www.cnblogs.com/cicaday/p/python-decorator.html
【Answer】
* 没有判断文件不存在的情况 * readlines是一次性读入全部文件,如果文件过大会导致内存爆掉,用readline或read(size) * 没考虑到IO异常时有可能不能关闭文件 * 没有考虑编码问题,文件写入可能是乱码(decode,encode和import codecs模块来解决)
【Answer】
1. 首先检车客户端请求资源类型。如果是静态资源检查cdn状态。 2. 如果是动态资源先检查服务端运行状态。 3. 服务端运行状态ok,排查上层代理。先是本机的请求代理,本机的网络状态,再是外层nginx状态 4. 排查客户端状态,是不是正确接受响应。 5. 如果都ok,考虑动态加速服务商的状态。
【Answer】
go func calc(base int) (func(int) int, func(int) int)
{
add := func(i int) int { base += i return base }
sub := func(i int) int { base -= i return base }
return add, sub
}
func main() {
f1, f2 := calc(10)
fmt.Println(f1(1), f2(2))
fmt.Println(f1(3), f2(4))
fmt.Println(f1(5), f2(6))
fmt.Println(f1(7), f2(8))
}
【Answer】
11 9 12 8 13 7 14 6
【Answer】
#define MAX(x,y) ((x)>(y)?(x):(y)) 避免传入自操作的参数,如 x++ ,会导致可能被自操作两次。传入的若是函数也都可能会被执行两次。 注意 (x>y?x:y) 或 (x)>(y)?(x):(y) 等 都是不对的
读程序,写出运行后的输出
#include <iostream>
using namespace std;
class A{
public:
virtual void F(){cout << 1 << endl;}
void CallF(){F();}
virtual ~A(){CallF(); F();}
};
class B : public A{
public:
void F(){cout << 2 << endl;}
~B(){}
};
class C : public B{
public:
void F(){cout << 3 << endl;}
void CallF(){F(); A::CallF();}
~C(){CallF();}
};
int main(){
A * p = new C();
p->CallF();
delete p;
return 0;
}
【Answer】
考点1: CallF()不是虚函数,用A的指针类型调用,执行A::CallF()
考点2: 父类的virtual关键字,子类默认继承
考点3: F()是虚函数,尽管在类内函数中被调用,仍然会通过虚函数表找到多态入口
考点4: delete p时会先执行 C::~C()的原因是A的析构函数为虚函数,析构函数的执行顺序,先执行子类,逐层向上执行父类
考点5: 可以追问析构函数不是虚函数的坏处——多态使用时,释放对象时往往只能delete公共父类的指针,此时执行不了子类的析构函数,导致内存泄漏
考点6: 在A::CallF()这里,还是在this下执行,所以虚表仍然有效
考点7: 在析构函数中调用虚函数,由于其子类已经被释放,虚函数表里面不再挂了子类的函数入口,所以此时会执行本类的函数
本题答案:
3
3
3
1
1
为什么?以及如何解决?
a = {"key": 1}
b = a
b["key"] = 2
print b
print a
【Answer】
使用copy.deepcopy达到深拷贝的目的
import copy a = {"key": 1} b = copy.deepcopy(a) b["key"] = 2 print b print a
【Answer】
1、主进程main主体代码fork(),write(),fork(),write,所以主进程会输出两次,然后会生成子进程p1和p2 * p1执行write(),fork(),write(),所以会输出两次,然后会生成子进程p11 * p2执行write(),所以会输出一次 * p11执行write(),所以会输出一次 所以总共输出六次,总共有四个进程,main, p1, p2, p11 2、改成printf("-")后,由于printf是带缓冲的(这里是行缓冲),然后并没有输出\n,数据量也小(没有到缓冲区满的程度) * 执行流跟上面一样,p1生成前,main并没有调用printf,所以main还是打印两次,p1也打印两次。 * p2生成时,main调用过一次printf,所以缓冲区会被复制到p2中,p2还会再调一次write,所以会打印两次。 * p11生成时,p1调用过一次printf,所以缓冲区会被复制到p11中,p11还会再调一次write,所以会打印两次。 所以共输出8个-
【Answer】
1. 打印出来的值是不确定的。依赖于打印的时候子线程里i=1是否执行了,并且值在主线程是否可以立刻读到。 2. 可以使用Object.wait/notify来确保主线程等待子线程i=1结束以后再执行。或者Semaphore也可以。另外最好能知道使用volatile。
【Answer】
考察对内存寻址的理解, 原因是32位机最大的寻址范围是 2^32 = 4G
【Answer】
进程间通信被称为IPC,请问:
问题一:IPC方式有哪些?
问题二:最快IPC的方式是什么?
问题三:semophore方式,两个进程如何能同时访问同一个semophore?如何避免与其他进程冲突?
【Answer】
答案一:pipe popen named_pipe message_queue semophore signal shared_memory socket
答案二:shared_memory
答案三:ftok(filename, proj_id)函数,只要保证filename指向的inode节点是一个,proj_id是一个,则返回的sem_id就是同一个。如果inode变了,则会有问题。
Java如何创建不可变类
【Answer】
- 将类声明为final,所以它不能被继承
- 将所有的成员声明为私有的,这样就不允许直接访问这些成员
- 对变量不要提供setter方法
- 将所有可变的成员声明为final,这样只能对它们赋值一次
- 通过构造器初始化所有成员,进行深拷贝(deep copy)
- 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝
【Answer】
1. 原码 * 原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制: * [+1]原 = 0000 0001 * [-1]原 = 1000 0001 * 第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是: * [1111 1111 , 0111 1111] * [-127 , 127] * 原码是人脑最容易理解和计算的表示方式 2. 反码 * 反码的表示方法是: * 正数的反码是其本身 * 负数的反码是在其原码的基础上, 符号位不变,其余各个位取反. * [+1] = [00000001]原 = [00000001]反 * [-1] = [10000001]原 = [11111110]反 * 可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算 3. 补码 * 补码的表示方法是: * 正数的补码就是其本身 * 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1。 (即在反码的基础上+1) * [+1] = [00000001]原 = [00000001]反 = [00000001]补 * [-1] = [10000001]原 = [11111110]反 = [11111111]补 * 对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值.
考察候选人对python里面常见系统调用的区别的了解,
os.system()
os.popen()
以及subprocess.checkout(),
subprocess.Popen()的异同
【Answer】
面试官参考:https://docs.python.org/2/library/subprocess.html
简要信息:
os.system
该函数返回命令执行结果的返回值,system()函数在执行过程中进行了以下三步操作:
- 1.fork一个子进程;
- 2.在子进程中调用exec函数去执行命令;
- 3.在父进程中调用wait(阻塞)去等待子进程结束。
- 对于fork失败,system()函数返回-1。
- 由于使用该函数经常会莫名其妙地出现错误,但是直接执行命令并没有问题,所以一般建议不要使用。
popen()
- 创建一个管道,通过fork一个子进程,然后该子进程执行命令(非阻塞)。
- 返回值在标准IO流中,该管道用于父子进程间通信。父进程要么从管道读信息,要么向管道写信息,
- 至于是读还是写取决于父进程调用popen时传递的参数(w或r)
subprocess
- subprocess模块是在2.4版本中新增的,官方文档中描述为可以用来替换以下函数:
- os.system、os.spawn、os.popen、popen2,
- class subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None,
- preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None,
- universal_newlines=False, startupinfo=None, creationflags=0)
1. __new__和__init__有啥区别?
2. 利用__new__实现一个单例模式的类:
class Singleton(object):
def __new__(cls, *args, **kwargs):
pass
【Answer】
1. __new__创建对象实例,__init__初始化对象实例。
2. 利用__new__实现一个单例模式的类:
class Singleton(object):
def __new__(cls, *args, **kwargs):
obj = getattr(cls, "__obj", None)
if obj is None:
obj = object.__new__(cls, *args, **kwargs)
setattr(cls, "__obj", obj)
return obj
def main():
o1 = Singleton()
o2 = Singleton()
print(o1 == o2)
if __name__ == "__main__":
main()
【Answer】
1. 先考虑协议,如果要现应用层做,应用层不能使用HTTP协议,如果直接依赖传输层协议。将图片序列化,计算需要的TCP包数量,拿到每个包传递成功的ack就可以回调更新进度。 2. 曲线的特点:建立链接的时候进度一直为0。所以曲线开始一段是一直在横坐标上的。 因为发送是段落式的,所以进度应该是段落式而不是平滑的,微信可能为了现实进度条平滑做了处理。 TCP链接刚建立的时候,可能因为网络不稳定,导致发送开始时速度偏慢。后来速度慢慢稳定变快。
【Answer】
1. 时间局部性:如果程序中的某条指令一旦执行,则短时间内该指令可能再次被执行;如果某数据被访问,则短时间内该数据可能再次被访问。 2. 空间局部性:一旦程序访问了某个存储单元,则不久之后。其附近的存储单元也很可能被访问。 从计算机体系的角度,cache-memory-disk这样的层次结构,本身就运用了局部性原理,将数据分为block,最近使用的数据以及其相邻的数据被存放在更快的存储中。 从工程的角度看,我们常常用用到的db cache、LRU等,本身也利用了时间局部性的原理,将最近访问到的数据缓存起来,大概率短时间内还可以再用到,减少对其他存储的压力。
【Answer】
颠簸本质上是指频繁的页调度行为。页面在内存和辅存间频繁交换的现象。会导致整个系统的效率急剧下降,这种现象称为颠簸(抖动)。 - 内存颠簸的解决策略包括: - 如果是因为页面替换策略失误,可以修改替换算法来解决这个问题; - 如果是因为进程太多,无法同时将所有频繁访问的页面调入内存,则要降低进程的数量; - 否则,增加物理内存容量。
什么是死锁?其条件是什么?怎样避免死锁?
【Answer】
什么是死锁?其条件是什么?怎样避免死锁?
判断以下代码返回值,并解释原因
package main
import (
"fmt"
)
type Detail struct {
x int32
}
type Item struct {
v int32
pv *int32
d Detail
pd *Detail
}
func (i Item)Do() {
i.v = 11
*(i.pv) = 21
i.d.x = 31
i.pd.x = 41
}
func (i *Item)PtrDo() {
i.v = 9
*(i.pv) = 19
i.d.x = 29
i.pd.x = 39
}
func main() {
a := int32(10)
b := int32(20)
c := int32(30)
d := int32(40)
i := Item {a,&b,Detail{c},&Detail{d}}
fmt.Println("orig",i.v,*(i.pv),i.d.x,i.pd.x)
i.Do()
fmt.Println("do",i.v,*(i.pv),i.d.x,i.pd.x)
i.PtrDo()
fmt.Println("ptrdo",i.v,*(i.pv),i.d.x,i.pd.x)
}
【Answer】
返回结果
orig 10 20 30 40 do 10 21 30 41 ptrdo 9 19 29 39
HTTP的长连接和短连接
【Answer】
长连接:建立SOCKET连接后发送后接收完数据后,保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据。HTTP1.1规定了默认保持长连接(持久连接)
短连接:建立SOCKET连接后发送后接收完数据后马上断开连接,一般银行都使用短连接
【Answer】
【Answer】
```javascript var isEqual = function (x, y) { // === 的使用 // If both x and y are null or undefined and exactly the same if (x === y) { return true; } // 优化和鲁棒性考虑 // If they are not strictly equal, they both need to be Objects if (!(x instanceof Object) || !(y instanceof Object)) { return false; } // 优化考虑 // They must have the exact same prototype chain,the closest we can do is // test the constructor. if (x.constructor !== y.constructor) { return false; } // 对象属性的枚举 for (var p in x) { // Inherited properties were tested using x.constructor === y.constructor if (x.hasOwnProperty(p)) { // Allows comparing x[ p ] and y[ p ] when set to undefined if (!y.hasOwnProperty(p)) { return false; } // === 的使用 // If they have the same strict value or identity then they are equal if (x[ p ] === y[ p ]) { continue; } // 数据类型的判断 // Numbers, Strings, Functions, Booleans must be strictly equal if (typeof (x[ p ]) !== 'object') { return false; } // 递归调用 // 属性为对象或数组时需要递归调用比较 // Objects and Arrays must be tested recursively if (!isEqual(x[ p ], y[ p ])) { return false; } } } // 全面性的考虑 for (p in y) { // allows x[ p ] to be set to undefined if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) { return false; } } return true; };
【Answer】
这道题考察了候选人对于 1. 指针 & 指针常量 & 常量指针的基本理解 1. 考察了类型转化的理解 1. 考察了const的使用习惯和规则
C++ STL里的四种智能指针
【Answer】
C++ 标准模板库 STL(Standard Template Library) 一共给我们提供了四种智能指针:auto_ptr、unique_ptr、shared_ptr 和 weak_ptr,其中 auto_ptr 是 C++98 提出的,C++11 已将其摒弃,并提出了 unique_ptr 替代 auto_ptr。虽然 auto_ptr 已被摒弃,但在实际项目中仍可使用,但建议使用更加安全的 unique_ptr。shared_ptr 和 weak_ptr 则是 C+11 从准标准库 Boost 中引入的两种智能指针。
unique_ptr
unique_ptr 由 C++11 引入,旨在替代不安全的 auto_ptr。unique_ptr 是一种定义在头文件
中的智能指针。它持有对对象的独有权——两个unique_ptr 不能指向一个对象,即 unique_ptr 不共享它所管理的对象。它无法复制到其他 unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL)算法。只能移动 unique_ptr,即对资源管理权限可以实现转移。这意味着,内存资源所有权可以转移到另一个 unique_ptr,并且原始 unique_ptr 不再拥有此资源。</p>
auto_ptr
auto_ptr 同样是 STL 智能指针家族的成员之一,由 C++98 引入,定义在头文件<memory>。其功能和用法类似于 unique_ptr,由 new expression 获得对象,在 auto_ptr 对象销毁时,他所管理的对象也会自动被 delete 掉。
shared_ptr
shared_ptr 是一个标准的共享所有权的智能指针,允许多个指针指向同一个对象,定义在 memory 文件中,命名空间为 std。shared_ptr最初实现于Boost库中,后由 C++11 引入到 C++ STL。shared_ptr 利用引用计数的方式实现了对所管理的对象的所有权的分享,即允许多个 shared_ptr 共同管理同一个对象。像 shared_ptr 这种智能指针,《Effective C++》称之为“引用计数型智能指针”(reference-counting smart pointer,RCSP)。
shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销:
(1)shared_ptr 对象除了包括一个所拥有对象的指针外,还必须包括一个引用计数代理对象的指针;
(2)时间上的开销主要在初始化和拷贝操作上, * 和 -> 操作符重载的开销跟 auto_ptr 是一样;
(3)开销并不是我们不使用 shared_ptr 的理由,,永远不要进行不成熟的优化,直到性能分析器告诉你这一点。
weak_ptr
weak_ptr 被设计为与 shared_ptr 共同工作,可以从一个 shared_ptr 或者另一个 weak_ptr 对象构造而来。weak_ptr 是为了配合 shared_ptr 而引入的一种智能指针,它更像是 shared_ptr 的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载 operator* 和 operator-> ,因此取名为 weak,表明其是功能较弱的智能指针。它的最大作用在于协助 shared_ptr 工作,可获得资源的观测权,像旁观者那样观测资源的使用情况。观察者意味着 weak_ptr 只对 shared_ptr 进行引用,而不改变其引用计数,当被观察的 shared_ptr 失效后,相应的 weak_ptr 也相应失效。
</pre> </details> --- ### 175. 请简单介绍下 Golang 中 make 和 new 的区别 【Question】
--- ### 176. java内存结构?string类型存储在哪个区?GC算法?哪些方法可以触发GC?讲讲ZGC? 【Question】【Answer】
new(T) 是为一个 T 类型的新值分配空间, 并将此空间初始化为 T 的零值, 并返回这块内存空间的地址, 也就是 T 类型的指针 *T, 该指针指向 T 类型值占用的那块内存. make(T) 返回的是初始化之后的 T, 且只能用于 slice, map, channel 三种类型. make(T, args) 返回初始化之后 T 类型的值, 且此新值并不是 T 类型的零值, 也不是 T 类型的指针 *T, 而是 T 类型值经过初始化之后的引用.
【Answer】
- 栈、存储局部变量表、操作栈、动态链接、方法出口等信息。局部变量表存放了编译期可知的各种基本数据类型和对象引用类型。
- 堆、对象实例
- 程序计数器,线程私有,可以看作是当前线程所执行的字节码的行号指示器。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
- 方法区,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 本地方法区,本地方法栈则是为虚拟机使用到的Native 方法服务
描述认证流程
【Answer】
参考答案:
SSL单向验证总共有四步
第一步,客户端向服务器端发起Client Hello,请求内容包括:
客户端支持的SSL/TLS协议版本列表;
客户端支持的对称加密算法列表;
客户端生成的随机数A;
第二步,服务器端回应客户端Server Hello,回应内容包括:
双方都支持的SSL/TLS协议版本;
双方都支持的对称加密算法;
服务器秘钥库中的证书;
服务器端生成的随机数B;
第三步,客户端收到服务器端回应,客户端检查服务器端证书是否合法,验证内容如下:
服务器端证书是否过期;
服务器端证书是否被吊销;
服务器端证书是否可信;
服务器端证书域名和客户端请求域名是否一致。
验证通过后,客户端回应服务器端,回应内容包括:
客户端生成一个“随机数C”,“随机数C”也被称为”pre-master-key”,然后使用服务器端证书中的公钥加密“随机数C”,将加密后的“随机数C”发送给服务器端;
第四步,服务器端使用秘钥库中的私钥解密加密后的“随机数C”得到“随机数C”,此时客户端和服务器端都拿到了随机数A、随机数B、随机数C,双发通过这3个随机数使用相同的秘钥交换算法计算得到相同的对称加密秘钥,这个对称加密秘钥就作为客户端和服务器端数据传输时对称加密使用的秘钥。
服务器端和客户端,握手结束,之后就可以用对称加密传输数据了。
SSL双向验证
SSL单向验证过程中,客户端会验证自己访问的服务器端,服务器端对客户端不做验证。如果服务器端验证客户端,则需要开启服务器端验证,这就是双向验证。
SSL双向验证和单向验证的不同之处在于:
第二步中服务器端第一次回应客户端的Server Hello消息中,会要求客户端提供客户端证书;
第三步中客户端验证完服务器端证书后,回应的内容中,会增加两个信息:
客户端证书;
客户端证书验证消息(CertificateVerify message):客户端将之前所有收到的和发送的消息组合起来,并用hash算法得到一个hash值,然后用客户端密钥库的私钥对这个hash进行签名,这个签名就是CertificateVerify message;
服务器端收到客户端证书后,会做如下处理:
确认客户端发送的证书是有效合法的;
用客户端证书中的公钥验证收到信息中的签名,以确定这个证书是客户端发出的;
服务器端和客户端,握手结束,之后就可以用对称加密传输数据了。
【Answer】
1)应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
2)TCP、UDP
3)是否有连接,是否可靠,报文/字节流、拥塞
4)请求响应应答
5)所以TIME_WAIT 是这么一种状态:TCP 四次握手结束后,连接双方都不再交换消息,但主动关闭的一方保持这个连接在一段时间内不可用。可靠关闭
写两个goroutine。交替打印1和2。要求:不能sleep。不能用有长度的chan,chan最多一个。
- 题目1 1和2顺序没有要求的情况
- 题目2 1和2顺序有要求,必须先打印1
// 修改下边的代码,实现12交替打印。各打印10次
func print12() {
go func() {
fmt.Println(1)
}()
go func() {
fmt.Println(2)
}()
}
【Answer】
思路:搞一个长度为0的chan,两个相互阻塞。一个打印后,进chan,当前只有另一个可以出chan。如此交替就可以,需要注意,在两个goroutine启动后需要初始化塞进去一个。
题目1答案
func print12() { ch := make(chan int) go func() { for i := 0; i < 10; i++ { <-ch fmt.Println(1) ch <- 0 } }() go func() { for i := 0; i < 10; i++ { <-ch fmt.Println(2) ch <- 0 } }() ch <- 0 }
题目2答案,以先打印1为例
func print12() { ch := make(chan int) go func() { for i := 0; i < 10; i++ { <-ch fmt.Println(1) ch <- 1 } }() go func() { for i := 0; i < 10; i++ { if i == 0 { // 判断第一次是否先打印了1 if <-ch == 0 { ch <- 1 } else { fmt.Println(2) ch <- 1 continue } } <-ch fmt.Println(2) // 2最后打印,所以到9后就可以跳出 if i == 9 { break } ch <- 1 } }() ch <- 0 }
或者(解法更简洁)
func print12() { ch := make(chan int) go func() { fmt.Print(1) ch <- 0 for i := 1; i < 10; i++ { <-ch fmt.Print(1) ch <- 0 } }() go func() { for i := 0; i < 9; i++ { <-ch fmt.Print(2) ch <- 0 } <-ch fmt.Print(2) }() }
【Answer】
### 首先需要明确这段代码运行在什么线程下? #### 1. 主线程下 > sleep 1, print 2, sleep 2, print 1, print 3 原因:主队列是穿行队列,block运行顺序是入队顺序 #### 2. 自线程下 > print 2, sleep 2, print 1, print 3 原因,子线程下外面的sleep 1对里面入队没影响,其他同上
【Answer】
1. app buffer->clib buffer -> page cache -> io queue -> driver -> disk cache -> disk 2. app buffer->clib buffer 用户态,后面是内核态 (3.0) 3. fwrite写到page cache层,如果fwrite之后进程挂掉数据会丢失, 如果想到直接写到磁盘,可以再打开文件的时候用o_direct参数 (3.5) 可以根据回答再发散问一下
【Answer】
int (*p)[10] 是指针,指向一个有10个int的数组 int *p[10] 是数组,每个元素是指向int的指针 int *f(int i) 是返回int指针的函数,是一个函数的声明 int (*f)(int i) 是指向函数的指针,定义了一个指针
代码-1
#include<stdio.h>
#include<math.h>
int main(int argc,char *argv[]){
printf("%lf",exp(2));
return 0;
}
代码-2
#include<stdio.h>
#include<math.h>
int main(int argc,char *argv[]){
int i = 2;
printf("%lf",exp(i));
return 0;
}
问题-1:
[R]➜ tmp gcc --static code-1.c // 编译通过
[R]➜ tmp gcc --static code-2.c // 编译出错
/tmp/ccBLcbCw.o: In function `main':
code-2.c:(.text+0x20): undefined reference to `exp'
原因是什么?如何解决?
问题-2:
[R]➜ tmp gcc --static -lm code-2.c
/tmp/cc3XLM0K.o: In function `main':
code-2.c:(.text+0x20): undefined reference to `exp'
collect2: error: ld returned 1 exit status
加上-lm为什么还会报错?如何解决?
【Answer】
问题-1:
[R]➜ tmp gcc --static code-1.c -S -o code-1.s [R]➜ tmp cat --static code-1.s .... 代码片段 ..... .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $32, %rsp movl %edi, -4(%rbp) movq %rsi, -16(%rbp) movq .LC0(%rip), %rax movq %rax, -24(%rbp) movsd -24(%rbp), %xmm0 leaq .LC1(%rip), %rdi movl $1, %eax call printf@PLT movl $0, %eax leave
根据汇编代码可以看出,当掺入exp函数时静态值时。没有call exp指令,而是在编译阶段直接计算好了。(xmm0-xmm7为浮点型计算寄存器:参考https://en.wikibooks.org/wiki/X86_Assembly/SSE )。所以不需要额外链接其他库。
而当传入的值是变量的时候汇编指令就调用了call exp
pxor %xmm0, %xmm0 cvtsi2sd -4(%rbp), %xmm0 call exp@PLT leaq .LC0(%rip), %rdi movl $1, %eax
这时候就需要需要exp的符号表了。需要链接动态库了。
因此需要增加 -lm 来解决。
问题二:
链接过程中,需要进行符号解析,并且是按照顺序解析;如果库链接在前,就可能出现库中的符号不会被需要,链接器不会把它加到未解析的符号集合中,那么后面引用这个符号的目标文件就不能解析该引用,导致最后链接失败。因此链接库的一般准则是将它们放在命令行的结尾。
问题一:golang/python怎么进行异常处理?
问题二:各自的优缺点?局限性?
问题三:发生异常后,如何进行善后,例如保证资源得到释放?(资源:例如从连接池获取到的一个数据库连接)
【Answer】
答案一:go使用error类型;python使用Exception类型
答案二:
go优点:完善。缺点:麻烦。
python优点:方便。缺点:发生异常时不知道函数的具体异常位置,异常处理时需要自行检查代码执行到哪儿了,释放资源时就需要额外一次判断;如果raise的姿势不对时,会导致调用信息丢失;假设输入无误,直接通过异常来处理,出错时调用方或者用户无法知道具体哪里出了问题。
答案三:go用defer,python用finally。
【Answer】
1. 进程的堆栈内核在创建进程的时候,在创建task_struct的同时,会为进程创建相应的堆栈。每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存在于内核空间。当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;当进程在内核空间时,cpu堆栈指针寄存器里面的内容是内核栈空间地址,使用内核栈。 2. 进程用户栈和内核栈的切换当进程因为中断或者系统调用而陷入内核态之行时,进程所使用的堆栈也要从用户栈转到内核栈。进程陷入内核态后,先把用户态堆栈的地址保存在内核栈之中,然后设置堆栈指针寄存器的内容为内核栈的地址,这样就完成了用户栈向内核栈的转换;当进程从内核态恢复到用户态之行时,在内核态之行的最后将保存在内核栈里面的用户栈的地址恢复到堆栈指针寄存器即可。这样就实现了内核栈和用户栈的互转。那么,我们知道从内核转到用户态时用户栈的地址是在陷入内核的时候保存在内核栈里面的,但是在陷入内核的时候,我们是如何知道内核栈的地址的呢?关键在进程从用户态转到内核态的时候,进程的内核栈总是空的。这是因为,当进程在用户态运行时,使用的是用户栈,当进程陷入到内核态时,内核栈保存进程在内核态运行的相关信心,但是一旦进程返回到用户态后,内核栈中保存的信息无效,会全部恢复,因此每次进程从用户态陷入内核的时候得到的内核栈都是空的。所以在进程陷入内核的时候,直接把内核栈的栈顶地址给堆栈指针寄存器就可以了。
【Answer】
范围查询、非叶子节点只存键不存值,因而一次IO可以捞取更多的索引
多路树可以减少树的层数
生产环境中B+树的高度总是3-4层
在MySQL中我们的InnoDB页的大小默认是16k,一个高度为3的B+树可以存放:1170*1170*16=21902400条这样的记录
多态的概念与实现原理
虚函数和纯虚函数的区别
【Answer】
什么是多态?
程序运行时,父类指针可以根据具体指向的子类对象,来执行不同的函数,表现为多态
C++ 多态的实现原理?
- 当类中存在虚函数时,编译器会在类中自动生成一个虚函数表
- 虚函数表是一个存储类成员函数指针的数据结构
- 虚函数表由编译器自动生成和维护
- virtual 修饰的成员函数会被编译器放入虚函数表中
- 存在虚函数时,编译器会为对象自动生成一个指向虚函数表的指针(通常称之为 vptr 指针)
虚函数和纯虚函数区别?
首先:强调一个概念
定义一个函数为虚函数,不代表函数没有被实现。
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。
定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
1、虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数;
2、友元函数是虚函数?友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题;
3、析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。
个人认为纯虚函数的引入,是出于两个目的:
1、为了安全,因为避免任何需要明确但是因为不小心而导致的未知的结果,提醒子类去做应做的实现。
2、为了效率,不是程序执行的效率,而是为了编码的效率。
【Answer】
```cpp template ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value) { ForwardIt it; typename std::iterator_traits::difference_type count, step; count = std::distance(first, last); while (count > 0) { it = first; step = count / 2; std::advance(it, step); if (*it < value) { first = ++it; count -= step + 1; } else count = step; } return first; } ```
【Answer】
>1. 观察者(observer) 模式广泛用于客户端Javascript编程中。所有的浏览器事件都是该模式的例子。它的另一个名字也称为自定义事件(custom events),与那些由浏览器触发的事件相比,自定义事件表示是由你编程实现的事件。此外,该模式的另一个别名也称为订阅/发布(subscriber/publisher)模式。 >1. subscribers 一个数组 >1. subscribe() 将订阅者添加到subscribers数组中 >1. unsubscribe() 从subscribers数组中删除订阅者 >1. publish() 循环遍历subscribers数组中的每一个元素,并且调用他们注册时所提供的方法 >1.所有这三种方法都需要一个topic参数,因为发布者可能触发多个事件(比如同时发布一本杂志和一份报纸)而用户可能仅选择订阅其中一种,而不是另外一种。 ```javascript var pubsub = {}; (function (q) { var topics = {}, // 回调函数存放的数组 subUid = -1; // 发布方法 q.publish = function (topic, args) { if (!topics[topic]) { return false; } setTimeout(function () { var subscribers = topics[topic], len = subscribers ? subscribers.length : 0; while (len--) { subscribers[len].func(topic, args); } }, 0); return true; }; //订阅方法 q.subscribe = function (topic, func) { if (!topics[topic]) { topics[topic] = []; } var token = (++subUid).toString(); topics[topic].push({ token: token, func: func }); return token; }; //退订方法 q.unsubscribe = function (token) { for (var m in topics) { if (topics[m]) { for (var i = 0, j = topics[m].length; i < j; i++) { if (topics[m][i].token === token) { topics[m].splice(i, 1); return token; } } } } return false; }; }(pubsub)); ```
【Answer】
"这道题考察了候选人对于 1. 指针 & 指针常量 & 常量指针的基本理解 2. 考察了类型转化的理解 3. 考察了const的使用习惯和规则"
【Answer】
1. Reactor模式适用于同步IO,读写操作由应用程序完成,读写过程中阻塞;Proactor模式适用于异步IO,读写操作由内核负责,应用程序不阻塞 2. Reactor实现相对简单,对于耗时短的处理场景处理高效,几乎所有的linux下IO模式都使用此种模式;Proactor实现逻辑复杂, 使用较少 3. Reactor处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理;Proactor性能更高,能够处理耗时长的并发场景;
TCP怎么保证错误重传
【Answer】
1. 当报文使用TCP传输时,重传计时器启动,收到ACK时计时器停止
2. 重传计时器有重传超时值(RTO)
3. 当报文发送之后,在RTO时间内发送方未收到ACK应答,将重发数据,并将RTO值扩大2倍。如此持续下去,每次重传RTO都翻倍,直到收到ACK报文或重传次数达到3次(不再重传)
python是否支持多线程?谈下对GIL的理解。
【Answer】
GIL 是python的全局解释器锁,同一进程中假如有多个线程运行,一个线程在运行python程序的时候会霸占python解释器(加了一把锁即GIL),使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。如果线程运行过程中遇到耗时操作,则解释器锁解开,使其他线程运行。所以在多线程中,线程的运行仍是有先后顺序的,并不是同时进行。
多进程中因为每个进程都能被系统分配资源,相当于每个进程有了一个python解释器,所以多进程可以实现多个进程的同时运行,缺点是进程系统资源开销大。
什么是进程(Process)和线程(Thread)
【Answer】
进程:已运行程序的实体
- 系统进行资源分配和调度的独立单位
- 进程需要系统分配资源:CPU使用时间、存储器、文件、I/O设备
线程:进程的一个实体
- CPU进行调度和分派的基本单元
- 线程只需分配较少资源(如程序计数器,寄存器和栈),可与其他的线程共享进程全部资源
对于一个给定的Java类名字符串进行长度压缩,返回一个长度不超过N的“最佳表示”,具体规则如下:
1. Java的类名使用英文标点“点号”分割的多个部分组成,每一部分满足正则^[a-zA-Z][a-zA-Z0-9]+。
2. 类名中的“点”不能省略,最靠右侧的一部分不能省略。
3. 其余部分均可使用每一部分的第一个字符省略表示。
由此我们可以定义类名的“最简表示”,如“org.elasticsearch.rest.action.document.RestMultiTermVectorsAction”的最简表示为“o.e.r.a.d.RestMultiTermVectorsAction”
4. 如果一个类名的“最简表示”,长度依然大于N,则最佳表示不存在,返回“”(空字符串)。
5. 如果一个类名的“最简表示”,长度小于N,那么为了展示更多信息,我们需要从右向左,将被省略的部分逐个展开,使其在长度不超过N的情况下,尽量展示更多信息。
举例:对于类名“org.elasticsearch.rest.action.document.RestMultiTermVectorsAction”,约定长度N=50。
最简表示“o.e.r.a.d.RestMultiTermVectorsAction”,长度为36,36<50
尝试展开右起第二段“document”,表示为“o.e.r.a.document.RestMultiTermVectorsAction”,长度为43,43<50
继续尝试展开右起第三段“action”,表示为“o.e.r.action.document.RestMultiTermVectorsAction”,长度为48,48<50
继续尝试展开右起第四段“rest”,表示为“o.e.rest.action.document.RestMultiTermVectorsAction”,长度为51,51>50
故类名“org.elasticsearch.rest.action.document.RestMultiTermVectorsAction”在N=50的情况下,最佳表示为“o.e.r.action.document.RestMultiTermVectorsAction”
对于数据规模,限定如下:
N为uint32,每个类名最多包含2^8-1个部分,每个部分最长2^8-1个字符。
【Answer】
参考解答:
def ans(name: str, length: int): tokens = name.split(".") short_tokens = list() for token in tokens[:-1]: short_tokens.append(token[0]) short_tokens.append(tokens[-1]) ret = ".".join(short_tokens) if len(ret) > length: return "" for n, token in enumerate(tokens[-2::-1]): short_tokens[-2 - n] = token tmp = ".".join(short_tokens) if len(tmp) <= length: ret = tmp else: break return ret
双亲委派的双是什么意思
好处有什么
【Answer】
双是一个典型的翻译误解,没有双,只有单线
好处:
- 优先级机制
- 防止核心类被篡改
【Answer】
Java线程池的工作流程为:线程池刚被创建时,只是向系统申请一个用于执行线程队列和管理线程池的线程资源。在调用execute()添加一个任务时,线程池会按照以下流程执行任务。- 如果正在运行的线程数量少于corePoolSize(用户定义的核心线程数),线程池就会立刻创建线程并执行该线程任务- 如果正在运行的线程数量大于等于corePoolSize,该任务就将被放入阻塞队列中在阻塞队列已满且正在运行的线程数量少于maximumPoolSize时,线程池会创建非核心线程立刻执行该线程任务- 在阻塞队列已满且正在运行的线程数量大于等于maximumPoolSize时,线程池将拒绝执行该线程任务并抛出RejectException异常- 在线程任务执行完毕后,该任务将被从线程池队列中移除,线程池将从队列中取下一个线程任务继续执行- 在非核心线程处于空闲状态的时间超过keepAliveTime时间时,正在运行的线程数量超过corePoolSize,该非核心线程将会被认定为空闲线程并停止。因此,在线程池中所有线程任务都执行完毕后,线程池会收缩到corePoolSize大小。
【Answer】
静态链接:在程序执行之前完成所有的组装工作,生成一个可执行的目标文件(EXE文件)动态链接:在程序已经为了执行被装入内存之后完成链接工作,并且在内存中一般只保留该编译单元的一份拷贝。优缺点:静态链接的缺点很明显,一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖。另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。动态链接的优点,就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副本,而是这多个程序在执行时共享同一份副本;另一个优点是,更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。缺点,因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。原理:静态链接:把所有文件压缩打包成可执行文件动态链接:动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
- 标记清楚算法
- 复制算法,以及为什么可用?
- 复制-整理算法(Mark-Compact)
- 分代算法
【Answer】
- 标记清除算法,放到连续空间中,会导致空间碎片;分配大对象时,可能空间不够导致需要触发GC;
- 大部分对象稍纵即逝,不需要复制;空间浪费问题,可改进,放不下后需要分配担保;
- 只复制一部分
- 青年、老年
判断以下代码的输出是什么
a = 10
b = 20
c = 30
def do():
print(a)
print(b)
print(c)
c += 1
print(c)
do()
【Answer】
上述代码是有语法错误的。会下面的出现报错
UnboundLocalError: local variable 'c' referenced before assignment
可以修改成这样
a = 10 b = 20 c = 30 def do(): global c print(a) print(b) print(c) c += 1 print(c) do()
那么输出就是
10 20 30 31