面试复习笔记
- 一、数据结构
- 1. 杂谈
- 2. 哈夫曼树
- 3. 哈希冲突
- 4. 图的存储方式
- 5. B-树
- 6. B 树
- 7. 堆
- 二、计算机网络
- 1. 应用层
- 2. 传输层
- 3. 网络层
- 4. 数据链路层
- 5. 物理层
- 三、操作系统
- 四、数据库
- 1. 索引
- 2. B树与B 树
- 五、组成原理
- 六、Linux
- 七、Java
- 1. 集合类源码分析
- 2. 容器
- 3. 多线程
- 4. 常见知识点罗列
- 八、JVM
- 1. Java优点
- 2. 运行时数据区域
- 3. 对象
- 4. 垃圾收集器与内存分配策略
- 九、项目经验
- 0. 信息安全实验
- 1. SSO CAS
- 2. Nginx
- 3. Layui
- 4. Ajax
- 5. Spring MVC
一、数据结构
1. 杂谈
- 循环队列的顺序表中,为什么要空一个位置? 这是为了用来区分队空与队满的情况。如果不空一个位置,则判断队空和队满的条件是一样的。 循环队列是为了解决“假溢出”现象。
- 有哪些查找算法 基于Hash表的查找 蛮力查找(顺序查找) 基于有序表的二分查找 字符串的查找 基于树的查找
2. 哈夫曼树
- 定义 给定 n 个权值作为 n 个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树 。
- 特点 ① 权值越大的结点,距离根节点越近; ② 树中没有度为一的结点。
- 构造方法
void HuffmanTree(element[] huffTree. int[] weight. int n){
//初始化结构
for(int i = 0; i < 2*n-1; i ){ //n个叶子节点的哈夫曼树,最多有2*n-1个节点,全在底层
huffTree[i].parent = -1;
huffTree[i].lchild = -1;
huffTree[i].rchild = -1;
}
//赋初始权值
for(int i = 0; i < n; i ){ //n个值
huffTree[i].weight = weight[i];
}
int min1, min2;
for(int k = n; k < 2*n-1; k ){
select(huffTree, min1, min2); //从huffman tree中找到最小的两个元素
huffTree[k].weight = huffTree[min1].weight huffTree[min2].weight;
huffTree[min1].parent = k;
huffTree[min2].parent = k;
huffTree[k].lchild = min1;
huffTree[k].rchild = min2;
}
}
- 存储结构
3. 哈希冲突
- 定义 根据关键码值(Key value)而直接进行访问的数据结构。根据给定的关键字来计算出关键字在表中的地址,以加快查找的速度。
- 冲突 指的是多个关键字映射同一个地址的情况。
- 解决冲突 (1)开放定址法 线性探测再散列、二次探测再散列、伪随机探测再散列(事先设置一个伪随机序列) (2)再哈希法 (3)链地址法(根据装填因子来更新表长) (4)建立公共溢出区
- 常见的哈希函数 直接定址法(直接取关键字),数字分析法(取若干位),平方取中法,分段叠加,除留余数法,伪随机数法(构造哈希函数)。
4. 图的存储方式
① 邻接矩阵:是图的顺序存储结构,用两个数组分别存储数据元素(顶点)信息和数据元素之间的关系(边/弧)的信息。图的邻接矩阵表示是唯一的,无向图的邻接矩阵是对称的。
② 邻接表:是图的链式存储结构,由单链表的表头形成的顶点表和单链表其余结点所形成的边表两部分组成。
③ 十字链表:有向图的另一种链式存储结构。
④ 邻接多重表:无向图的链式存储结构。
5. B-树
- 一棵m阶的B-树的定义(递归): 或者是一棵空树,或者是满足下列特性的m叉树; (1)树中每个结点至多有m棵子树; (2)若根结点不是叶子结点,则至少有两棵子树; (3)除根之外的所有非终端结点至少有m/2向上取整棵子树,至多有m棵子树; (4)所有的非终端结点中包含下列信息数据(n, P0, K1, P1, K2, P2,…, Kn , Pn):关键字个数、关键字i(记录 地址)、指向子树根节点的指针i; (5)所有叶子结点都出现在同一层,叶子结点可看作Null节点 。
注意:B树即二叉搜索树,B-树是多路搜索树。
6. B 树
B 树不是树!是B-树的一个变型树。
- 区别 有n棵子树的结点中含有n个关键字; 所有叶子结点中包含了全部关键字的信息及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接; 所有非终端结点可以看成是索引部分,结点中仅含有其子树中的最大或最小关键字。
- 结构 B-树索引: (索引块)
- B-树的指针是稀疏索引,指向块号;
- B-树仅起路标的作用,B-树中的索引记录项可能实际并不存在;
顺序集合:(数据块)
代码语言:txt复制- 存放真实的记录索引;
- 一般组织成稠密索引,指针指向记录地址。查找方式
(横向)顺序查找:从顺序集链头开始;
(纵向)随即查找:从B-树的根开始。
7. 堆
堆是一种数据结构,可以把堆看成一个完全二叉树,并且这个完全二叉树满足:任何一个非叶节点的值都不大于(或不小于)其左右子树的结点的值。
应用:堆排序
注意:被问及时,应明确是数据结构里的堆,还是操作系统里的堆。
二、计算机网络
1. 应用层
- 目的 对应用程序的通信提供服务。
- 单位:报文
2. 传输层
- 作用 提供主机间进程和进程之间的逻辑通信。
- 单位 报文段(分割),存放源端口和目的端口。
- 显示网页流程 浏览器分析URL、浏览器向DNS请求解析IP地址、DNS解析出IP地址、浏览器与服务器建立TCP连接、浏览器发出取文件的HTTP请求报文、服务器响应,文档依附在HTTP响应报文中、释放TCP连接、浏览器渲染并显示页面。
3. 网络层
- 作用 把分组从源端传入目的端,为分组交换网上的不同主机提供通信服务。
- 单位 IP数据报、分组 (首部加源、目的IP地址/分割)
- 设备:路由器(路由选择、分组转发)
4. 数据链路层
- 数据链路 网络中两个结点(主机或路由器)之间的逻辑通道,等于链路(两个结点之间的物理通道,有线/无线)加上实现控制数据的传输协议的硬件和软件。
- 作用 负责通过一条链路从一个结点向另一个物理链路直接相连的相邻结点传送数据报。
- 单位 帧(首部加源、目的MAC地址,尾部加FCS)
- 设备 网桥(有目的的转发)、交换机(switch,多接口网桥)
5. 物理层
- 作用 解决如何在连接各种计算机的传输媒体上传输数据比特流。
- 单位 比特流(编码为数字信号/调制为模拟信号)
- 设备 中继器/转发器、集线器/Hub(CSMA/CD),用于对接收的信号进行再生整形放大。
自用路由器一般是多个网络设备的集合体,非传统意义上的路由器。
- 信息安全 我的参考博客
三、操作系统
- 什么是进程(Process)和线程(Thread)?有何区别?
- 进程 定义:进程是对运行时程序的封装,是系统资源调度和分配的基本单位,实现了操作系统的并发。 基本特征:
- 动态性:动态产生、动态消亡;
- 并发性:同其它进程并发执行;
- 独立性:独立运行的活动实体;
- 异步性:进程间相互制约,按各自独立的不可预知的速度推进。
一个程序至少有一个进程,一个进程至少有一个线程,线程依赖于进程而存在;进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。
- Windows下的内存是如何管理的? Windows提供了3种方法来进行内存管理: 虚拟内存,最适合用来管理大型对象或者结构数组; 内存映射文件,最适合用来管理大型数据流(通常来自文件)以及在单个计算机上运行多个进程之间共享数据; 内存堆栈,最适合用来管理大量的小对象。
- 描述实时系统的基本特性 在特定时间内完成特定的任务,实时性与可靠性。 指当外界事件或数据产生时,能够接受并以足够快的速度予以处理,其处理的结果又能在规定的时间之内来控制生产过程或对处理系统做出快速响应,调度一切可利用的资源完成实时任务,并控制所有实时任务协调一致运行的操作系统。
- 中断和轮询的特点。
- 轮询/程控输入输出 由CPU定时发出询问,依序询问每一个周边设备是否需要其服务,有即给予服务,服务结束后再问下一个周边,接着不断周而复始。 特点:轮询要比I/O设备的速度要快得多、I/O设备的数量有限、轮询占据CPU相当一部分处理时间。效率低,等待时间很长,CPU利用率不高
- 程序中断/中断 指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。 特点:容易遗漏一些问题,CPU利用率高
- 什么是临界区?如何解决冲突?
- 定义 每个进程中访问临界资源(一次仅允许一个进程使用的资源)的那段程序称为临界区,每次只准许一个进程进入临界区,进入后不允许其他进程进入。
- 措施
- 如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入;
- 任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待;
- 进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区;
- 如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象。
- 说说分段和分页 段式存储管理是一种符合用户视角的内存分配管理方案。在段式存储管理中,将程序的地址空间划分为若干段(segment),如代码段,数据段,堆栈段;这样每个进程有一个二维地址空间,相互独立,互不干扰。段式管理的优点是:没有内碎片(因为段大小可变,改变段大小来消除内碎片)。但段换入换出时,会产生外碎片(比如4k的段换5k的段,会产生1k的外碎片) 页式存储管理方案是一种用户视角内存与物理内存相分离的内存分配管理方案。在页式存储管理中,将程序的逻辑地址划分为固定大小的页(page),而物理内存划分为同样大小的帧,程序加载时,可以将任意一页放入内存中任意一个帧,这些帧不必连续,从而实现了离散分离。页式存储管理的优点是:没有外碎片(因为页的大小固定),但会产生内碎片(一个页可能填充不满)。 两者的不同点: 目的不同:分页是由于系统管理的需要而不是用户的需要,它是信息的物理单位;分段的目的是为了能更好地满足用户的需要,它是信息的逻辑单位,它含有一组其意义相对完整的信息; 大小不同:页的大小固定且由系统决定,而段的长度却不固定,由其所完成的功能决定; 地址空间不同: 段向用户提供二维地址空间;页向用户提供的是一维地址空间; 信息共享:段是信息的逻辑单位,便于存储保护和信息的共享,页的保护和共享受到限制; 内存碎片:页式存储管理的优点是没有外碎片(因为页的大小固定),但会产生内碎片(一个页可能填充不满);而段式管理的优点是没有内碎片(因为段大小可变,改变段大小来消除内碎片)。但段换入换出时,会产生外碎片(比如4k的段换5k的段,会产生1k的外碎片)。
- 进程通信有哪些方式?
- 管道(pipe)及命名管道(named pipe): 管道(局域网)可用于具有亲缘关系的父子进程间(共性大)的通信;有名管道(外网)除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信,但是要指明发送方和接收方;
- 信号(signal): 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
- 消息队列: 消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息;
- 共享内存: 可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等;
- 信号量: 主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段;
- 套接字: 这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。
- 说出你所知道的保持进程同步的方法? 进程间同步的主要方法有内存屏障,互斥锁,信号量和锁,管程,消息,管道。
- makefile文件的作用是什么? 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。 makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C 的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
- CPU在上电后,进入操作系统的main()之前必须做什么? 加电后,会触发CPU的reset信号,导致CPU复位,然后CPU会跳到(arm下0x00000000,x86下0xfffffff0)执行指令。主要是做CPU初始化,确定CPU的工作模式,mmu初始化。建立页表段表,初始化中孤单控制器和中断向量表,初始化输入和输出,初始化nandflash,把OS的TEXT区加载到sdram,然后跳转到sdram的main()
- 什么是中断?中断时CPU做什么工作? 中断是指在计算机执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得CPU暂时中断当前正在执行的程序而转去执行相应的事件处理程序。待处理完毕后又返回原来被中断处继续执行或调度新的进程执行的过程。
- 存储过程是什么?有什么用?有什么优点? 存储过程(Stored Procedure)是一组为了完成特定功能的SQL 语句集,经编译后存储在数据库中。用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是SQL 语句和可选控制流语句的预编译集合,以一个名称存储并作为一个单元处理。存储过程存储在数据库内,可由应用程序通过一个调用执行,而且允许用户声明变量、有条件执行以及其它强大的编程功能。存储过程在创建时即在服务器上进行编译,所以执行起来比单个SQL语句快。 存储过程的优点: 存储过程只在创造时进行编译,以后每次执行存储过程都不需再重新编译,而一般SQL语句每执行一次就编译一次,所以使用存储过程可提高数据库执行速度; 当对数据库进行复杂操作时(如对多个表进行Update, Insert, Query, Delete时),可将此复杂操作用存储过程封装起来与数据库提供的事务处理结合一起使用; 存储过程可以重复使用,可减少数据库开发人员的工作量; 安全性高,可设定只有某此用户才具有对指定存储过程的使用权。 存储过程的缺点: 如果更改范围大到需要对输入存储过程的参数进行更改,或者要更改由其返回的数据,则您仍需要更新程序集中的代码以添加参数、更新 GetValue() 调用,等等,这时候估计比较繁琐了。 可移植性差。由于存储过程将应用程序绑定到 SQL Server,因此使用存储过程封装业务逻辑将限制应用程序的可移植性。
- 操作系统的内容分为几块吗?什么叫做虚拟内存?他和主存的关系如何?内存管理属于操作系统的内容吗? 操作系统的主要组成部分:进程和线程的管理,存储管理,设备管理,文件管理。 虚拟内存是一些系统页文件,存放在磁盘上,每个系统页文件大小也为4K,物理内存也被分页,每个页大小也为4K,这样虚拟页文件和物理内存页就可以对应,实际上虚拟内存就是用于物理内存的临时存放的磁盘空间。 页文件就是内存页,物理内存中每页叫物理页,磁盘上的页文件叫虚拟页,物理页 虚拟页就是系统所以使用的页文件的总和。 属于。
- 进程有哪几种状态? 就绪状态:进程已获得除处理机以外的所需资源,等待分配处理机资源; 运行状态:占用处理机资源运行,处于此状态的进程数小于等于CPU数; 阻塞状态: 进程等待某种条件,在条件满足之前无法执行;
- OS中如何实现物理地址到逻辑地址的转换? CPU要利用其段式内存管理单元,先将逻辑地址转换成一个线程地址,再利用其页式内存管理单元,转换为最终物理地址。
- 堆和栈的区别? 栈区,由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 堆:一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式是类似于链表。可能用到的关键字如下:new、malloc、delete、free等等。
- 线程是否具有相同的堆栈?dll是否有独立的堆栈? 每个线程有自己的堆栈。 DLL中有没有独立的堆栈,这个问题不好回答,或者说这个问题本身是否有问题。因为DLL中的代码是被某些线程所执行,只有线程拥有堆栈,如果DLL中的代码是EXE中的线程所调用,那么这个时候是不是说这个DLL没有自己独立的堆栈?如果DLL中的代码是由DLL自己创建的线程所执行,那么是不是说DLL有独立的堆栈? 以上讲的是堆栈,如果对于堆来说,每个DLL有自己的堆,所以如果是从DLL中动态分配的内存,最好是从DLL中删除,如果你从DLL中分配内存,然后在EXE中,或者另外一个DLL中删除,很有可能导致程序崩溃。
- 网络编程中设计并发服务器,使用“多进程”与“多线程”,请问有什么区别? 进程:子进程是父进程的复制品。子进程获得父进程数据空间、堆和栈的复制品。 线程:相对与进程而言,线程是一个更加接近与执行体的概念,它可以与同进程的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。 两者都可以提高程序的并发度,提高程序运行效率和响应时间。 线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。
- 解释一下分页式管理. 用户程序的地址空间被划分成若干固定大小的区域,称为“页”,相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配。
- 解释一下P操作与V操作 P就是请求资源,V就是释放资源
- 什么是缓冲区溢出?有什么危害?其原因是什么? 缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量溢的数据覆盖在合法数据上, 危害:在当前网络与分布式系统安全中,被广泛利用的50%以上都是缓冲区溢出,其中最著名的例子是1988年利用fingerd漏洞的蠕虫。而缓冲区溢出中,最为危险的是堆栈溢出,因为入侵者可以利用堆栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址,带来的危害一种是程序崩溃导致拒绝服务,另外一种就是跳转并且执行一段恶意代码,比如得到shell,然后为所欲为。通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其它指令,以达到攻击的目的。 造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数
- 什么是死锁?其条件是什么?怎样避免死锁? 在两个或者多个并发进程中,如果每个进程持有某种资源而又等待其它进程释放它或它们现在保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁。通俗的讲,就是两个或多个进程无限期的阻塞、相互等待的一种状态。 死锁产生的原因: 系统资源不足; 进程运行推进的顺序不合适; 资源分配不当。 产生死锁的必要条件: 互斥(mutual exclusion),一个资源每次只能被一个进程使用; 占有且等待(hold and wait)/请求和保持,一个进程必须占有至少一个资源,并等待另一个资源,而该资源为其他进程所占有; 不可抢占(no preemption),进程已获得的资源,在未使用完之前,不能强行剥夺; 环形等待(circular wait),若干进程之间形成一种首尾相接的循环等待资源关系。 死锁的处理基本策略和常用方法 解决死锁的基本方法主要有 预防死锁、避免死锁、检测死锁、解除死锁 、鸵鸟策略 等。 (1).死锁预防 死锁预防的基本思想是 只要确保死锁发生的四个必要条件中至少有一个不成立,就能预防死锁的发生,具体方法包括:
打破互斥条件:允许进程同时访问某些资源。但是,有些资源是不能被多个进程所共享的,这是由资源本身属性所决定的,因此,这种办法通常并无实用价值。
打破占有并等待条件:可以实行资源预先分配策略(进程在运行前一次性向系统申请它所需要的全部资源,若所需全部资源得不到满足,则不分配任何资源,此进程暂不运行;只有当系统能满足当前进程所需的全部资源时,才一次性将所申请资源全部分配给该线程)或者只允许进程在没有占用资源时才可以申请资源(一个进程可申请一些资源并使用它们,但是在当前进程申请更多资源之前,它必须全部释放当前所占有的资源)。但是这种策略也存在一些缺点:在很多情况下,无法预知一个进程执行前所需的全部资源,因为进程是动态执行的,不可预知的;同时,会降低资源利用率,导致降低了进程的并发性。
打破非抢占条件:允许进程强行从占有者哪里夺取某些资源。也就是说,但一个进程占有了一部分资源,在其申请新的资源且得不到满足时,它必须释放所有占有的资源以便让其它线程使用。这种预防死锁的方式实现起来困难,会降低系统性能。
打破循环等待条件:实行资源有序分配策略。对所有资源排序编号,所有进程对资源的请求必须严格按资源序号递增的顺序提出,即只有占用了小号资源才能申请大号资源,这样就不回产生环路,预防死锁的发生。
(2). 死锁避免的基本思想
死锁避免的基本思想是动态地检测资源分配状态,以确保循环等待条件不成立,从而确保系统处于安全状态。所谓安全状态是指:如果系统能按某个顺序为每个进程分配资源(不超过其最大值),那么系统状态是安全的,换句话说就是,如果存在一个安全序列,那么系统处于安全状态。资源分配图算法和银行家算法是两种经典的死锁避免的算法,其可以确保系统始终处于安全状态。其中,资源分配图算法应用场景为每种资源类型只有一个实例(申请边,分配边,需求边,不形成环才允许分配),而银行家算法应用于每种资源类型可以有多个实例的场景。
(3). 死锁解除
死锁解除的常用两种方法为进程终止和资源抢占。所谓进程终止是指简单地终止一个或多个进程以打破循环等待,包括两种方式:终止所有死锁进程和一次只终止一个进程直到取消死锁循环为止;所谓资源抢占是指从一个或多个死锁进程那里抢占一个或多个资源,此时必须考虑三个问题:
(I). 选择一个牺牲品
(II). 回滚:回滚到安全状态
(III). 饥饿(在代价因素中加上回滚次数,回滚的越多则越不可能继续被作为牺牲品,避免一个进程总是被回滚)
- 同步通讯效率为什么高于异步通信 因为同步通信用一个公共的时钟信号进行同步。
- 操作系统中进程调度策略有哪几种? FCFS(先来先服务,队列实现,非抢占的):先请求CPU的进程先分配到CPU SJF(最短作业优先调度算法):平均等待时间最短,但难以知道下一个CPU区间长度 优先级调度算法(可以是抢占的,也可以是非抢占的):优先级越高越先分配到CPU,相同优先级先到先服务,存在的主要问题是:低优先级进程无穷等待CPU,会导致无穷阻塞或饥饿;解决方案:老化 时间片轮转调度算法(可抢占的):队列中没有进程被分配超过一个时间片的CPU时间,除非它是唯一可运行的进程。如果进程的CPU区间超过了一个时间片,那么该进程就被抢占并放回就绪队列。 多级队列调度算法:将就绪队列分成多个独立的队列,每个队列都有自己的调度算法,队列之间采用固定优先级抢占调度。其中,一个进程根据自身属性被永久地分配到一个队列中。 多级反馈队列调度算法:与多级队列调度算法相比,其允许进程在队列之间移动:若进程使用过多CPU时间,那么它会被转移到更低的优先级队列;在较低优先级队列等待时间过长的进程会被转移到更高优先级队列,以防止饥饿发生。
- 什么是虚拟内存? 虚拟内存允许执行进程不必完全在内存中。虚拟内存的基本思想是:每个进程拥有独立的地址空间,这个空间被分为大小相等的多个块,称为页(Page),每个页都是一段连续的地址。这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。当程序引用到一部分在物理内存中的地址空间时,由硬件立刻进行必要的映射;当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的命令。这样,对于进程而言,逻辑上似乎有很大的内存空间,实际上其中一部分对应物理内存上的一块(称为帧,通常页和帧大小相等),还有一些没加载在内存中的对应在硬盘上。 注意,请求分页系统、请求分段系统和请求段页式系统都是针对虚拟内存的,通过请求实现内存与外存的信息置换。 页面置换算法 FIFO先进先出算法:在操作系统中经常被用到,比如作业调度(主要实现简单,很容易想到); LRU(Least recently use)最近最少使用算法:根据使用时间到现在的长短来判断; LFU(Least frequently use)最少使用次数算法:根据使用次数来判断; OPT(Optimal replacement)最优置换算法:理论的最优,理论;就是要保证置换出去的是不再被使用的页,或者是在实际内存中最晚使用的算法。 虚拟内存的应用与优点 虚拟内存很适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中。当一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用。虚拟内存的使用可以带来以下好处:
在内存中可以保留多个进程,系统并发度提高
解除了用户与内存之间的紧密约束,进程可以比内存的全部空间还大
- 颠簸 颠簸本质上是指频繁的页调度行为,具体来讲,进程发生缺页中断,这时,必须置换某一页。然而,其他所有的页都在使用,它置换一个页,但又立刻再次需要这个页。因此,会不断产生缺页中断,导致整个系统的效率急剧下降,这种现象称为颠簸(抖动)。
内存颠簸的解决策略包括:
如果是因为页面替换策略失误,可以修改替换算法来解决这个问题;
如果是因为运行的程序太多,造成程序无法同时将所有频繁访问的页面调入内存,则要降低多道程序的数量;
否则,还剩下两个办法:终止该进程或增加物理内存容量。
- 局部型原理 时间上的局部性:最近被访问的页在不久的将来还会被访问;
空间上的局部性:内存中被访问的页周围的页也很可能被访问。
四、数据库
1. 索引
索引是一种数据结构,索引基本上是用来存储列值的数据结构,同时存储了指向表中的相应行的指针。指针是指一块内存区域, 该内存区域记录的是对硬盘上记录的相应行的数据的引用。
- 类型(MySQL,*不是) 普通索引:没有任何限制; 唯一索引:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一; 主键索引:一个表只能有一个主键,不允许有空值; 组合索引:多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用; 全文索引:用来查找文本中的关键字,而不是直接与索引中的值相比较。 *(非)聚集索引:表中记录的物理顺序与键值的索引顺序相同。一个表只能有一个聚集索引。
- 实现 B-树(Sql server ,Oracle)、B 树(MySQL)、哈希表、Trie树、R-树(空间问题)、位图索引(布尔值)。
- 哈希索引的缺点 是无序的,对于查询“小于40”的情况无能为力。故不常作为数据库索引结构。
- 索引的缺点 索引会占用空间 ;性能损失,尤其在更新索引时。
- Oracle分区(partition) range分区(范围值)、hash分区、list分区(具体值)、复合分区 分区索引(local、global)
2. B树与B 树
- MySQL中的索引 实际上,查询索引操作最耗资源的不在内存中,而是磁盘IO。索引是存在磁盘上的,当数据量比较大的时候,索引的大小可能达到几个G。那么,我们利用索引进行查询的时候,不可能把索引直接加载到内存中,只能一次读取一个磁盘页,一个磁盘页对应着一个节点,一次读取操作是一个磁盘IO。
- 哈希索引和B 树索引 哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B 树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。 (1)如果是等值查询,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到相应的键值;前提是键值都是唯一的(不存在哈希冲突)。 (2)如果是范围查询检索,这时候哈希索引就毫无用武之地了,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检索; (3)哈希索引也没办法利用索引完成排序,以及like ‘xxx%’ 这样的部分模糊查询(这种部分模糊查询,其实本质上也是范围查询); (4)哈希索引也不支持多列联合索引的最左匹配规则。
- B 树对比B-树的好处 (1)IO次数少:B 树中间节点只存索引,不存在实际的数据,所以可以存储更多的数据。索引树更加的矮胖。 (2)性能稳定:B 树数据只存在于叶子节点,查询性能稳定 (3)范围查询简单:B 树不需要中序遍历,遍历链表即可。
AVL树和红黑树基本都是存储在内存中才会使用的数据结构。在大规模数据数据存储的时候,红黑树往往出现由于树的深度过大而造成磁盘IO读写过于频繁,进而导致效率底下的情况。
五、组成原理
六、Linux
七、Java
1. 集合类源码分析
集合类源码分析
2. 容器
容器大全
3. 多线程
多线程与多进程
锁机制
4. 常见知识点罗列
- equal和==的区别 对于 ==,比较的是值是否相等,基本数据类型为值,引用类型为地址; 对于equals方法,注意:equals方法不能作用于基本数据类型的变量,equals继承Object类,比较的是是否是同一个对象。如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
- StringBuilder和StringBuffer (1)String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。 (2)StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全
- ArrayList和LinkedList (1)ArrayList和Vector使用了数组的实现,可以认为ArrayList或者Vector封装了对内部数组的操作。只要ArrayList的当前容量足够大,add()操作的效率非常高的。只有当ArrayList对容量的需求超出当前数组大小时,才需要进行扩容。扩容的过程中,会进行大量的数组复制操作。扩容1.5倍。 (2)LinkedList使用了循环双向链表数据结构,由一系列表项连接而成。一个表项总是包含3个部分:元素内容,前驱表和后驱表。在JDK的实现中,无论LikedList是否为空,链表内部都有一个header表项,它既表示链表的开始,也表示链表的结尾。如果要删除的位置处于List的前半段,则从前往后找;若其位置处于后半段,则从后往前找。避免随机访问。 随机访问ArrayList效率高,增加删除LinkedList高。列表遍历方式:forEach操作,迭代器和for循环。List有序、Set不重复。
- 线程创建的方法、start与run的区别、停止线程的方法、什么场景用哪个 (1)创建方法:继承Thread类重写run()方法、实现Runnable接口重写run()方法、实现Callable接口重写call()方法(JUC并发包,线程池解决重用问题)。同时继承Thread类,其中的run()方法会当作对Runnable()接口的实现。 (2)start()方法是一个native方法,启动一个新线程并执行(异步调用)run()方法,调用后变为可运行态,什么时候运行由OS决定。直接调用run()方法会被当作一个普通的函数同步调用,无法达到多线程效果。 (3)sleep()方法不涉及线程间的通信,不释放锁,需要捕获异常(其他方法调用interrupt()方法);wait()方法会释放锁,且必须放在synchronized块中;yield()方法会给同级或更高级线程让出锁,当前进程变为可执行态;stop()方法释放锁,suspend()方法不释放锁挂起,不安全不推荐使用。 synchronized是托管给JVM执行的,块中自动解锁,资源竞争激烈时性能下降;Lock的锁是通过代码实现的,手动释放锁。 (4)Thread实现了Runnable接口,java单继承规定,一个类只有在需要被加强或修改时才会被继承。
- assert断言的思考 (1)assert关键字需要在运行时候显式开启才能生效,而现在主流的Java IDE工具默认都没有开启-ea断言检查功能,即需要更改Web容器的运行配置参数。这对程序的移植和部署都带来很大的不便。 (2)用assert代替if是陷阱之二,assert关键字本意上是为测试调试程序时使用的,但如果不小心用assert来控制了程序的业务流程,那在测试调试结束后去掉assert关键字就意味着修改了程序的正常的逻辑。 (3)assert断言失败将面临程序的退出,这在一个生产环境下的应用是绝不能容忍的。一般都是通过异常处理来解决程序中潜在的错误。
- abstract class和interface区别 abstract修饰的类:不能被实例,可以包含具体方法,必须实现,所以不能与final、static、private公用。 所有的对象都是通过类来描绘的,但是反过来却不是这样。在面向对象领域,抽象类主要用来进行类型隐藏。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。OCP(Open-Closed Principle)原则。 (1)单继承和多实现的区别。 (2)在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,需使用委托模式。 (3)不能定义默认行为可能会造成维护上的麻烦。如果想修改类的界面以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,可能要花费很多的时间(多派生类)。通过abstract class来实现的,只需要修改定义在abstract class中的默认行为(避免重用)。
- Static 作用:方便在没有创建对象的情况下来进行调用(方法/变量)。不需要依赖于对象来进行访问,只要类被加载(按定义顺序加载)了,就可以通过类名去进行访问。 优化:static代码块由于只会在类加载的时候执行一次,所以可以替换重复访问的非静态方法。 误区: (1)Java中的static关键字不会影响到变量或者方法的作用域。在Java中能够影响到访问权限的只有private、public、protected(包括包访问权限)这几个关键字。 (2)能通过this访问静态成员变量吗?虽然对于静态方法来说没有this,静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。
public class Main {
static int value = 33; //static变量可被对象(new)和类所享有
public static void main(String[] args) throws Exception{
new Main().printValue();
}
private void printValue(){
int value = 3;
System.out.println(this.value);
}
}
//结果为:33
(3)static是不允许用来修饰局部变量。(C 可以)
所以在mian函数中需要new一个实例,才能访问非static对象。
- 异常类
Throwable
Error
VirtualMachineError
ThreadDeath
Exception
RuntimeException非检测异常
空指针、数组下标越界、类型转换、算术
Non-RuntimeException检测异常
IO、SQL
Exception类分两大类型:Error类代表了编译和系统的错误,不允许捕获(虚拟机和线程死锁);Exception类代表了标准Java库方法所激发的异常(Runtime解释器引发非检测和Non-Runtime可检测)。
- IO流 (1)字节流: InputStream、OutputStream。不会用到缓存 (2)字符流: Reader、Writer。会用到缓存
- Spring、Springboot、SpringMVC请求过程、微服务 (1)Spring (2)Springboot (3)SpringMVC (4)微服务:微服务架构风格是一种将单个应用程序作为一套小型服务开发的方法,每种应用程序都在自己的进程中运行,并与轻量级机制(通常是HTTP资源API)进行通信。 这些服务是围绕业务功能构建的,可以通过全自动部署机制独立部署。 这些服务的集中管理最少,可以用不同的编程语言编写,并使用不同的数据存储技术。垂直组织架构。 SOA架构强调的是异构系统之间的通信和解耦合,而微服务架构强调的是系统按业务边界做细粒度的拆分和部署
- synchronized方法底层实现,与Lock()的区别 synchronized 锁住的是普通对象或类对象,当一个对象被锁时,就在它的对象头中进行一个MarkWord 标记,在 jvm 堆中的每个对象都有对象头。基于monitor对象实现, 是非公平锁,即不能保证等待锁线程的顺序。 lock 锁住的是 Lock 对象,当调用它的 lock 方法时,会将 Lock 类中的一个标志位 state 加 1,释放锁时是将 state 减 1。实现 ReentrantLock 可通过实例化true or false 的构造参数实现公平锁和非公平锁,可定时。
- 重写equals方法 hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的。 考虑和集合类协同工作的需要,一般集合为加快存取速度,通常使用类hashtable的方式存取对象,hashCode() && equals() 则是判断待查找元素与集合中某个元素相等的依据。 而java中默认的hashCode是由对象的内存地址生成的, 如果重写了equals 而不重写 hashCode,则会造成“A和B相等,A加入集合后,用B查询集合却查不到”的悖论。
- 线程优先级一定先运行吗 1、java线程是通过映射到系统的原生线程上来实现的,所以线程的调度最终还是取决于操作系统,操作系统的优先级与java的优先级并不一一对应,如果操作系统的优先级级数大于java的优先级级数(10级)还好,但是如果小于得的话就不行了,这样会导致不同优先级的线程的优先级是一样的。 2、优先级可能会被系统自动改变,比如windows系统中就存在一个优先级推进器,大致功能就是如果一个线程执行的次数过多的话,可能会越过优先级为他分配执行时间
- finally块 finally中的代码是必须要执行的,而且是在return前执行,除非碰到exit(),并且守护线程的finally子句不一定能够执行。
八、JVM
1. Java优点
摆脱了平台束缚、相对安全的内存管理和访问机制、热点代码检测和运行时编译及优化、一套完善的应用程序接口和开源优势。
Java与C 之间的高墙:内存动态分配、垃圾收集技术。
2. 运行时数据区域
(1)程序计数器(线程私有):当前线程所执行的字节码的行号指示器,分支、循环、跳转、异常处理、线程恢复等基础功能。唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
(2)Java虚拟机栈(线程私有):每个方法在执行的同时会创建一个栈帧,存储局部变量表(基本数据类型、对象引用、returnAddress类型,编译时完成分配)、操作数栈、动态链接、方法出口等。
(3)本地方法栈(线程私有):为执行Native方法(与平台相关,存在非Java的实现类)服务。
(4)Java堆:JVM所管理内存中最大的一块,对象实例、数组在堆上分配,有“指针碰撞”和“空闲列表”两种分配方式,线程安全有同步处理和本地线程分配缓冲(TLAB)两种方式。堆分代为,Young(1/3,Eden 8/10,From Survivor 1/10,To Survivor 1/10)、Old(2/3,有一段年龄的对象)。
(5)方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。
(6)运行时常量池:方法区的一部分,存放编译期生成的各种字面量和符号引用。
(7)直接内存。
3. 对象
- 布局 对象头、实例数据、对齐填充。 对象头:一部分存储对象自身的运行时数据,“Mark Word”有哈希码、GC分代年龄、锁状态标志等。另一部分是类型指针,对象指向它的类元数据的指针。 实例数据:对象真正有效信息,代码中的各字段内容,包含继承。 对齐填充:如HotSpot VM为8字节整数倍。
- 访问定位 (1)句柄:Java栈中本地变量表中引用名地址为句柄池地址,句柄池中包含到对象实例数据和对象类型数据的指针地址,分别对应到Java堆中的实例池和方法区中的对象类型数据。修改时只需要修改句柄池,reference本身不需要修改。 (2)直接指针:直接存储到对象类型数据的指针。速度快。
4. 垃圾收集器与内存分配策略
- 对象回收判断方法 (1)引用计数算法:很难解决对象之间相互循环引用的问题,无法被访问,但是不能回收。 (2)可达性分析算法:GC Roots对象作为起始点,向下搜索,搜索的路径称引用链。没有引用链的对象会被第一次标记,并进行一次筛选,条件是此对象是否有必要执行finalize()方法(@Override)。
- 引用 (1)强引用:只要存在就不会被回收,如Object obj = new Object(); (2)软引用:还有用但并非必须的对象,将要发生内存溢出异常前会回收; (3)弱引用:非必须对象,只能生存到下一次GC发送前; (4)虚引用:幽灵引用或幻影引用,无法取得一个对象实例,作用是能在这个对象被GC回收时收到一个系统通知。
- 无用类的判断 该类的所有实例已被回收; 加载该类的ClassLoader已被回收; 该类对应的java.lang.Class对象没有在任何地方被引用,即无反射访问。
- 垃圾收集算法 (1)标记-清除算法:效率问题、空间问题(内存碎片)。 (2)复制算法:将内存容量划分为大小相同的两块,每次只适用其中的一块,内存用完后将存活对象复制到另一块,再对整个半区一次清理。用于HotSpot VM中的Survivor空间。 (3)标记-整理算法:让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。 (4)分代收集算法:新生代少量存活用(2),老年代用(1)或(3)。 此外,还有串行和并行两种执行方法。
- HotSpot VM实现 (1)枚举根节点:GC停顿,“Stop The World”,利用OopMap的数据结构计算偏移量。 (2)安全点:程序执行只有到达安全点才能开始GC,分抢先式中断(先中断,不在安全点再运行一段)和主动式中断(在到达一个安全点时,检查一个轮询标志)。 (3)安全区域:在这段代码中,引用关系不会发生变化。解决程序没有分配CPU的情况。
- 垃圾收集器 Serial:单线程收集器,GC时暂停其他所有工作线程。Client模式下默认新生代收集器,复制算法。 ParNew:Serial的多线程版本,Server模式下首选的新生代收集器,能与CMS配合。 Paraller Scavenge:新生代收集器,复制算法,可以控制吞吐量,但是不能与CMS配合。 Serial Old(MSC):Serial收集器的老年代版本,Client模式。 Parallel Old:Parallel Scavenge收集器的老年代版本,标记-整理算法。 CMS:以获取最短回收停顿时间为目标,初始标记、并发标记后重新标记,再并发清除,标记-清除方法。 G1:并行与并发、分代收集、空间整合(标记-整理算法)、可预测的停顿(在明确指定的一个长度的时间段内,消耗在垃圾收集上的时间不超过的时间)。
- 回收策略 (1)对象优先分配在Eden:Enden没有足够内存需要发起一次Minor GC(老年代GC为Major GC/FullGC); (2)大对象直接进入老年代:避免在Eden区及两个Survivor区之间发生大量的内存复制; (3)长期存活的对象进入老年代:对象年龄计数器,每经过一次Minor GC年龄加1,默认15进入老年代; (4)动态对象年龄判定:如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象直接进入老年代; (5)空间分配担保:把Survivor无法容纳的对象直接进入老年代。Full GC会检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小。Minor GC会检查老年代最大可用的连续空间是否大于新生代所有对象总空间。
九、项目经验
0. 信息安全实验
ICMP重定向
Netfilter编程实现用户名和密码的窃取
1. SSO CAS
我的参考博客
2. Nginx
作用:静态代理(放置静态文件,动静分离)、负载均衡、限流(基于漏桶算法,高并发)、缓存(浏览器/静态资源、代理层)、黑白名单(不限流白名单)
我的参考博客
3. Layui
我的参考博客
4. Ajax
我的参考博客
5. Spring MVC
我的参考博客