最近遇到的一些面试问题

堆与栈的区别

  • 栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  • 堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。

区别和联系:

1.申请方式

  • 堆是由程序员自己申请并指明大小,在c中malloc函数 如p1 = (char *)malloc(10);
  • 栈由系统自动分配,如声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间

2.申请后系统的响应

  • 栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
  • 堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

3.申请大小的限制

  • 栈:在Windows下,栈是向低地址扩展的数据结 构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是 一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
  • 堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

4.申请效率的比较:

  • 栈由系统自动分配,速度较快。但程序员是无法控制的。
  • 堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.

为什么在Linux中删除一个大文件,硬盘还是占用100%

在Linux中,当我们使用rm在linux上删除了大文件,但是如果有进程打开了这个大文件,却没有关闭这个文件的句柄,那么linux内核还是不会释放这个文件的磁盘空间,最后造成磁盘空间占用100%,整个系统无法正常运行。这种情况下,通过df和du命令查找的磁盘空间,两者是无法匹配的,可能df显示磁盘100%,而du查找目录的磁盘容量占用却很小。

在linux系统中,想要彻底的删除一个文件,取决于两个“计数器”,这两个计数器一个是磁盘引用的“计数器”(记录了这个文件有几个硬链接),另一个则是内存引用的“计数器”(纪录了这个文件正在被几个进程所调用),当这两个“计数器”全部为0,也就是这个文件没有硬链接,没有任何进程在调用的时候,这个文件才会真正的被删除。

遇到这种情况,基本可以断定是某些大文件被某些程序占用了,并且这些大文件已经被删除了,但是对应的文件句柄没有被某些程序关闭,造成内核无法收回这些文件占用的空间。

查找出那些已经删除但是继续占用内存空间的文件:

lsof -n | grep deleted

或者直接查找删除的大文件

lsof -n | grep deleted |grep debug
rsyslogd    5154 root    7w      REG                8,3 94263283712    6030228 /var/log/debug.log (deleted)

关掉或者重启占用这些文件的应用程序就可以释放磁盘空间了

/etc/init.d/rsyslogd restart

如果这个日志文件正在被程序写入,同时又想清空,可以使用下面的方法

cat /dev/null > /data/access.log

手写binary search代码

参考 https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%88%86%E6%90%9C%E7%B4%A2%E7%AE%97%E6%B3%95

# Python3 版本 递归
def binary_search(arr,start,end,hkey):
	if start > end:
		return -1
	mid = start + (end - start) / 2
	if arr[mid] > hkey:
		return binary_search(arr, start, mid - 1, hkey)
	if arr[mid] < hkey:
		return binary_search(arr, mid + 1, end, hkey)
	return mid

memcached与redis区别

Memcached的优点:

  • Memcached可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS(取决于key、value的字节大小以及服务器硬件性能,日常环境中QPS高峰大约在4-6w左右)。适用于最大程度扛量。
  • 支持直接配置为session handle。
  • 坑少。

Memcached的局限性:

  • 只支持简单的key/value数据结构,不像Redis可以支持丰富的数据类型。
  • 无法进行持久化,数据不能备份,只能用于缓存使用,且重启后数据全部丢失。
  • 无法进行数据同步,不能将MC中的数据迁移到其他MC实例中。
  • Memcached内存分配采用Slab Allocation机制管理内存,value大小分布差异较大时会造成内存利用率降低,并引发低利用率时依然出现踢出等问题。需要用户注重value设计。

Redis的优点:

  • 支持多种数据结构,如 string(字符串)、 list(双向链表)、dict(hash表)、set(集合)、zset(排序set)、hyperloglog(基数估算)
  • 支持持久化操作,可以进行aof及rdb数据持久化到磁盘,从而进行数据备份或数据恢复等操作,较好的防止数据丢失的手段。
  • 支持通过Replication进行数据复制,通过master-slave机制,可以实时进行数据的同步复制,支持多级复制和增量复制,master-slave机制是Redis进行HA的重要手段。
  • 单线程请求,所有命令串行执行,并发情况下不需要考虑数据一致性问题。
  • 支持pub/sub消息订阅机制,可以用来进行消息订阅与通知。
  • 支持简单的事务需求,但业界使用场景很少,并不成熟。

Redis的局限性:

  • Redis只能使用单线程,性能受限于CPU性能,故单实例CPU最高才可能达到5-6wQPS每秒(取决于数据结构,数据大小以及服务器硬件性能,日常环境中QPS高峰大约在1-2w左右)。
  • 支持简单的事务需求,但业界使用场景很少,并不成熟,既是优点也是缺点。
  • Redis在string类型上会消耗较多内存,可以使用dict(hash表)压缩存储以降低内存耗用。

更深入讲解参考 http://www.techug.com/post/redis-vs-memcached.html

TCP三次握手,以及与UDP区别

为了保证服务端能收接受到客户端的信息并能做出正确的应答而进行前两次(第一次和第二次)握手,为了保证客户端能够接收到服务端的信息并能做出正确的应答而进行后两次(第二次和第三次)握手。

这个问题的本质是, 信道不可靠, 但是通信双发需要就某个问题达成一致. 而要解决这个问题, 无论你在消息中包含什么信息, 三次通信是理论上的最小值. 所以三次握手不是TCP本身的要求, 而是为了满足”在不可靠信道上可靠地传输信息”这一需求所导致的.

参考 https://github.com/jawil/blog/issues/14

TCP(传输层)

  • 优点:TCP 面向连接,可靠,稳定,传输数据前需要建立连接,故有三次握手四次挥手,还有拥塞控制,重传等
  • 缺点:慢,占用系统资源,有确认机制,三次握手,所以容易被攻击,DDos

UDP

  • 优点:快,无状态传输协议
  • 缺点:不稳定,不可靠,容易丢包

协程与线程的区别

  • 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
  • 线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是这样的)。
  • 协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。
  • 一个应用程序一般对应一个进程,一个进程一般有一个主线程,还有若干个辅助线程,线程之间是平行运行的,在线程里面可以开启协程,让程序在特定的时间内运行。
  • 协程和线程的区别是:协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。

高可用的一些方案

keepalived + haproxy