项目
理论 (略)
需求分析的步骤
- 获取需求,识别问题
- 分析需求,建立模型
- 需求文档化
- 需求验证
功能性需求和非功能性需求
-
功能性需求
有具体的要完成的内容的需求
-
非功能性需求
系统性能、可靠性、可维护性、可扩充性、对技术/业务的适应性
统一过程UP的FURPS+模型中的软件特性
-
F - 功能性
-
U - 可用性
-
R - 可靠性
-
P - 性能 – 响应时间、吞吐量、准确性、有效性、资源利用率
-
S - 可支持性
-
‘+’ - 指辅助性的和次要的因素
需求工程
应用已证实有效的技术、方法进行需求分析,确定客户需求,帮助分析人员理解问题并定义目标系统的特征和规模的一门学科。
-
需求获取
-
需求建模
螺旋模型 – 瀑布模型 和 快速原型的迭代 的 组合
-
需求验证
-
需求管理
软件工程
将系统化的、严格约束的、可量化的方法应用于软件的开发、运行和维护,即将工程化应用于软件。
软件质量
- 正确性、健壮性、效率、完整性、可用性、风险(产品运行)
- 可理解性、可维修性、灵活性、可测试性(产品修改)
- 可移植性、可再用性、互运行性(产品转移)
-
你们的项目在做什么?有什么意义?
-
你在这个项目中做了什么工作?
-
项目的算法细节、采用的技术、框架或算法
基于深度学习的爆管预测
基于深度学习的爆管预测系统,目的是实时监测管道信息,及时处理可能发生的爆管事件,通过训练好的模型预测未来一段时间内正常运行状态下的管网流量数据,通过实时上报的监测点数据与正常数据进行对比,如与预测数据的偏差超过阈值,则判定此时发生异常事件,并在地图模块上提示用户某处管道可能发生了爆管事件,及时处理,其中阈值的确定需通过已有爆管事件数据进行分析得出。
为了更好地展示数据与管理,一共分为了四个模块,分别是地图展示模块,重点监测点模块,深度学习相关展示模块,综合信息模块。
城市供水管网中的爆管事件异常检测问题
- 当管网系统中发生爆管事件时,管网监测点上报的流量和压力数据会出现异常变化;可以考虑通过监测点上报的数据来对爆管事件进行判定。
- 管网中供水情况与季节、天气以及每天的时间点密切相关。考虑到监测点上报的数据存在很大的时间关联性,对于这种时间序列异常检测问题,采用的是深度学习的方式去解决。即使用一个拟合效果较好的模型先去预测该时间序列的未来走势,再将预测后的值与真实值进行对比,只要超出了一定的阈值范围,就判定该值为异常值,在具体的管网系统中异常值即为发生了爆管事件。
模型介绍 – LSTM
长短时记忆网络 LSTM 可以解决 RNN 梯度弥散和梯度爆炸,而且能有“长期记忆”
为了针对管网数据的特殊性,需要构建合适的模型对数据进行拟合。而管网系统中的供水信息具有较强的时间相关性。相比于一般的网络,RNN被证明能够很好的适应这种时间上具有相关性的场景。LSTM神经网络作为RNN的变体模型,在信息的记忆机制上进行了改进,因此能够有效的解决长时间的依赖问题。通过对管网数据的特殊性分析,改良后的LSTM模型可以对时间的历史序列信息保持记忆力。网络模型的训练是网络通过训练数据集不断的调整其中的参数,使损失函数收敛的过程。网络模型需要确定几个关键的超参数选择:学习速率、LSTM的层数、每层LSTM网络单元数目。同时,结合供水流量时序预测这个具体的场景,将时间维度作为一个超参数选择项,构建一个具有针对性的LSTM模型。但网络模型本身参数复杂,参数的设置对最后的拟合效果有很大的影响。为了解决这个问题,需要在模型中设计并引入合适的算法,对模型进行优化。
爆管事件检测,通过训练好的模型对未来一段时间内正常运行状态下的管网流量数据进行预测,通过实时上报的监测点数据与正常数据进行对比,如与预测数据存在超过阈值的较大偏差,则判定此时发生爆管异常事件。阈值的确定需通过已有爆管事件数据进行分析得出。
训练方式
-
优化器
机器学习的任务就是优化参数使之达到最合适的值,同时也就是使损失函数达到最小。损失函数即目标函数的值与真实值的差值函数,实际上就是欲优化参数的函数。而优化器的任务就是在每一个epoch中计算损失函数的梯度,进而更新参数。
一般过程:给定一组训练样本(x, y),对于若干个x运用此时的参数求出预测值y’,而y’与这些x对应的真实值y的差值(即损失函数)作为优化器的目标函数(使得函数值越小越好)。将该目标函数求梯度(有的优化器还要求动量),而后使用梯度值(动量)更新参数。直到停止条件达到为止。
Adam优化器
Adam在深度学习领域是一种很受欢迎的算法,该算法计算了梯度和平方梯度的指数移动平均值,在训练中能很快取得好的成果,下图为其核心算法:
翻译:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2hA5TO0m-1626139004076)(C:\Users\toutou\AppData\Roaming\Typora\typora-user-images\image-20210703002800300.png)]
-
调学习率
CosineAnnealingWarmRestarts – “余弦退火暖重启”,是一种学习率衰减的策略
-
余弦退火(Cosine annealing)可以通过余弦函数来降低学习率。余弦函数中随着x的增加余弦值首先缓慢下降,然后加速下降,再次缓慢下降。这种下降模式能和学习率配合,以一种十分有效的计算方式来产生很好的效果
-
由于目标优化函数可能是多峰的,除了全局最优解之外还有多个局部最优解,在训练时梯度下降算法可能陷入局部最小值,此时可以通过突然提高学习率,来“跳出”局部最小值并找到通向全局最小值的路径,这种方式称为热重启(warm restart)
-
阈值确定
在模型训练完成后,在得到预测值和真实值之间检测异常,需要确定阈值边界。当预测值与真实值之间误差超过设定的阈值时,判定此时出现异常,即爆管发生。
3σ原则
在正态分布中σ代表标准差 ,μ代表均值。(σ和μ均表示预测值和真实值之间误差的标准差和均值),其中:
- 数值分布在(μ-σ,μ+σ)中的概率为0.6826
- 数值分布在(μ-2σ,μ+2σ)中的概率为0.9544
- 数值分布在(μ-3σ,μ+3σ)中的概率为0.9974
即在此方法中,可以通过模型训练产生的预测值与真实值(用于训练的真实值是正常值,不包括异常)之间的差值确定 阈值(μ-3σ,μ+3σ)。
在初步的实验结果中,如上图,黑色曲线为真实数据,绿色曲线为预测数据,红色节点为超出阈值范围,被判定为异常的数据点。
实验评测指标
异常检测的评测标准与分类问题的评测标准相似:
预测结果为阳性 Positive | 预测结果为假阳性 Negative | |
---|---|---|
预测结果正确 True | TP | TN |
预测结果错误 False | FP | FN |
-
recall:召回率计算公式
正类被检测为正类,占所有正类标签数据的比例,即在异常检测的场景下,多少异常被正确检测。
-
precision:精确率计算公式
正类被检测为正类,占所有被检测为正类的数据的比例,在异常检测场景下,即在被检测为异常的数据 中,多少异常是被正确检测的。
-
F-score:在F-score计算中,β是用来平衡precision和recall的权重
- 如果取β为1,表示precision与recall一样重要
- 如果取β小于1,表示precision比recall重要
- 如果取β大于1,表示recall比precision重
数据结构
知识点
数据结构是指相互有关联的 数据元素 的集合。
数据元素之间的任何关系都可以用 前驱 和 后继 关系来描述
数据结构作为计算机的一门学科,主要研究
- 数据的逻辑结构
- 数据的存储结构
- 对各种数据结构进行的运算
数据结构中,与所使用的计算机无关的是数据的 逻辑结构
数据的存储结构 是指 数据的逻辑结构在计算机中的表示
数据的逻辑结构 是指 反映数据元素之间逻辑关系的数据结构
数据结构分为逻辑结构与存储结构,如线性链表属于存储结构
下列叙述
- 数据的存储结构与数据处理的效率密切相关 ( √ )
- 数据的存储结构与数据处理的效率无关 ( × )
- 数据的存储结构在计算机中所占的空间不一定是连续的 ( √ )
- 一种数据的逻辑结构可以有多种存储结构 ( √ )
根据数据结构中各数据元素之间的逻辑结构,将数据结构分为
- 线性结构
- 非线性结构
常用的存储结构
-
顺序
把逻辑上相邻的结点存储在物理位置相邻的存储单元中
特点 – 逻辑结构中相邻的结点在存储结构中仍相邻
-
链接
头指针和头结点的区别?
-
头指针
指向第一个节点存储位置的指针,具有标识作用,头指针是链表的必要元素,无论链表是否为空,头指针都存在。
-
头结点
放在第一个元素节点之前,便于在第一个元素节点之前进行插入和删除的操作,头结点不是链表的必须元素,可有可无,头结点的数据域也可以不存储任何信息。
-
-
索引
顺序存储结构是用一段连续的存储空间来存储数据元素,可以进行随机访问,访问效率较高;
链式存储结构是用任意的存储空间来存储数据元素,不可以进行随机访问,访问效率较低。
数据结构具有记忆功能的是
-
队列
-
上溢当循环队列非空且队尾指针等于对头指针时,说明循环队列已满,不能进行入队运算
-
下溢
当循环队列为空时,不能进行退队运算
-
-
循环队列
-
当 rear < front 时,元素个数=总容量-( front-rear )
在一个容量为25的循环队列中,若头指针front=16,尾指针rear=9,则该循环队列中共有 18 个元素
-
-
栈 ( √ )
后进栈的元素先出栈,所以对栈进行出栈操作,出来的元素肯定是后存入栈中的元素,所以栈有记忆功能
递归算法一般需要利用 栈 实现
在实际应用中,带链的栈可以用来收集计算机存储空间中所有空闲的存储结点,这种带链的栈称为 可利用栈
-
顺序表
两个栈共享一个存储空间的好处
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dRAMSwzB-1626139004080)(C:\Users\toutou\AppData\Roaming\Typora\typora-user-images\image-20210703171432807.png)]
-
节省存储空间
-
降低上溢发生的机率
因为溢出是指栈满,对于共享栈来说,两个栈顶接触了,甚至越过了,也不是溢出,只是会导致另一方的栈出现错误,只有整个空间被一方占满,才会发生溢出
栈中的上溢更多指的是栈顶指针超出了栈的最大范围,而栈的一些操作如插入都**只是在栈顶进行,**只可能发生上溢,所以也就没有下溢。
线性链表中的各元素在存储空间中的位置不一定是连续的,且各元素的存储顺序也是任意的
非空的循环单链表head的尾结点(由p所指向),满足 p->next=head
与单向链表相比,双向链表的优点之一是 更容易访问相邻结点
在(D)中,只要指出表中任何一个结点的位置,就可以从它出发依次访问到表中其他所有结点。
A.线性单链表 B.双向链表 C.线性链表 D.循环链表
// 其中双向链表不能从尾部直接访问到头部,即不是依次访问
栈和队列的共同特点
只允许在某端插入和删除元素
- 栈 – 后进先出
- 队列 – 先进先出
栈通常采用的两种存储结构
-
线性存储
-
链式存储
链式存储特点
- 不必事先估计存储空间
- 插入删除不需要移动元素
- 所需空间与线性表长度成正比
用链表表示线性表的优点
便于插入和删除
在单链表中,增加头结点的目的
带头结点 | 不带头节点 | |
---|---|---|
空 | ||
非空 |
- 使得在链表**
头
部的操作(如:插入删除等)与在链表中
部与尾
**部一致(统一) - 使**
非空
链表与空
**链表的操作统一
// 带头节点的单链表(空) 插入节点
s->next = H->next;
H->next = s;
// 不带头节点的单链表(空) 插入节点
s->next = H;
H = s;
// 带头节点的单链表(非空) 插入节点
s->next = L->next;
L->next = s;
// 不带头节点的单链表(非空) 插入节点
s->next = L->next;
L->next = s;
循环链表的主要优点
从表中任一结点出发都能访问到整个链表
对于线性表 L= (a1, a2, a3, …… ai, …… an)
- 每个元素都有一个直接前驱和直接后继 ( × ) 除 头 / 尾 元素
- 线性表中至少要有一个元素 ( × ) 可为空
- 表中诸元素的排列顺序必须是由小到大或由大到小 ( × )
- 除第一个和最后一个元素外,其余每个元素都有一个且只有一个直接前驱和直接后继 ( × )
**线性表若采用链式存储结构时,要求内存中可用存储单元的地址 ** 可连续可不连续
线性表的顺序存储结构和线性表的链式存储结构分别是 随机存取的存储结构、顺序存取的存储结构
树是结点的集合,其根结点数目 有且只有1
在深度为5(根结点深度从1开始)的满二叉树中,叶子结点的个数为 16 ,总有 31 个结点
( 此处满二叉树指国内定义 )
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WjUgcZGV-1626139004081)(C:\Users\toutou\AppData\Roaming\Typora\typora-user-images\image-20210703155907762.png)]
设一棵完全二叉树共有699个结点,则在该二叉树中的叶子结点数为 350
分支结点数 = 1 + 2 + 4 + 8 +16 + 32 + 64 + 128 + 256 = 255+256 = 511 ,
699 - 511 = 188,188 / 2 = 94,所以叶子结点数 = 188 + (256-94) = 350
说明:
完全二叉树总结点数为N
-
若N为奇数,则叶子结点数为 (N+1)/2;
-
若N为偶数,则叶子结点数为 N/2
具有3个结点的二叉树有 5 个形态
设一棵二叉树中有3个叶子结点,有8个度为1的结点,则该二叉树中总的结点数为 13
前序/先序遍历: 根 左 右
中序遍历: 左 根 右
后序遍历: 左 右 根
已知二叉树后序遍历序列是 dabec,中序遍历序列是 debac,它的前序遍历序列是 cedba
已知二叉树前序遍历和中序遍历分别为 ABDEGCFH 和 DBGEACHF,则其后序遍历为 DGEBHFCA
已知某二叉树的前序遍历是abdgcefh,中序遍历是dgbaechf,则其后序遍历是 gdbehfca
N个顶点的连通图中边的条数至少为N-1
N个顶点的强连通图的边数至少有N
复杂度分析
堆排序的时间复杂度和堆排序中建堆过程的时间复杂度
- 堆排序的时间复杂度是O(nlogn)
- 堆排序中建堆过程的时间复杂度是O(n)
就排序/分类算法所用的辅助空间而言, 堆~ 、快速~和 归并~的关系
堆 O(1) < 快速分类 O(logn) < 归并分类 O(n)
对长度为n的线性表进行顺序查找,在最坏情况下所需要的比较次数为 n
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QjnX1j7Q-1626139004082)(C:\Users\toutou\AppData\Roaming\Typora\typora-user-images\image-20210704150605577.png)]
最简单的交换排序方法是 冒泡排序 ;
假设线性表的长度为n,则在最坏情况下,冒泡排序需要的比较次数为 n(n-1)/2 ;
在待排序的元素序列基本有序的前提下,效率最高的排序方法是 **冒泡排序 **;
希尔排序法属于 插入类排序 ;
堆排序法属于 选择类排序 ;
要求内存量最大的是 归并排序 ;
已知数据表A中每个元素距其最终位置不远,为节省时间,应采用 直接插入排序 ;
题目
-
判断链表是否存在环型链表问题
判断一个链表是否存在环,例如 N1->N2->N3->N4->N5->N2 就是一个有环的链表,环的开始结点是 N5
解法
设置两个指针 p1,p2
-
每次循环p1向前走一步,p2向前走两步
-
直到 p2 碰到NULL指针或者两个指针相等结束循环
如果两个指针相等则说明存在环
struct link { int data; link* next; }; bool IsLoop(link* head) { link *p1=head, *p2 = head; if(head ==NULL || head->next ==NULL) { return false; } do{ p1= p1->next; p2 = p2->next->next; } while(p2 && p2->next && p1!=p2); if(p1 == p2) return true; else return false; }
-
-
链表反转
如一个链表是:1->2->3->4->5 通过反转后成为 5->4->3->2->1
解法 1
遍历一遍链表,利用一个辅助指针,存储遍历过程中当前指针指向的下一个元素,然后将当前节点元素的指针反转后,利用已经存储的指针往后面继续遍历
struct linka { int data; linka* next; }; void reverse(linka* &head) { if(head == NULL) return; linka *pre, *cur, *ne; pre=head; cur=head->next; while(cur) { ne = cur->next; cur->next = pre; pre = cur; cur = ne; } head->next = NULL; head = pre; }
解法 2
利用递归的方法,基本思想是在反转当前节点之前先调用递归函数反转后续节点。不过这个方法有一个缺点,在反转后的最后一个结点会形成一个环,所以必须将函数的返回的节点的next域置为NULL。
linka* reverse(linka* p,linka* &head) { if(p == NULL || p->next == NULL) { head=p; return p; } else { linka* tmp = reverse(p->next,head); tmp->next = p; return p; } }
-
判断两个数组中是否存在相同的数字
给定两个排好序的数组,怎样高效判断这两个数组中是否存在相同的数字
解法 1 – O(nlogn)
任意挑选一个数组,遍历这个数组的所有元素,遍历过程中,在另一个数组中对第一个数组中的每个元素进行binary search
bool findcommon(int a[],int size1,int b[],int size2) { int i; for(i=0;i<size1;i++) { int start=0,end=size2-1,mid; while(start<=end) { mid=(start+end)/2; if(a[i]==b[mid]) return true; else if(a[i]<b[mid]) end=mid-1; else start=mid+1; } } return false; }
解法 2 – O(n)
首先设两个下标,分别初始化为两个数组的起始地址,依次向前推进。推进的规则是比较两个数组中的数字,小的那个数组的下标向前推进一步,直到任何一个数组的下标到达数组末尾时,如果这时还没碰到相同的数字,说明数组中没有相同的数字。
bool findcommon2(int a[], int size1, int b[], int size2) { int i=0,j=0; while(i<size1 && j<size2) { if(a[i]==b[j]) return true; if(a[i]>b[j]) j++; if(a[i]<b[j]) i++; } return false; }
-
最大子序列 问题
给定一整数序列A1,A2,… An (可能有负数),求 A1 ~ An 的一个子序列 Ai ~ Aj,使得 Ai ~ Aj 的和最大
如:整数序列- 2, 11, -4, 13, -5, 2, -5, -3, 12, -9 的最大子序列的和为21
解法 1 – O(n3)
穷举所有子序列,利用三重循环(使用 k 求在 i ~ j 范围内子序列的值),依次求出所有子序列的和然后取最大的那个
int max_sub(int a[],int size) { int i,j,k,sum=0,max=0; for(i=0;i<size;i++) { for(j=0;j<size;j++) { sum = 0; for(k=i;k<j;k++) sum += a[k]; if(sum > max) max = sum; } } return max; }
解法 2 – O(n2)
对穷举算法进行优化:不需要每次重新计算子序列的和,假设Sum(i, j)是A[i] … A[j]的和,那么Sum(i, j+1) = Sum(i, j) + A[j+1]。
int max_sub(int a[],int size) { int i,j,v,max=a[0]; for(i=0;i<size;i++) { v=0; for(j=i;j<size;j++) { v = v + a[j]; //Sum(i,j+1) = Sum(i,j) + A[j+1] if(v > max) max=v; } } return max; }
解法 3 – O(n)
动态规划
int max_sub2(int a[], int size){ int i,max=0,temp_sum=0; for(i=0;i<size;i++) { temp_sum += a[i]; if(temp_sum > max) max = temp_sum; else if(temp_sum < 0) temp_sum = 0; } return max;}
-
按单词反转字符串
如:Here is key 经过反转后变为 key is Here
如果只是简单的将所有字符串翻转的话,可以遍历字符串,将第一个字符和最后一个交换,第二个和倒数第二个交换,依次循环。
按照单词反转的话,可以在第一遍遍历的基础上,再遍历一遍字符串,对每一个单词再反转一次,这样每个单词又恢复了原来的顺序。
char* reverse_word(const char* str){ int len = strlen(str); char* restr = new char[len+1]; strcpy(restr,str); int i,j; for(i=0,j=len-1;i<j;i++,j--) { char temp = restr[i]; restr[i] = restr[j]; restr[j] = temp; } int k=0; while(k<len) { i=j=k; while( restr[j]!=' ' && restr[j]!='' ) j++; k=j+1; j--; for(;i<j;i++,j--) { char temp=restr[i]; restr[i]=restr[j]; restr[j]=temp; } } return restr;}
-
字符串反转
给定一个字符串及字符串的子串,将字符串反转,但保留子串的顺序不变
如:
输入:“This is fishsky’s Chinese site: http://www.fishsky.com.cn/cn” “fishsky”
输出:“nc/nc.moc.fishsky.www//:ptth :etis esenihC s’fishsky si sihT”
一般是先遍历第一个字符串,用stack把它反转,同时记录下子串出现的位置,然后再扫描一遍把记录下来的子串再用stack反转。
这里使用一遍扫描数组的方法。扫描中如果发现子串,就将子串倒过来压入堆栈。最后再将堆栈里的字符弹出,这样子串又恢复了原来的顺序。
#include <iostream>#include <cassert>#include <stack>using namespace std;//reverse the string 's1' except the substring 'token'.const char* reverse(const char* s1, const char* token){ assert(s1 && token); stack<char> stack1; const char* ptoken = token, *head = s1, *rear = s1; while (*head != '') { while(*head!= '' && *ptoken == *head) { ptoken++; head++; } if(*ptoken == '')//contain the token { const char* p; for(p=head-1;p>=rear;p--) stack1.push(*p); ptoken = token; rear = head; } else { stack1.push(*rear); head=++rear; ptoken = token; } } char * return_v = new char[strlen(s1)+1]; int i=0; while(!stack1.empty()) { return_v[i++] = stack1.top(); stack1.pop(); } return_v[i]=''; return return_v;}int main(int argc, char* argv[]){ cout<<"This is fishsky 's Chinese site: http://www.fishsky.com.cn/cn"; cout<<reverse("This is fishsky's Chinese site: http://www. fishsky.com.cn/cn", "fishsky"); return 0;}
-
如何判断一棵二叉树是否是平衡二叉树
判断一个二叉排序树是否是平衡二叉树
解法
根据平衡二叉树的定义,如果任意节点的左右子树的深度相差不超过1,那这棵树就是平衡二叉树。-
首先编写一个计算二叉树深度的函数,利用递归实现
template<typename T>static int Depth(BSTreeNode<T>* pbs){ if (pbs == NULL) return 0; else { int ld = Depth(pbs->left); int rd = Depth(pbs->right); return 1 + (ld>rd ? ld : rd); }}
-
利用递归判断左右子树的深度是否相差1来判断是否是平衡二叉树
template<typename T>static bool isBalance(BSTreeNode<T>* pbs){ if (pbs==NULL) return true; int dis = Depth(pbs->left) - Depth(pbs->right); if (dis>1 || dis<-1) return false; else return isBalance(pbs->left) && isBalance(pbs->right);}
-
abstract 的 methods 不能以 private、final 修饰;
局部变量前不能放置任何访问修饰符 (private,public,和protected),但final可以用来修饰局部变量
(final、abstract、strictfp 都是非访问修饰符,strictfp(精确浮点) 只能修饰 class 和 method 而非 variable)
下面该段代码是否有错,若是有错,错在哪里?
abstract class Name { private String name; public abstract boolean isStupidName(String name) {}}
错。abstract method必须以分号结尾,且不带花括号
计算机算法
在计算机中,算法是指 解题方案的准确而完整的描述
算法的四个基本特征
- 可行性 E
- 确定性 D
- 有穷性 F
- 输入项
- 输出项
算法一般都可以用哪几种控制结构组合而成
- 顺序
- 选择
- 循环
算法的时间复杂度
算法执行过程中所需要的基本运算次数
算法的空间复杂度
算法执行过程中所需要的存储空间
算法分析的目的
分析算法的效率以求改进
下列叙述
- 算法的执行效率与数据的存储结构无关 ( × )
- 算法的空间复杂度是指算法程序中指令(或语句)的条数 ( × )
- 算法的有穷性是指算法必须能在执行有限个步骤之后终止 ( √ )
- 算法的时间复杂度是指执行算法程序所需要的时间 ( × )
设有两个串p和q,求q在p中首次出现位置的运算称做 模式匹配
实现算法所需的存储单元多少和算法的工作量大小分别称为算法的空间复杂度和时间复杂度
数据处理是指对数据集合中的各元素以各种方式进行运算
- 插入、删除、查找、更改等运算
- 对数据元素进行分析
OS
进程、线程、协程的区别
进程是操作系统资源分配的基本单位,每启动一个进程都需要向操作系统索要资源;
而线程是依附在进程里面的,是 cpu 调度的最小单位, 通过线程去执行进程中代码;
多进程开发比单进程多线程开发稳定性要强,但是进程切换时资源开销大;
而协程是一种比线程更加轻量级的存在,它不是被操作系统内核所管理,而完全是由程序所控制,也就是在用户态执行,这样性能得到了很大的提升,比线程切换消耗资源更少。
进程间通信方式
管道
管道是用于连接通信进程的一个共享文件,就是在内存中开辟了一个固定大小的缓冲区,只能采用半双工通信,特点是写满才能读,读空才能写;
消息队列
消息队列在发送数据的时候,按照一个个独立单元也就是消息体进行发送,其中每个消息体规定大小块,同时发送方和接收方约定好消息类型或者正文的格式;
在管道中,消息的大小受限且只能承载无格式字节流的方式,而消息队列允许不同进程以消息队列的形式发送给任意的进程。
共享内存
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。由OS提供共享空间和同步互斥工具(如 P 加锁、V 解锁 操作),进程来实现互斥访问。
信号量通信
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。所以它主要是作为进程间以及同一进程内不同线程之间的同步手段。
套接字通信
套接字也是一种进程间通信机制,它可以用于不同主机之间的进程通信。套接字是全双工的,根据目的主机和目的端口号找到目的进程,在使用TCP运输层协议的时候,还会加上源主机和源端口号来唯一标识一个套接字,发送主机可通过多路复用从不同套接字收集数据块并为每个数据块封装上首部信息生成报文段,将报文段传递到网络层,接收主机可使用多路分解将从网络层接收到的报文通过报文段首部字段定向到目的套接字并交付。
CPU调度算法
FCFS
按进程到达的先后顺序服务,非抢占式算法,优点是实现简单而且公平,缺点是长作业后的短作业的带权周转时间(作业的周转时间/作业实际运行的时间)很大,对长作业有利,对短作业不利。不会导致饥饿。
SJF
要求服务时间最短的进程优先,是非抢占式算法,它的抢占式版本是最短剩余时间优先算法,优点是带权周转时间最短,缺点是对短作业有利,对长作业不利,可能导致饥饿。
优先级调度算法
优先服务优先级高的进程,优先级相同时按FCFS服务,也分抢占式和非抢占式,抢占式即当一个进程正在运行,另一个优先级更高的进程到来的时候后来的进程会直接抢占CPU开始运行。会导致饥饿。
时间片轮转调度 RoundRobin
以固定时间片大小循环为就绪队列中每个进程提供服务,当时间片很大的时候会退化为FCFS算法,是一种抢占式算法,不会饥饿。
多级反馈队列调度算法
会有N个队列,每个队列对于CPU的优先级不同,对于优先级最低的队列来说,遵循时间片轮转算法,对于其他队列,遵循FCFS算法,对每个进程分配一定的时间片,如果时间片运行完成时进程未结束,就会进入下一优先级队列的末尾。这样既可以让高优先级的作业得到响应又可以让短作业迅速完成。
死锁的发生和预防、避免
发生死锁有四个必要条件,互斥、占有并等待、非抢占、循环等待,四个条件并不完全独立。一组进程处于死锁状态就是说组内的每个进程都在阻塞等待一个只能由组内另一个进程产生的事件。发生死锁的原因由系统资源不足、进程运行推进的顺序不合适 (对资源申请、释放的顺序不合理)、资源分配不当。
预防死锁就是要破坏死锁产生的四个必要条件中的一个或者几个,
-
破坏互斥条件就是把只能互斥使用的资源改为允许共享使用,但为了系统安全,一般不能破坏这个条件。
-
破坏占有并等待条件可以采用静态分配法,也就是进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让其投入运行。还有一种方法是允许进程在没有资源时才可申请资源,在它申请其它更多的资源时,必须释放其现已分配的资源。
-
破坏非抢占可以通过考虑各进程的优先级,当某个优先级高的进程需要的资源被其他进程所占有时,可由 OS 协助将想要的资源强行剥夺过来,但是这样实现复杂,而且释放已获得的资源可能造成前一阶段工作的失效 ,系统开销也大。
-
破坏循环等待条件可以采用顺序资源分配法,首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(即编号相同的资源)一次申请完。但是这样编程很麻烦,而且进程实际使用资源的顺序可能和编号递增顺序不一致,导致系统资源浪费。
死锁避免一般采用银行家算法,它的核心思想是在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求。
页面置换算法
当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间,而用来选择淘汰哪一页的规则叫做页面置换算法
-
理想置换算法 OPT
从主存中移出永远不再需要的页面;如无这样的页面存在,则选择最长时间不需要访问的页面。于所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。
-
先进先出置换算法 FIFO
是最简单的页面置换算法。这种算法的基本思想是:当需要淘汰一个页面时,总是选择驻留主存时间最长的页面进行淘汰,即先进入主存的页面先淘汰。其理由是:最早调入主存的页面不再被使用的可能性最大。
可能出现Belady 异常 – 当所分配的物理块数增大而页故障数不减反增的异常现象
-
最近最久未使用(LRU)算法
利用局部性原理,根据一个作业在执行过程中过去的页面访问历史来推测未来的行为。它认为过去一段时间里不曾被访问过的页面,在最近的将来可能也不会再被访问。所以,这种算法的实质是:当需要淘汰一个页面时,总是选择在最近一段时间内最久不用的页面予以淘汰。
-
CLOCK(二次机会算法) 和 改进的 CLOCK算法
简单的CLOCK算法是给每一帧关联一个附加位,称为使用位。当某一页首次装入主存时,该帧的使用位设置为1;当该页随后再被访问到时,它的使用位也被置为1。对于页替换算法,用于替换的候选帧集合看做一个循环缓冲区,并且有一个指针与之相关联。当某一页被替换时,该指针被设置成指向缓冲区中的下一帧。当需要替换一页时,操作系统扫描缓冲区,以查找使用位被置为0的一帧。每当遇到一个使用位为1的帧时,操作系统就将该位重新置为0;如果在这个过程开始时,缓冲区中所有帧的使用位均为0,则选择遇到的第一个帧替换;如果所有帧的使用位均为1,则指针在缓冲区中完整地循环一周,把所有使用位都置为0,并且停留在最初的位置上,替换该帧中的页。该算法循环地检查各页面的情况,也称为最近未用(Not Recently Used, NRU)算法。
在使用位的基础上再增加一个修改位,则得到改进型的CLOCK置换算法。这样,每一帧都处于以下四种情况之一:
- 最近未被访问,也未被修改(u=0, m=0)。
- 最近被访问,但未被修改(u=1, m=0)。
- 最近未被访问,但被修改(u=0, m=1)。
- 最近被访问,被修改(u=1, m=1)。
算法执行如下操作步骤:
- 从指针所指示的当前位置开始,扫描循环队列,寻找最近未被访问,也未被修改的页面,将所遇到的第一个页面作为所选中的淘汰页。在第一次扫描期间不改变访问位。2. 如果第一步失败,即查找一周后未遇到第一类页面,则开始第二轮扫描,寻找第二类页面,将所遇到的第一个这类页面作为淘汰页。在第二轮扫描期间,将所有扫描过的页面的访问位都置0。3. 如果第二步也失败,亦即未找到第二类页面,则将指针返回到开始位置,并将所有访问位复位为0,返回第一步。
磁盘调度算法
-
先来先服务
根据进程请求访问磁盘的先后顺序进行调度;
-
最短寻道时间优先
选择调度处理的磁道是与当前磁头所在磁道距离最近的磁道,以使每次的寻找时间最短;
-
SCAN 和 C-SCAN;
-
LOOK 和 C-LOOK;
内存管理中的连续分配和非连续分配
连续分配
-
单一连续分配
内存中只有一道程序
内部碎片
-
固定分区分配
将内存空间划分为若干区域,每个分区装入一道作业,又可分为分区大小相等和分区大小不等两种。
内部碎片
-
动态分区分配
外部碎片,紧凑技术解决,但时间代价高
- 首次适应
- 最佳适应
- 最坏适应 / 最大适应
- 邻近适应 / 循环首次适应
非连续分配
-
分页
将内存空间分为一个个大小相等的分区,每个分区就是一个 “页框”,将用户进程的地址空间也分为与页框大小相等的一个个区域,称为 “页” ,OS 以页框为单位为各个进程分配内存空间。
- 更小的页
- 内部碎片少
- 页表项开销大
- 更大的页
- 内部碎片多
- I/O 效率高
- 更小的页
-
分段
-
段页式
分页和分段的区别
页是信息的物理单位,分页是为实现离散分配方式,减少外部碎片,提高内存的利用率。它出于系统管理的需要而不是用户需要。段是信息的逻辑单位,是为了更好地满足用户的需要。这也决定了分页有内部碎片无外部碎片,分段有外部碎片无内部碎片。
页的大小固定而且由系统决定,由系统把逻辑地址划分为页号和页内地址两部分,段的长度不固定,决定于用户所编写的程序。
页的地址空间是一维的,而段地址空间是二维的,程序员在标识一个地址时,既需要给出段名,又需给出段内地址。
计网
TCP
TCP和UDP的区别
TCP面向连接,提供可靠传输服务,进行数据传输之前要进行三次握手建立连接,数据传输完成之后释放连接,而UDP无连接,提供尽力而为的服务。
相同点:都是运输层协议
不同点:
TCP是面向连接的、点对点的、 面向字节流的、 提供可靠交付的、全双工的
UDP是无连接的、支持一对一、一对多、多对多的通信,面向报文的,尽最大努力交付的,半双工的,具有较好的实时性。
http和tcp的区别
TCP协议是传输层协议,主要解决数据在网络中的可靠传输问题;
http协议是应用层协议,建立在tcp协议之上。
TCP保证可靠的机制
检验和、确认应答机制(ACK)、序号机制、超时重传机制、连接管理机制,还有流量控制、拥塞控制。
序列号的作用:
a、保证可靠性(当接收到的数据总少了某个序号的数据时,能马上知道)
b、保证数据的按序到达
c、提高效率,可实现多次发送,一次确认
d、去除重复数据
确认应答机制(ACK):TCP通过确认应答机制实现可靠的数据传输。在TCP的首部中有一个标志位ACK,此标志位表示确认号是否有效。接收方对于按序到达的数据会进行确认,当标志位ACK=1时确认首部的确认字段有效。进行确认时,确认字段值表示这个值之前的数据都已经按序到达了。而发送方如果收到了已发送的数据的确认报文,则继续传输下一部分数据;而如果等待了一定时间还没有收到确认报文就会启动重传机制。
超时重传:当报文发出后在一定的时间内未收到接收方的确认,发送方就会进行重传。
如何让 UDP 实现可靠
应用程序自己实现。
三次握手、四次挥手
-
三次握手
首先Client端发送连接请求报文,Server段接受连接后回复ACK报文,并为这次连接分配资源。Client端接收到ACK报文后也向Server段发生ACK报文,并分配资源,这样TCP连接就建立了。
第一次握手是为了让服务器知道自己接收正常,客户机发送正常,
第二次握手是为了让客户机知道自己发送正常,服务器发送、接收正常,
第三次握手是为了让服务器知道自己发送正常。
-
四次挥手
客户机、服务器都可以选择中断连接,
第一次挥手是主动关闭方发起中断连接请求也就是 FIN 报文,说明自己没有数据要发送了,
第二次挥手是被动关闭方接收到之后发送 ACK 确认报文,表明自己已经收到了主动关闭方关闭连接的请求,但此时自己还没准备好,可能还有数据要传输,让主动关闭方继续等待自己的消息,
第三次挥手是被动关闭方数据传送完成后发送 FIN 报文,告诉主动关闭方自己的数据已经发送完成,可以关闭连接了,
第四次挥手是主动关闭方收到 FIN 报文后发送 ACK 确认报文,表明自己已经知道可以关闭连接了,这时候主动关闭方会进入TIME_WAIT状态,如果被动关闭方没有收到 ACK 则会重传。被动关闭方若收到ACK就断开连接。主动关闭方等待了一个2MSL后如果没有收到任何报文,则证明被动关闭方已正常关闭,主动关闭方也关闭连接。
timewait 和 closewait 状态
timewait 是 主动关闭方接收到第三次挥手的 FIN 报文后进入的状态,是为了保证 被动关闭方能接收到自己发送的 ACK 报文;
closewait 是 被动关闭方接收到第一次挥手的 FIN 报文后进入的状态,此时被动关闭方可能还有数据传输。
HTTP
HTTP是什么?作用?
HTTP是应用层的超文本传输协议,是一个简单的请求-响应协议,运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。
它可以提供客户端到Web服务器之间的传输服务。
状态码
状态码是用来表示HTTP响应状态的3位数字代码,常见的有
- 200 - 请求成功
- 301 - 资源(网页等)被永久转移到其它URL
- 304 - 使用条件get的时候,说明文件未被修改过,可直接使用Web缓存中文件响应
- 404 - 请求的资源(网页等)不存在
- 500 - 内部服务器错误
请求、响应报文结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yhcBfp17-1626139004082)(C:\Users\toutou\AppData\Roaming\Typora\typora-user-images\image-20210705235824707.png)]
https加密过程
HTTPS 全称: Hypertext Transfer Protocol Secure,超文本传输安全协议
-
HTTP: 直接通过明文在浏览器和服务器之间传递信息。
-
HTTPS: 采用 对称加密 和 非对称加密 结合的方式来保护浏览器和服务端之间的通信安全。
对称加密算法加密数据 + 非对称加密算法交换密钥 + 数字证书验证身份 = 安全
HTTPS 由 HTTP 和 SSL(安全套接字层)/TLS(安全传输层协议) 组成,也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过TLS进行加密,所以传输的数据都是加密后的数据。
传统的HTTP协议通信是传统的HTTP报文是直接将报文信息传输到TCP然后TCP再通过TCP套接字发送给目的主机上。
HTTPS协议通信是HTTPS是HTTP报文直接将报文信息传输给SSL套接字进行加密,SSL加密后将加密后的报文发送给TCP套接字,然后TCP套接字再将加密后的报文发送给目的主机,目的主机将通过TCP套接字获取加密后的报文给SSL套接字,SSL解密后交给对应进程。
HTTPS加密请求(一次握手)过程
- 首先,客户端发起握手请求,以明文传输请求信息,包含版本信息,加密-套件候选列表,压缩算法候选列表,随机数,扩展字段等信息(
这个没什么好说的,就是用户在浏览器里输入一个HTTPS网址,然后连接到服务端的443端口。
) - 服务端的配置,采用HTTPS协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面。
这套证书其实就是一对公钥和私钥。
如果对公钥不太理解,可以想象成一把钥匙和一个锁头,只是世界上只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁起来的东西。 - 服务端返回协商的信息结果,包括选择使用的协议版本 version,选择的加密套件 cipher suite,选择的压缩算法 compression method、随机数 random_S 以及证书。(
这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。
) - 客户端验证证书的合法性,包括可信性,是否吊销,过期时间和域名。(
这部分工作是由客户端的SSL/TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警示框,提示证书存在的问题。如果证书没有问题,那么就生成一个随机值。然后用证书(也就是公钥)对这个随机值进行加密。就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,不然看不到被锁住的内容。
) - 客户端使用公匙对对称密匙加密,发送给服务端。(
这部分传送的是用证书加密后的随机值,目的是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。
) - 服务器用私钥解密,拿到对称加密的密匙。(
服务端用私钥解密后,得到了客户端传过来的随机值,然后把内容通过该随机值进行对称加密,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。
) - 传输加密后的信息,这部分信息就是服务端用私钥加密后的信息,可以在客户端用随机值解密还原。
- 客户端解密信息,客户端用之前生产的私钥解密服务端传过来的信息,于是获取了解密后的内容。整个过程第三方即使监听到了数据,也束手无策。
cookie、session 等及其一些相关的攻击
HTTP 是一种无状态的协议,cookie机制采用的是在客户端保持状态的方案,session 是在服务端保持状态的协议。
cookie 本质上是存储在客户端本地硬盘的信息,有持久性和临时性两种,浏览器会根据domain【域名】来向服务器发送对应的cookie;
session 本质上是一个临时cookie,保存的信息是sessionId,服务器内存会保存有该ID记录的session对象信息。
Cookie欺骗
1.利用抓包截获 cookie信息
2.利用 Cookies插件 , 人为在domain下面添加cookie信息
3.达到信息认证的效果
Web Storage
由sessionStorage与localStorage组成 。 sessionStorage用于本地存储一个会话中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此sessionStorage不是一种持久化的本地存储,仅仅是会话级别的存储。 localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。
-
cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而且cookie数据有路径(path)的概念,可以限制cookie只属于某个路径下
-
存储大小限制不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据,如会话标识。webStorage虽然也有存储大小的限制,但是比cookie大得多,可以达到5M或更大
-
数据的有效期不同
- sessionStorage:仅在当前的浏览器窗口关闭有效;
- localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;
- cookie:只在设置的cookie过期时间之前一直有效,即使窗口和浏览器关闭
-
作用域不同
-
sessionStorage:不在不同的浏览器窗口中共享,即使是同一个页面;
-
localStorage:在所有同源窗口都是共享的;
同源 – 协议、域名、端口号 相同
-
cookie:也是在所有同源窗口中共享的
-
数据库
数据库组件
一条查询SQL语句的执行过程
一条更新SQL语句的执行过程
事务相关
事务的四个特性
-
A 原子性
事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
-
C 一致性
事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
举例来说,假设用户A和用户B两者的钱加起来一共是1000,那么不管A和B之间如何转账、转几次账,事务结束后两个用户的钱相加起来应该还得是1000,这就是事务的一致性。
-
I 隔离性
当多个用户并发访问数据库时,比如同时操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。MySQL中实现了四种隔离级别。
-
D 持久性
一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失已提交事务的操作。
脏读、不可重复读、幻读
-
脏读(Dirty Read)
A事务读取B事务尚未提交的数据并在此基础上操作,而B事务执行回滚,那么A读取到的数据就是脏数据;
解决办法
如果在第一个事务提交前,任何其他事务不可读取其修改过的值,则可以避免该问题
-
不可重复读(Non-repeatable Reads)
一个事务对同一行数据重复读取两次,但是却得到了不同的结果。事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值;解决办法
如果只有在修改事务完全提交之后才可以读取数据,则可以避免该问题
-
幻读
幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的行。
事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读。
也就是说在一个事务中使用相同的 SQL 两次读取,第二次读取到了其他事务新插入的行。
解决办法
如果在操作事务完成数据处理之前,任何其他事务都不可以添加新数据,则可避免该问题
不可重复读和幻读的区别
不可重复读重点在于update和delete,而幻读的重点在于insert。
四种隔离级别
-
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别会出现读取未提交的数据,也被称之为脏读(Dirty Read)。
-
Read Committed(读取提交内容)
一个事务只能看见已经提交事务所做的改变。解决了脏读,但会出现不可重复读,因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
-
Repeatable Read(可重读)
确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。理论上这会导致幻读 (Phantom Read)。
-
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
悲观锁和乐观锁
-
悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。
-
乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。
索引
聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。具体细节依赖于其实现方式。MySQL数据库中innodb存储引擎,B+树索引可以分为聚簇索引和辅助索引。这两种索引内部都是B+树,聚集索引的叶子节点存放着一整行的数据。
Innobd中的主键索引是一种聚簇索引,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引。
聚簇索引
聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。这个特性决定了索引组织表中数据也是索引的一部分,每张表只能拥有一个聚簇索引。
优点:
-
数据访问更快,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快
-
聚簇索引对于主键的排序查找和范围查找速度非常快
缺点:
- 插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键
- 更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新。
- 二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。
辅助索引(非聚簇索引)
在聚簇索引之上创建的索引就是辅助索引,辅助索引访问数据总是需要二次查找。辅助索引叶子节点存储的不再是行的物理位置,而是主键值。通过辅助索引首先找到的是主键值,再通过主键值找到数据行的数据页,再通过数据页中的Page Directory找到数据行。
Innodb辅助索引的叶子节点并不包含行记录的全部数据,叶子节点除了包含键值外,还包含了相应行数据的聚簇索引键。
辅助索引的存在不影响数据在聚簇索引中的组织,所以一张表可以有多个辅助索引。在innodb中有时也称辅助索引为二级索引。
B 树 和 B+ 树
B 树 是一种多路搜索树(并不是二叉的):
-
定义任意非叶子结点最多只有M个儿子;且M>2;
-
根结点的儿子数为[2, M];
-
除根结点以外的非叶子结点的儿子数为[M/2, M];
-
每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
-
非叶子结点的关键字个数=指向儿子的指针个数-1;
-
非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
-
非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;
-
所有叶子结点位于同一层;
B+ 树 是 B-树 的变体,也是一种多路搜索树:
-
其定义基本与B-树同,除了:
-
非叶子结点的子树指针与关键字个数相同;
-
非叶子结点的子树指针P[i],指向关键字值属于**[ K[i] , K[i+1] )**的子树(B-树是开区间);
-
为所有叶子结点增加一个链指针;
-
所有关键字都在叶子结点出现;
B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中,B-树可以在非叶子结点命中,其性能也等价于在关键字全集做一次二分查找;
B+的特性:
-
所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字是有序的,不可能在非叶子结点命中;
-
非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;
-
更适合文件索引系统,因为B+树的节点不存储data,这样一个节点就可以存储更多的key,可以使得树更矮,IO操作次数更少。
数据库保护
-
安全性控制
保护数据库,以防止因非法使用数据库,造成的数据泄漏,更改或破坏
-
完整性控制
保护数据库中的数据的正确性,有效性,相容性
-
并发性控制
防止多个用户同时存取同一数据,造成的数据不一致
-
数据恢复
将数据库从错误的状态恢复到某一已知的正确的状态
大数据
大数据的特点
-
大
数据的采集、计算、存储量都非常庞大
-
多
种类和来源多样化
-
种类
结构化、半结构化、非结构化
-
来源
音频、视频、图片等等
-
-
值
大数据价值密度相对较低,信息海量,但存在大量不相关信息,需要对未来趋势与模式作可预测分析,利用机器学习、人工智能等进行深度复杂分析。而如何通过强大的机器算法更迅速地完成数据的价值提炼,是大数据时代亟待解决的难题。
-
快
数据增长速度快、处理速度快、获取数据速度快
-
信
数据的准确性和可信赖度 即 数据的质量
大数据应用场景
-
电商
通过对用户的浏览行为、点击行为等进行大数据采集和分析,挖掘用户的喜好
-
传媒
通过对受众人群大数据分析,结合对应算法,根据受众的爱好交互推荐
-
金融
通过对个人的信用评估、风险承担能力评估,推荐相应投资理财产品
-
交通
- 通过对车流量等海量数据的收集,估算,预测该路段一定时间内的车流量情况,给用户提供便利,合理进行道路规划
- 利用大数据来实现即时信号灯调度,提高已有线路通行能力
-
电信
通过对用户当前的行为习惯、偏好,节假日的相应数据变化,调节自身业务结构,做到按需分配
-
安防
通过人脸识别,一一匹配,存储用户数据,结合人工智能,分析及甄别用户行为,预防犯罪行为发生
-
医疗
通过对海量病例大数据的存储,匹配、检索、结合用户的饮食、行为等习惯,搭建智慧医疗体系
-
物流
通过大数据解决方案得出司机工作表现的若干预测模型,解决了事故发生率和人员流动等人事部门的问题
大数据业务分析基本步骤
-
明确分析目的和思路
明确数据分析目的以及确定分析思路,是确保数据分析过程有效进行的先决条件,它可以为数据的收集、处理及分析提供清晰的指引方向。
-
数据收集
数据收集是按照确定的数据分析框架,收集相关数据的过程,它为数据分析提供了素材和依据。这里所说的数据包括第一手数据与第二手数据,第一手数据主要指可直接获取的数据,第二手数据主要指经过加工整理后得到的数据。
一般数据来源:
- 数据库
- 公开出版物
- 互联网
- 市场调查
-
数据处理
数据处理是指对收集到的数据进行加工整理,形成适合数据分析的样式,它是数据分析前必不可少的阶段。数据处理的基本目的是从大量的、杂乱无章、难以理解的数据中,抽取并推导出对解决问题有价值、有意义的数据。
- 数据清洗
- 数据转化
- 数据提取
- 数据计算
通过数据处理,将收集到的原始数据转换为可以分析的形式,并且保证数据的一致性和有效性。
-
数据分析
数据分析是指用适当的分析方法及工具,对处理过的数据进行分析,提取有价值的信息,形成有效结论的过程。由于数据分析多是通过软件来完成的,这就要求数据分析师不仅要掌握各种数据分析方法,还要熟悉数据分析软件的操作。
数据挖掘其实是一种高级的数据分析方法,就是从大量的数据中挖掘出有用的信息,它是根据用户的特定要求,从浩如烟海的数据中找出所需的信息,以满足用户的特定需求。
数据挖掘侧重解决四类数据分析问题:分类、聚类、关联和预测,重点在寻找模式和规律。
数据分析与数据挖掘的本质是一样的,都是从数据里面发现关于业务的知识。
-
数据展现
一般情况下,数据是通过表格和图形的方式来呈现的
-
报告撰写
数据分析报告其实是对整个数据分析过程的一个总结与呈现。通过报告,把数据分析的起因、过程、结果及建议完整地呈现出来,供决策者参考。
- 好的分析框架,图文并茂,层次明晰
- 明确的结论
- 建议 / 解决方案
分布式技术
-
为什么需要分布式
-
计算问题
无论是线程、进程,本质上,目的都是为了计算的并行化,解决的是算的慢的问题。而如果计算量足够大,就算榨干了机器的计算能力,也算不过来,那就要多搞几台机器。所以就从多线程/进程的计算并行化,进化到计算的分布式化(当然,分布式一定程度上也是并行化)。
-
存储问题
如果处理的数据有10T,而你手上的机器只有500G 的硬盘,怎么办?
- 纵向扩展,搞一台几十T硬盘的机器;
- 横向扩展,多搞几台机器,分散着放;
前者很容易到瓶颈,毕竟数据无限,而一台机器的容量有限,所以在大数据量的情况下,只能选后者。把数据分散到多台机器,本质上解决的是存不下的问题。
而且计算分布式化后,如果所有程序都去同一台机器读数据,效率必然会受到单台机器性能的拖累,比如磁盘 IO、网络带宽等,所以数据存储也需要分布式化。
-
-
分布式系统概述
分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。
分布式意味着可以采用更多的普通计算机(相对于昂贵的大型机)组成分布式集群对外提供服务。计算机越多,CPU、内存、存储资源等也就越多,能够处理的并发访问量也就越大。各个主机之间通信和协调主要通过网络进行,所以,分布式系统中的计算机在空间上几乎没有任何限制。
-
分布式实现方案
-
集群 Cluster
-
负载均衡 (为避免单点故障,也需要集群)
拿到请求,分发请求
-
弹性
云计算,在云端可以轻松的创建、删除虚拟的服务器,可以轻松的随着用户的请求动态地增减服务器
-
失效转移
记录用户状态
- 把状态信息在集群的各个服务器之间复制,让集群的各个服务器达成一致
- 把几种状态信息存储在一个地方,让集群服务器的各个服务器都能访问到
-