TCP/IP
应用层:最高层,主要有远程登录协议(Telnet),文件传送协议(FTP),简单邮件协议(SMTP),域名系统(DNS),简单网络管理协议(SNMP),超文本传输协议(HTTP),邮局协议(POP3);
传输层:传输控制协议(TCP),面向链接、保证高可靠性;用户数据报协议(UDP),无连接、不保证可靠性
网络层:网际协议(IP)不可靠,无连接,无状态;ARP协议,将IP地址解析为MAC地址;RARP协议;控制报文协议(ICMP)
TCP/IP
三次握手:
1. Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
2. Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
3. Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
四次挥手:
由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
1.数据传输结束后,客户端的应用进程发出连接释放报文段,并停止发送数据,客户端进入FIN_WAIT_1状态,此时客户端依然可以接收服务器发送来的数据。
2.服务器接收到FIN后,发送一个ACK给客户端,确认序号为收到的序号+1,服务器进入CLOSE_WAIT状态。客户端收到后进入FIN_WAIT_2状态。
3.当服务器没有数据要发送时,服务器发送一个FIN报文,此时服务器进入LAST_ACK状态,等待客户端的确认
4.客户端收到服务器的FIN报文后,给服务器发送一个ACK报文,确认序列号为收到的序号+1。此时客户端进入TIME_WAIT状态,等待2MSL(MSL:报文段最大生存时间),然后关闭连接。
socket:
TCP
1、服务器端程序:
1创建一个socket,用函数socket()
2绑定IP地址、端口等信息到socket上,用函数bind()
3设置允许的最大连接数,用函数listen()
4接收客户端上来的连接,用函数accept()
5收发数据,用函数send()和recv(),或者read()和write()
6关闭网络连接
2、客户端程序:
1创建一个socket,用函数socket()
2设置要连接的对方的IP地址和端口等属性
3连接服务器,用函数connect()
4收发数据,用函数send()和recv(),或read()和write()
5关闭网络连接
同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。
阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。
同步阻塞
同步非阻塞
IO多路复用是异步阻塞的一种方式。
异步非阻塞
事务:
1)原子性:事务被视为不可分割的最小单元,事物的所有操作要不成功,要不失败回滚,而回滚可以通过日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作。
2)一致性:数据库在事务执行前后都保持一致性状态,在一致性状态下,所有事务对一个数据的读取结果都是相同的。
3)隔离性:一个事务所做的修改在最终提交以前,对其他事务是可不见的。
4)持久性:一旦事务提交,则其所做的修改将会永远保存到数据库中
进程是资源分配的最小单位,线程是CPU调度的最小单位
做个简单的比喻:进程=火车,线程=车厢
- 线程在进程下行进(单纯的车厢无法运行)
- 一个进程可以包含多个线程(一辆火车可以有多个车厢)
- 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
- 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
- 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
- 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
- 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
- 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"
- 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”
包括用于存放程序正文、数据的磁盘和内存地址空间,以及在运行时所需要的I/O设备,已打开的文件,信号量等
- 在工作区添加、修改文件;
- 将修改后的文件放入暂存区域;
- 将暂存区域的文件提交到本地仓库;
- 将本地仓库的修改推送到远程仓库。
一种是LIB包含了函数所在的DLL文件和文件中函数位置的信息(入口),代码由运行时加载在进程空间中的DLL提供,称为动态链接库dynamic link library。
一种是LIB包含函数代码本身,在编译时直接将代码加入程序当中,称为静态链接库static link library。
共有两种链接方式:
动态链接使用动态链接库,允许可执行模块(.dll文件或.exe文件)仅包含在运行时定位DLL函数的可执行代码所需的信息。
静态链接使用静态链接库,链接器从静态链接库LIB获取所有被引用函数,并将库同代码一起放到可执行文件中。
关于lib和dll的区别如下:
(1)lib是编译时用到的,dll是运行时用到的。如果要完成源代码的编译,只需要lib;如果要使动态链接的程序运行起来,只需要dll。
(2)如果有dll文件,那么lib一般是一些索引信息,记录了dll中函数的入口和位置,dll中是函数的具体内容;如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。使用静态编译的lib文件,在运行程序时不需要再挂动态库,缺点是导致应用程序比较大,而且失去了动态库的灵活性,发布新版本时要发布新的应用程序才行。
(3)动态链接的情况下,有两个文件:一个是LIB文件,一个是DLL文件。LIB包含被DLL导出的函数名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到DLL文件。在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中相应函数代码的地址,从而节省了内存资源。DLL和LIB文件必须随应用程序一起发行,否则应用程序会产生错误。如果不想用lib文件或者没有lib文件,可以用WIN32 API函数LoadLibrary、GetProcAddress装载。因此dll在加载时在内存里只有一份
单向链表离不开指针
使用两个指针,第一个指针先走k-1步,然后第二个指针开始走。当第一个指针移动到最后时,第二个指针正好指向倒数第k个结点,只需要遍历一遍链表,显然更高效。
同样的方式可以判断链表中有没有闭环,使用的是指针的不同运行速度
TOP K 算法
用容量为K的最小堆来存储最大的K个数。最小堆的堆顶元素就是K个数中最小的一个。每次考虑一个数X,如果X比堆顶元素Y小,则保持最小堆不变,因为这个元素比最大的K个数小。如果X
比堆顶元素Y大,那么用X替换原来的堆顶元素Y,X可能破坏原来的最小堆结构(每个结点比它的父节点大),需要更新堆来维持堆的性质。更新堆时间复杂度为O(log2K).总的算法复杂度为O(N*log2k)
内存泄漏:
在申请内存时记录下该内存的地址和在代码中申请内存的位置,在内存销毁时删除该地址对应的记录,程序最后统计下还有哪条记录没有被删除,如果还有没被删除的记录就代表有内存泄漏。
所谓内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是会产生内存溢出的问题。
递归时间复杂度为O(n^2)
function fun(n) { if (n < 0){ return 0 } if (n === 1){ return 1 } if (n === 2){ return 2 } var a = 1 var b = 2 var temp = 0 for(var i = 3; i <= n; i++){ temp = a + b a=b b=temp } return temp } 优化递归调用的重复情况 时间复杂度O(n)
矩阵快速幂 时间复杂度为O(logn)
静态变量存储在虚拟地址空间的数据段和bss段,C语言中其在代码执行之前初始化,属于编译期初始化。而C++中由于引入对象,对象生成必须调用构造函数,因此C++规定全局或局部静态对象当且仅当对象首次用到时进行构造
条件断点是当满足条件就中断程序运行,命令:break line-or-function if expr。(GDB调试)
互斥锁:mutex,用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒
读写锁:rwlock,分为读锁和写锁。处于读操作时,可以允许多个线程同时获得读操作。但是同一时刻只能有一个线程可以获得写锁。其它获取写锁失败的线程都会进入睡眠状态,直到写锁释放时被唤醒。 注意:写锁会阻塞其它读写锁。当有一个线程获得写锁在写时,读锁也不能被其它线程获取;写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)。适用于读取数据的频率远远大于写数据的频率的场合。
自旋锁:spinlock,在任何时刻同样只能有一个线程访问对象。但是当获取锁操作失败时,不会进入睡眠,而是会在原地自旋,直到锁被释放。这样节省了线程从睡眠状态到被唤醒期间的消耗,在加锁时间短暂的环境下会极大的提高效率。但如果加锁时间过长,则会非常浪费CPU资源。
大端是指低字节存储在高地址;小端存储是指低字节存储在低地址。我们可以根据联合体来判断该系统是大端还是小端。因为联合体变量总是从低地址存储。
进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket。
1、管道:
管道主要包括无名管道和命名管道:管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信
普通管道PIPE:
它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端
它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)
它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
命名管道FIFO:
FIFO可以在无关的进程之间交换数据
FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
2、消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标记。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点。具有写权限得进程可以按照一定得规则向消息队列中添加新信息,对消息队列有读权限得进程则可以从消息队列中读取信息。消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
3、信号量semaphore
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器,可以用 来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步,而不是用于 存储进程间通信数据。
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
信号量基于操作系统的PV 操作,程序对信号量的操作都是原子操作。
每次对信号量的PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任 意正整数。
支持信号量组。
4信号signal
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
5共享内存(Shared Memory)
它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
共享内存是最快的一种IPC,因为进程是直接对内存进行存取
因为多个进程可以同时操作,所以需要进行同步
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问
6、套接字SOCKET:
socket也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机之间的进程通信。
3)线程间通信的方式:
1、临界区:
通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问;
2、互斥量 Synchronized/Lock:
采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问
3、信号量 Semphare:
为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。
4、事件(信号),Wait/Notify:
通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。
1. 局部数组过大。当函数内部的数组过大时,有可能导致堆栈溢出。局部变量是存储在栈中的,因此这个很好理解。解决这类问题的办法有两个,一是增大栈空间,二是改用动态分配,使用堆(heap)而不是栈(stack)。
2. 递归调用层次太多。递归函数在运行时会执行压栈操作,当压栈次数太多时,也会导致堆栈溢出。
3. 指针或数组越界。这种情况最常见,例如进行字符串拷贝,或处理用户输入等等。
红黑树在查找,插入删除的性能都是O(logn),且性能稳定,所以STL里面很多结构包括map底层实现都是使用的红黑树。
对于map,其底层是基于红黑树实现的,优点如下:
1)有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作
2)map的查找、删除、增加等一系列操作时间复杂度稳定,都为logn
缺点如下:
1)查找、删除、增加等操作平均时间复杂度较慢,与n相关
对于unordered_map来说,其底层是一个哈希表,优点如下:
查找、删除、添加的速度快,时间复杂度为常数级O(c)
缺点如下:
因为unordered_map内部基于哈希表,以(key,value)对的形式存储,因此空间占用率高
Unordered_map的查找、删除、添加的时间复杂度不稳定,平均为O(c),取决于哈希函数。极端情况下可能为O(n)
hash函数就是根据key计算出应该存储地址的位置
hash冲突解决办法
1.开放定制法
2.链地址法
3.公共溢出区法
4.再散列法B树和平衡二叉树稍有不同的是B树属于多叉树又名平衡多路查找树(查找路径不只两个),数据库索引技术里大量使用者B树和B+树的数据结构,让我们来看看他有什么特点;
B+树:
(1)B+跟B树不同B+树的非叶子节点不保存关键字记录的指针,只进行数据索引,这样使得B+树每个非叶子节点所能保存的关键字大大增加;
(2)B+树叶子节点保存了父节点的所有关键字记录的指针,所有数据地址必须要到叶子节点才能获取到。所以每次数据查询的次数都一样;
(3)B+树叶子节点的关键字从小到大有序排列,左边结尾数据都会保存右边节点开始数据的指针。
(4)非叶子节点的子节点数=关键字数(来源百度百科)(根据各种资料 这里有两种算法的实现方式,另一种为非叶节点的关键字数=子节点数-1(来源维基百科),虽然他们数据排列结构不一样,但其原理还是一样的Mysql 的B+树是用第一种方式实现);
死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的下相互等待的现象。死锁发生的四个必要条件如下:
互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源;
请求和保持条件:进程获得一定的资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源
不可剥夺条件:进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放
环路等待条件:进程发生死锁后,必然存在一个进程-资源之间的环形链
解决死锁的方法即破坏上述四个条件之一,主要方法如下:
资源一次性分配,从而剥夺请求和保持条件
可剥夺资源:即当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可剥夺的条件
资源有序分配法:系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反,从而破坏环路等待的条件
cap原理是
数据强一致性(consistency)
高可用性(availability)
分区容错性(partition tolerance)
判断图中间是不是有环
深度优先搜索
依次删除入度为0的节点,如果不存在入度为0的节点且途中仍有节点,则存在环