1.网络相关
OSI模型
物理层/Physical:(网线) 光线网线一类的物理设施
- Transmission and reception of raw bit streams over a physical medium
数据链路层/Data link:(MAC)
- Transmission of data frames between two nodes connected by a physical layer
- MAC地址:每一张网卡特有的“身份证号”,交换机通过MAC来寻找目标地址
- 分组交换:较大的数据会按照 1500字节 (以太网帧)来切割然后再进行传输
- 以太网帧:1500字节 -> 目标地址,源地址,类型,数据
- 交换机:有很多端口,根据每一帧的目标来链接目标地址
网络层/Network:(IP)
- Structuring and managing a multi-node network, including addressing, routing and traffic control
- 数据链路层的作用在于实现同一种数据链路下的包传递,而网络层则可以实现跨越不同数据链路的包传递
- IP地址对比MAC地址:就像是我从哈尔滨的家去广州的公司,家和公司就相当于IP地址,每一个中转站就相当于MAC地址,车站相当于交换机
- IPv4有32位,分为ABCD四种,分为网络标识(区分不同的网段)和主机标识(区分相同网段下不同的主机)
- 但是IPv4太小只有43亿,现在通过128位的IPv6还有子网掩码来解决IP地址不够的问题
- DNS协议就是逐层的向上把我们我们容易记住的域名转化为IP地址
传输层:(TCP/UDP)
- TCP: 需要创建更加复杂的链接以保证传输的安全性和有序性,可以通过控制流量来避免堵塞,三次握手(3 way handshake),四次挥手(4 way close),但是请求头带有序列号和应答号等占用空间(40字节)很大,适合传输文件等功能,只支持单对单
- UDP: 非常简单,请求头损耗小。但是安全性差,没有链接协议, 不能保证数据传输顺序,无法避免堵塞,只管发不管到没到。可以一对多,多对一,多对多,适合视频语音通话或者直播一类掉帧影响很小的应用
- 三次握手以及为什么不能是两次:1.客户端发送Synchronize number 2.服务器端同意链接发送Syn + Ack 3.客户端发送ACK并开始建立连接,如果一次握手就相当于UDP一样不管对方接没接电话就开始说,而且网络有时候会有延迟或者拥堵,如果一次SYN请求没有的到回应客户端会在等待后再发一次请求,如果只有两次握手的话服务器端很有可能在处理完第二次SYN请求后又建立起第一个syn的空链接
- 四次挥手:1.我这边要finish了,2.好我知道了 3.我这边也finish了 4.好的我知道了 (确认最后的信息都传输完成)
- 流量控制和拥塞控制:因为一条一条发数据的话非常的慢,需要不停的等待前一个发完才能发下一个,所以会根据客户端和服务器端较小的流量窗口作为吞吐量,如果吞吐量过大会导致丢包,这个吞吐量是通过先指数级增长再线性增长来快速寻找的(慢启动)
- TCP传输的数据流的分类:1. TCP交互数据流:一般情况下数据总是以小于MSS的分组发送,做的是小流量的数据交互,常见的应用比如SSH,Telnet等 2. TCP成块数据流:TCP尽最大能力的运载数据,数据基本都是按照MSS发送,常见的应用有FTP,Iperf等
应用层:(包含了三层)HTTP/HTTPS协议
- HTTP 1.0/1.1区别:HTTP 1.0每次请求都会建立一次TCP连接请求,1.1开始每个TCP连接可以处理多次多个请求,减少了复杂的链接断开,从而提高效率,支持只发送HEAD确定能否链接
- GET/POST/PUT区别:GET一般来说用来获取数据,会存在于历史记录里,而且可以添加进标签,可以发送的参数长度很短,而且参数会暴露在url中非常的不安全,而且GET请求会被浏览器自动cache,回退不需要刷新网页。POST请求一般用来发送数据,回退自动刷新网页,参数可以很长,但是信息依旧会暴露在request body里,比GET相对更安全。每次PUT会覆盖掉之前的资源,每次POST会新建一个资源。
- Cookie/Session/Local Storage: Cookie的话一般用于储存账号密码一类比较小的数据,因为会跟着HTTP请求一起发送,所以大小不能超过4K。Session是存在去服务器端,Local Storage和Session Storage用来储存一些本地数据来提高网页加载速度,比如购物车,避免每次都需要重新加载,session storage关闭页面删除,local storage需要手动删除
- 什么是HTTPS:通俗的讲就是HTTP security。添加了SSL/TLS协议对HTTP明文传输的内容进行了加密更加安全。要确保1.加密信息第三方看不懂。2.如果被第三方修改能被发现 3.避免第三方攻击冒充服务器端
- 对称加密比较简单,但是容易被第三方破解,非对称加密解密起来非常复杂,所以这里引入了公钥和私钥,服务器留着私钥,把公钥发给用户端,某些权威的公钥会预装在电脑里 (登陆学校网站用SSH先握手免密)
- 在HTTP2.0中,服务端可以在客户端某个请求后,主动推送其他资源,支持数据压缩和多路复用。
64 bit 机器最多能同时建立多少connection?
TCP请求头端口有16bit,理论上可以同时建立 2^16 - 1个连接
Nagle's算法 link
- Nagle's算法:是为了避免每次传输的数据过小(ex:1字节数据加上40字节请求头),要求TCP连接上最多只能有一个未被确认的小分组,在该分组的确认到达之前不能发送其他的小分组。 当目前待传输的数据小于指定值的时候会先放在buffer等待合并传输来提升效率。
- 缺点:会造成网络延迟,不适合对实时性要求很高的请求。特别是如果服务器端是Delay ACK策略的时候会互相等待造成死锁。
- Delay ACK:也是为了提高TCP的运载效率会等待多个请求后打包一起发送数据。
流量控制和拥塞控制 link
- 流量控制:收件方在收到SYN请求后会在返回的ACK中会包含接受窗口大小和一个希望接受数据的序列号,发送方可以根据已发送SYN的序列号还有ACK中的信息来算出当前还可以发送多少数据(优点:避免发送方发送过快导致丢包)
- 拥塞控制:慢开始和拥塞 避免通过先指数级增长再线性增长寻找到滑动窗口和拥塞窗口的较小值(优点:避免网络崩溃,超过网络负荷) 快速重传 当接收方发现接收到的序列号是乱序的时候快速通知发送方重传,避免像超时重传一样再进入慢启动环节
在浏览器输入URL后执行的全部过程
- DNS 解析:将域名解析成 IP 地址
- TCP 连接:TCP 三次握手
- 发送 HTTP 请求
- 服务器处理请求并返回 HTTP 报文
- 浏览器解析渲染页面
- 断开连接:TCP 四次挥手
DNS域名解析
在接收到域名后会一次在浏览器缓存,系统缓存和路由器缓存中查找,如果依然找不到的话会向上去根域名服务器(.com),顶级域名服务器(.baidu),二级域名服务器(.zhidao)请求递归寻找
2.数据结构相关
HashMap的实现原理?link
- HashMap的底层实现其实是基于Entry<K,V> Array+LinkedList(JDK7), Array+BST(JDK8)。
- 源码中会用hash和indexFor方法通过异或,位移还有seed来使数据散列的尽量均匀。hashMap的constructor并不会直接分配内存,只有再第一个put后会分配初始容量16,到达loadFactor后会rehash double
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
- 每次翻倍是因为 indexFor方法是index = HashCode(key)&(len-1)如果翻倍的话只需要改变一位
- 如果出现了hash冲突一般有开放寻址,再散列和链表,HashMap用的是链表。
HashCode相等.equals()是否相等
.equals()相等HashCode一定相等,HashCode相等.equals()不一定相等。因为.equals()相等代表内存地址相等,所以HashCode一定相等,但是HashCode相等会存在Hash冲突
为什HashMap线程不安全,但是HashTable线程安全, ConcurrentHashMap是怎么实现的 link
-
rehash冲突:JDK7之前在rehash的时候为了O(1)的时间复杂度采用头插法,但是如果在多线程的情况下如果线程1调整链表l1.next = l2的时间片到期,线程2进行rehash的时候再次反转l2.next = l1此时会发生死锁
-
put冲突:put()和putVal()不同步,如果thread1和thread2操作有相同hashcode的链表中的某一位,thread1在put()后时间片用完了,thread2对该位置进行putVal()后thread1会覆盖该位置
-
HashTable是synchronized的,同一时间只能有一个线程操作,效率较低
-
ConcurrentHashMap引入了一个分段锁的概念,他把一个HashMap分成多个部分,每个部分相当于一个HashTable,每个部分是都是Synchronized。这样既保证了效率又确保线程安全
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size] = e;
size++
return true;
}
源码中会先执行arr[size] = num1, size++。在多线程中如果arr[size] = num1执行完切换到另一个线程arr[size]会被覆盖。ArrayList会在满了以后扩容1.5倍
堆和栈的区别
- (Heap)堆内存:用来储存实例化的对象,一个JVM只有一个Heap,线程共享Heap,大小动态变化,没有预设的生命周期
- (Stack)栈内存:用来储存局部变量和引用变量,每个线程的栈都是独立的,速度非常快,但是生命周期固定灵活性低。
红黑树和平衡二叉树
BST的目标插入时间是O(logN),但是如果BST的最长边特别长的话会导导致插入时间趋于O(N)。红黑树做出的优化是最深的子节点不超过最浅子节点的两倍。从而保证数据结构的效率,又可以保证每次操作在3次以内(平衡二叉树绝对平衡无法预测旋转次数。)
3.JVM和Garbage Collection
什么是垃圾回收? link
不像c++一样需要手动free()释放内存,Java会自动不定时的对没人引用的对象所占用的内存进行清理。(引用分为:Strong,Soft,Weak,phantom reference)
- 第一步: 找到所有没人引用的对象 搜索算法:
- 引用计数算法,当一个对象计数器为0的时候被回收。执行简单,但是无法应对循环调用并且需要额外开销,用于早期的JVM。
- 根搜索算法,通过一系列的GC Roots寻找到所有与他们Strong相连的对象,并对没有相连的对象进行回收。 hint:搜索时需要暂停线程来保证搜索的正确性,回收时会用低级别线程调用finalize() function,在程序执行前被引用仍有机会免于回收。
- 第二步: 回收无用对象所占用的空间 清除算法:
- Tracing Collector(标记-清除),效率低并且会造成大量碎片空间,适用于存活率较高的场景
- Compacting Collector(标记-整理),不会发生碎片化内存问题,暂停时间较长
- Copying Collector(复制),方法2的优化,标记和整理可以同步进行,每次只回收一半内存更高效,但是可分配内存缩小了一半。 内存区域分配:(不同对象的生命周期不同,根据不同的生命周期使用不同的算法策略可以提升垃圾回收效率)
- 新生代:分为Eden, Survivor0, Survivor1,采用Copying Collector。在Survivor区每经历一次GC年龄加1,超过15岁会进入老年代。
- 年老代:一般存放生命周期较长或者较大的对象,老年代满后会发生Full GC。
- 永生代:用于储存Class, Method一类,Java SE8中已经取消并改为Meta Space。 Thread策略:
- 串行:用于单线程环境,非常的少见。
- 并行:用于多线程环境,是Java的默认策略。
- 并发:只在标记和最后进行finalized的时候占用所有线程,有利于程序的吞吐量。
4.数据库相关
数据库索引
就像是BST可以将搜索速度从O(N)提升到O(logN)一样,如果我们针对某一字段增加索引的话可以将搜索速度提升至常熟级别(height),大大提高存取效率。
B+树对比B树
每次磁盘IO的时间大概是9ms,一般会预读附近4k作为一个page,我们把每一个page作为一个节点。B+树的中间节点因为不会保存数据而可以额外储存更大量的地址信息,从而指数级增长每层的宽度,也就减少了height。优点:1.查询更快。2.所有节点访问时间相同。3.叶节点有序排列,B树需要InOrder遍历
最左前缀原则
MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city),而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。
5.进程/线程
由于CPU的处理速度与hard disk的IO速度差别非常大,CPU经常要有很长的空置时间等待IO处理,所以提出了异步IO理论。
进程与线程的优缺点
- 进程是资源分配的最小单位,线程是程序执行的最小单位。
- 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
- 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
- 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
进程调度算法
- 先来先服务调度算法 FCFS
- 时间片轮转调度法
- 短作业(SJF)优先调度算法
- 优先级调度算法
进程间通信方式
- 管道:速度慢,容量有限,只有父子进程能通讯
- FIFO:任何进程间都能通讯,但速度慢
- 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
- 信号量:不能传递复杂消息,只能用来同步
- 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
死锁的四个条件
死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进
- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。