基本概念:
- 机械硬盘
- 固态硬盘
一个m阶的B树具有如下几个特征:B树中所有结点的孩子结点最大值称为B树的阶,通常用m表示。一个结点有k个孩子时,必有k-1个关键字才能将子树中所有关键字划分为k个子集。
- B树主要用于文件系统以及部分数据库索引,例如: MongoDB。而大部分关系数据库则使用B+树做索引,例如:mysql数据库;
- 从查找效率考虑一般要求B树的阶数m >= 3;
- B-树上算法的执行时间主要由读、写磁盘的次数来决定,故一次I/O操作应读写尽可能多的信息。因此B-树的结点规模一般以一个磁盘页(一般是4K)为单位。一个结点包含的关键字及其孩子个数取决于磁盘页的大小。
B+树是B树的变种,B+树相比B树的优势: 1.单一节点存储更多的元素,使得查询的IO次数更少; 2.所有查询都要查找到叶子节点,查询性能稳定; 3.所有叶子节点形成有序链表,便于范围查询。
有关B树/B+树的详细内容,参见:https://blog.csdn.net/z_ryan/article/details/79685072
参见:https://blog.csdn.net/las723/article/details/93767240
不属于一个具体的数据结构,它更多是一种数据结构的设计思想。大多NoSQL数据库核心思想都是基于LSM来做的,只是具体的实现不同。
LSM树由两个或以上的存储结构组成,比如在论文中为了方便说明使用了最简单的两个存储结构。一个存储结构常驻内存中,称为C0 tree,具体可以是任何方便健值查找的数据结构,比如红黑树、map之类,甚至可以是跳表。另外一个存储结构常驻在硬盘中,称为C1 tree,具体结构类似B树。C1所有节点都是100%满的,节点的大小为磁盘块大小。
插入一条新纪录时,首先在日志文件中插入操作日志,以便后面恢复使用,日志是以append形式插入,所以速度非常快;将新纪录的索引插入到C0中,这里在内存中完成,不涉及磁盘IO操作;当C0大小达到某一阈值时或者每隔一段时间,将C0中记录滚动合并到磁盘C1中;对于多个存储结构的情况,当C1体量越来越大就向C2合并,以此类推,一直往上合并Ck。
文件系统将硬盘空间以块(一般大小是4K)为单位进行划分,每个文件占据若干个块,然后再通过一个文件控制块 FCB 记录每个文件占据的硬盘数据块。
大小固定
原理
比较:
- 概念略
- 常见时间复杂度比较
-
P 问题:能在多项式时间复杂度内解决的问题。
-
NP 问题:能在多项式时间复杂度内验证答案正确与否的问题。
-
NP ?= P
-
NP-hard 问题:比 NP 问题更难的问题(NP问题的解法可以规约到 NP-hard 问题的解法)
-
NP 完全问题:是一个 NP-hard 问题,也是一个 NP 问题
简单的内容就不列出相关知识点了,这块实在太基础了~.~
数组+链表,实现快速查找与删除 --> JDK HashMap基本思想,JDK 1.8有进一步优化,引入红黑树
知识点:hash冲突
- 后进先出
- 先进先出
使用:使用队列搜索最短路径
- 实际上就是使用队列来实现BFS
- 左子树上所有结点的值均小于或等于它的根结点的值
- 右子树上所有结点的值均大于或等于它的根结点的值
- 左、右子树也分别为二叉排序树。
- 从任何一个节点出发,左右子树深度之差的绝对值不超过1。
- 左右子树仍然为平衡二叉树。
插入/删除节点时,通过旋转二叉树来回复平衡
- 每个节点只有两种颜色:红色和黑色。
- 根节点是黑色的。
- 每个叶子节点(NIL)都是黑色的空节点。
- 从根节点到叶子节点,不会出现两个连续的红色节点。
- 从任何一个节点出发,到叶子节点,这条路径上都有相同数目的黑色节点。
- 穷举算法
- 递归算法
- 贪心算法
- 背包问题
- 迪杰斯特拉算法
- 动态规划
- 背包问题
- 遗传算法
- 基因编码,染色体
- 适应函数与控制参数
- 选择算法
- 交叉遗传
- 遗传算法得到的不是最优解,但速度一般会快一些
一次网络通信历程
负责数据的物理传输。
将数据封装成数据帧,以帧为单位通过物理层进行通信,有了帧,就可以在帧上进行数据校验,进行流量控制。
链路层会定义帧的大小,这个大小也被称为最大传输单元(Maximum Transmission Unit)。
一个标准的以太网数据帧大小是:1518
,头信息有14字节,尾部校验和FCS占了4字节,所以真正留给上层协议传输数据的大小就是:1518 - 14 - 4 = 1500。
MTU为何是1500参见 这篇文章:https://developer.aliyun.com/article/222535
上图来自《网络是怎样连接的》。
TCP头部长度为20-60字节,IP头部长度为20-60字节,一般而言这两者都是20字节,因此MSS长度一般为1460字节。
数据链路层也会将封装好的帧添加一个帧头,帧头里记录的一个重要信息就是发送者和接受者的 MAC 地址。
网络层 IP 协议使得互联网应用根据 IP 地址就能访问到目标服务器,请求离开 App 后,到达运营服务商的交换机,交换机会根据这个 IP 地址进行路由转发,可能中间会经过很多个转发节点,最后数据到达目标服务器。
网络层的数据需要交给链路层进行处理,而链路层帧的大小定义了最大传输单元,网络层的 IP 数据包必须要小于最大传输单元才能进行网络传输,这个数据包也有一个 IP 头,主要包括的就是发送者和接受者的 IP 地址。
IP 协议不是一个可靠的通信协议,不会建立稳定的通信链路,并不会确保数据一定送达。要保证通信的稳定可靠,需要传输层协议 TCP。
TCP协议是一种面向连接的、可靠的、基于字节流的传输层协议。
TCP作为一个比较基础的通讯协议,有很多重要的机制保证了TCP协议的可靠性和强壮性:
- 使用序号,对收到的TCP报文段进行排序和检测重复的数据
- 无错传输,使用校验和检测报文段的错误
- 使用确认和计时器来检测和纠正丢包或者延时
- 流量控制,避免主机分组发送得过快而使接收方来不及完全收下
- 拥塞控制,发送方根据网络承载情况控制分组的发送量,以获得高性能同时避免拥塞崩溃
- 丢失包的重传
和服务器的通信结束之后,用来通信的套接字也就不会再使用了,这时我们就可以删除这个套接字了。不过,套接字并不会立即被删除,而是会等待一段时间之后再被删除。等待这段时间是为了防止误操作,引发误操作的原因有很多,比如,假设客户端先发起断开,4次挥手流程如下:
(1)客户端发送 FIN
(2)服务器返回 ACK 号
(3)服务器发送 FIN
(4)客户端返回 ACK 号
如果最后客户端返回的 ACK 号丢失了,结果会如何呢?这时,服务器没有接收到 ACK 号,可能会重发一次 FIN。如果这时客户端的套接字已经删除了,会发生什么事呢?套接字被删除,那么套接字中保存的控制信息也就跟着消失了,套接字对应的端口号就会被释放出来。这时,如果别的应用程序要创建套接字,新套接字碰巧又被分配了同一个端口号 B,而服务器重发的 FIN 正好到达,会怎么样呢?本来这个 FIN 是要发给刚刚删除的那个套接字的,但新套接字具有相同的端口号,于是这个 FIN 就会错误地跑到新套接字里面,新套接字就开始执行断开操作了。之所以不马上删除套接字,就是为了防止这样的误操作。
至于具体等待多长时间,这和包重传的操作方式有关。网络包丢失之后会进行重传,这个操作通常要持续几分钟。如果重传了几分钟之后依然无效,则停止重传。在这段时间内,网络中可能存在重传的包,也就有可能发生前面讲到的这种误操作,因此需要等待到重传完全结束。协议中对于这个等待时间没有明确的规定,一般来说会等待几分钟之后再删除套接字。
以上引自《网络是怎样连接的》
HTTP协议。
- GET
- POST
- PUT
- DELETE
- TRACE
- OPTION
- HEAD
- 1xx消息——请求已被服务器接收,继续处理
- 2xx成功——请求已成功被服务器接收、理解、并接受
- 3xx重定向——需要后续操作才能完成这一请求
- 4xx请求错误——请求含有词法错误或者无法被执行
- 5xx服务器错误——服务器在处理某个正确请求时发生错误
-
HTTP 1.0
- 客户端和服务器之间交换的每个请求 / 响应都会创建一个新的 TCP 连接,这意味着所有请求之前都需要进行 TCP握手连接,因此所有请求都会产生延迟
-
HTTP 1.1
- 允许客户端复用 TCP 连接,从而分摊了建立初始连接和针对多个请求缓慢启动的成本。但任意时点上每个连接只能执行一次请求 / 响应交换。
- 由于 HTTP/1.1 只允许客户端同时进行一次HTTP 请求 / 响应交换,因此在网络层上获得并发能力的唯一方法是并行使用多个 TCP 连接。内部实现时使用了一个队列
-
HTTP 2.0
- 引入了 HTTP“流”的概念,允许将不同的 HTTP 并发地复用到同一 TCP 连接上, 使浏览器更高效地复用 TCP 连接。
- HTTP/2 解决了单个 TCP 连接的使用效率低的问题,现在可以通过同一连接同时传输多个请求 / 响应。但是,TCP并不理解HTTP流,当多个HTTP请求复用一个TCP连接,如果前面的请求/响应没有处理完,后面的请求/响应也无法处理,也就是会出现队头堵塞现象。
-
HTTP 3.0
-
不是使用 TCP 作为会话的传输层,而是使用 QUIC (一种新的互联网传输协议)。该协议在传输层将流作为一等公民引入。多个QUIC 流共享相同的 QUIC 连接,因此不需要额外的握手和慢启动来创建新的 QUIC 流。但 QUIC 流是独立的,因此在大多数情况下,只影响一个流的丢包不会影响其他流,
这是因为 QUIC 数据包封装在 UDP 数据包。
-
分类:
- 同步阻塞IO
- 同步非阻塞IO
- IO复用
- 信号驱动IO
- 异步IO
进行 I/O 操作时,用户线程会一直阻塞,直到读操作或者写操作完成
典型:ServerSocket与Socket
I/O 操作立即返回,发起线程不会阻塞等待。
非阻塞 read 操作:
• Socket 接收缓冲区有数据,读 n 个(不保证数据被读完整,因此有可能需要多次读)。
• Socket 接收缓冲区没数据,则返回失败(不会等待)。
非阻塞write:
• Socket 发送缓冲区满,返回失败(不会等待)。
• Socket 发送缓冲区不满,写 n 个数据(不保证一次性全部写入,因此可能需要多次写)。
示例:JDK NIO
内部实现select, poll, epoll。
select是通过不断的轮询,查看是否有就绪事件。如果有的话,再把所有的流遍历一遍看是哪个流准备就绪。而poll也是采用这样的轮询,只不过poll采用的是链表存储,所以没有最大连接数的限制,epoll是even poll,和忙轮询、无差别轮询不一样,它会把哪个流发生了怎样的I/O事件通知我们,不用全都遍历一遍才知道是哪个流发生了。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1)),而select和poll查找复杂度都是O(n)。
- 单个进程能够监视的文件描述符(file descriptor)的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但 是这样也会造成效率的降低。
- 从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
- 没有最大数量限制(因为采用了链表存储,但是数量过大后性能也是会下降)
- 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。
- epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
- epoll事先通过epoll_ctl()来注册一 个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait() 时便得到通知。
- 如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当遇到大量的idle- connection,就会发现epoll的效率大大高于select/poll。