感悟
之前没有做过笔试题,现在做了后发现一塌糊涂。。。 在这里我只贴出自己错过的题目,几乎是全部了,呵呵呵。。。
客观题
1. 下列程序的运行结果是( )
#include <iostream>
#include <list>
using namespace std;
typedef list<int> iList;
int main()
{
iList list1;
iList::iterator i;
list1.push_back(2);
list1.push_back(4);
list1.insert(++list1.begin(),3,9);
list1.pop_front();
list1.pop_back();
list1.erase(++list1.begin());
for(i=list1.begin();i!=list1.end();i++)
cout<<(*i)<<" ";
cout<<endl;
return 0;
}
答案:
9,9
解析
list1.insert(++list1.begin(),3,9):在第二个位置插入3个元素9,列表内容为 [2, 9, 9, 9, 4]
list1.erase(++list1.begin()):移除列表的第二个元素,列表内容为 [9, 9]
2. G1收集器
- Rset维护并跟踪收集器单元的内部引用,减少了扫描整个Heap堆获取信息的耗时
- G1保留的堆数目的虚假上限,以减少提升失败的可能性
- G1是压缩回收器,完全避免了使用细粒度空闲列表来进行分配,而是依赖区域,基本消除了潜在碎片的问题
3. 程序并发执行的特征包括
- 不可再现性
- 间断性
- 失去封闭性
4.若用一个大小为8的数组来实现循环队列,且当前队尾Rear和队首Front的值分别为0和5,当从队列中删除一个元素,再加入三个元素后,Rear和Front的值分别为( )
解析
循环队列是一种环形结构,当队列的尾部(Rear)或头部(Front)到达数组的末尾时,它会绕回到数组的开头。在给定情况下,初始队列大小为8,队尾(Rear)为0,队首(Front)为5。
删除一个元素后,队首(Front)会向后移动一个位置,即Front = (Front + 1) % 8 = 6。然后,添加三个元素,队尾(Rear)会依次向后移动三个位置,即Rear = (Rear + 3) % 8 = 3。
所以,删除一个元素并添加三个元素后,队首(Front)为6,队尾(Rear)为3
5.存储过程
在Java中,存储过程(Stored Procedure)是一种预编译的数据库对象,它是一组预定义的 SQL 语句集合,可以像函数一样被调用。存储过程通常在数据库服务器上创建,然后可以通过调用它们来执行数据库操作,例如查询、更新、插入或删除数据。存储过程的类型有系统存储过程、用户存储过程、扩展存储过程
存储过程有以下几个特点:
- 封装和模块化: 存储过程将一组数据库操作封装成一个单独的模块,使数据库的逻辑更加模块化和可维护。
- 提高性能: 存储过程通常是在数据库服务器上编译和优化的,因此可以提高查询性能。数据库可以缓存存储过程的执行计划,以减少查询的执行时间。
- 减少网络流量: 通过调用存储过程,可以减少需要传输到数据库服务器的数据量,因为只需发送存储过程的参数,而不是完整的 SQL 语句。
- 安全性和权限控制: 存储过程可以实现细粒度的权限控制,只允许执行特定的数据库操作,从而提高系统的安全性。
- 重用性: 存储过程可以在多个应用程序中重用,避免了重复编写相同的 SQL 语句
6. Mysql数据库存储引擎和索引的描述有哪些是正确的?
- InnoDB支持事务和外键,MyISAM不支持事务和外键;
- Memory 不支持外键、行级锁和事务
- EXAMPLE 不支持索引和分区
7. JVM判断对象是否会被回收的算法有
JVM(Java Virtual Machine)使用垃圾回收算法来判断哪些对象可以被回收,以释放内存并管理堆空间。以下是JVM判断对象是否会被回收的主要垃圾回收算法:
- 引用计数算法(Reference Counting): 这种算法通过在对象中维护一个引用计数器,记录有多少个指向该对象的引用。当计数器降为零时,表示对象不再被引用,可以被回收。但是,引用计数算法难以解决循环引用的问题,导致内存泄漏。
- 可达性分析算法(Reachability Analysis): 这是现代垃圾回收算法的主流方法,JVM通过一组称为"GC Roots"的起始对象开始分析,然后通过遍历对象图来判断哪些对象是可达的(即有引用指向),哪些对象是不可达的。不可达的对象被判定为可回收。
8.建造者模式使用简单对象并采用逐步方法来构建复杂对象?
建造者模式让我们可以把构建一个复杂的对象分成几个简单的步骤。就像积木一样,每一步都是一个小任务,最后通过这些小任务的组合,就能够建造出一个复杂的对象,比如房子。
这样做的好处是,我们可以根据需要来定制每一步的细节,就像你可以选择用不同形状和颜色的积木来搭建不同风格的房子一样。而且,如果以后想要改变房子的设计,只需要调整每一步的积木组合方式,而不用从头开始。
9. 拓扑排序序列
拓扑排序序列就是告诉我们做任务的正确顺序,确保每个任务都在它所依赖的任务之后完成,就像做美味的三明治一样,每个步骤都是按照顺序来的!
10. 单元测试不能保证程序的正确性
11.判断一个有向图是否存在回路可以利用?
可以利用拓扑排序算法。拓扑排序是一种将有向图中的节点线性排序的方法,其中每个节点的前驱都在它前面。
如果一个有向图存在回路,那么它就不是DAG,因为回路意味着存在一条路径从某个节点出发回到自身,这就违反了拓扑排序的特性。
12.产生死锁的必要条件是
- 不剥夺条件
- 请求和保持条件
- 环路等待条件
- 互斥条件
13. 关于并发调度的可串行性的说法中正确的是?
- 可串行性(serializability)是并发事务正确调度的准则。按这个准则规定,一个给定的并发调度,当且仅当它是可串行化的,才认为是正确的调度
- 冲突操作是指不同的事务对同一个数据的读写操作和写写操作
- 一个调度是冲突可串行化,则不一定是可串行化的调度
14. web服务通过http协议传输数据,http协议使用传输层建立连接的协议是
tcp协议
15.在服务器端配置web服务时,可以支持web服务的平台有?
- IIS
- nginx
- apache
16. 在linux系统中使用vi编写程序时,挂起该进程的操作是
ctrl+z
17. Jdk的实现中,OutputStream和InputStream类族是通过装饰器设计模式实现的
18.策略模式
策略模式(Strategy Pattern)是一种行为型设计模式,它允许在运行时选择算法或策略,从而使一个类的行为可以根据需要而变化。策略模式的特点包括:
- 封装变化: 策略模式将算法或行为封装在独立的策略类中,使得这些算法可以独立地变化,不会影响到使用这些算法的客户端代码。
- 选择算法: 策略模式允许客户端在运行时从多个策略中选择一个合适的算法来执行,而不需要修改客户端代码。
- 替换扩展容易: 由于策略模式将不同的算法独立封装,当需要添加新的算法时,只需要添加一个新的策略类,而不需要修改现有代码。
- 减少条件语句: 使用策略模式可以避免大量的条件语句,使代码更加清晰、可维护,也降低了代码的复杂度。
- 切换策略方便: 策略模式允许在运行时动态地切换策略,从而实现动态适应不同的需求。
- 提高可测试性: 策略模式将不同的算法分离,可以更容易地进行单元测试,测试每个策略的行为独立性。
- 不同策略共享上下文: 不同策略类可以共享同一个上下文对象,从而可以在策略之间传递信息。
19. 并发说法正确的是?
- Java中所有使用的并发机制依赖于JVM的实现和CPU的指令
- 如果Volatile变量修饰符使用恰当的话,他比synchronized的使用和执行成本更低。
- "可见性"的意思是当一个线程修改共享变量时,另外一个线程能读到这个修改的值
- synchronized是一个重量级的锁
20.Spring MVC的核心组件包含哪些?
- Controller: 控制器负责处理业务逻辑,接收请求并返回相应的视图或数据。通常是开发者编写的Java类,处理特定的请求
- ModelAndView: ModelAndView是一个用于存储数据和视图信息的对象,Controller可以通过它设置需要返回的数据和视图
- DispatcherServlet: 是整个Spring MVC框架的前端控制器,负责接收客户端的HTTP请求,然后将请求分发给相应的处理器(Controller)来处理
- ViewResolver: ViewResolver负责将逻辑视图名称解析为具体的视图实现。它根据逻辑视图名查找合适的视图,比如JSP、Thymeleaf、FreeMarker等
21. 最小生成树算法
- 普里姆算法(Prim's Algorithm): 普里姆算法是一种贪心算法,它从一个初始顶点开始,逐步扩展最小生成树,每次选择连接已有最小生成树和未加入最小生成树的顶点之间的最短边。这个过程持续进行,直到所有的顶点都被包括在最小生成树中。
- 克鲁斯卡尔算法(Kruskal's Algorithm): 克鲁斯卡尔算法也是一种贪心算法,它不是从一个初始顶点开始,而是按照边的权重递增的顺序逐步选择边,并且在选择的过程中避免构成回路。当最终选择的边数等于总顶点数减一时,得到的就是最小生成树。
这两种算法的时间复杂度如下:
- 普里姆算法时间复杂度: 在最简单的实现情况下,普里姆算法的时间复杂度是O(V^2),其中V是顶点数。然而,通过使用优先队列(堆)数据结构,可以将其优化到O(E + VlogV),其中E是边数。
- 克鲁斯卡尔算法时间复杂度: 在最简单的实现情况下,克鲁斯卡尔算法的时间复杂度是O(E^2),其中E是边数。但是,通过使用并查集等数据结构,可以将其优化到O(ElogE)
编程题
1. 抽奖
A和B两个人在抽奖。现在有一个抽奖箱,里面有n张中奖票,m张不中奖票。A和B轮流从中抽一张奖票出来。如果有人抽到中奖票就结束,抽到中奖票的人胜利。抽过的奖票会被丢弃。
额外的,B每次抽后,会再次抽取一张票并丢弃掉(这张票中奖不算B胜利)。
现在,A先抽,请问A的胜率,保留4位小数后输出。
如果两人到最后也没有抽到中奖票算作B胜利。
import java.util.Scanner;
public class Main {
static int maxn = 1005;
static double[][] f = new double[maxn][maxn];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int w = scanner.nextInt();
int b = scanner.nextInt();
System.out.printf("%.4f\n", dfs(w, b));
}
// 计算获胜概率的函数
static double dfs(int nw, int nb) {
// 边界情况:没有中奖票,B已获胜
if (nw == 0) return 0.0;
// 边界情况:没有不中奖票,A必胜
if (nb == 0) return 1.0;
// 如果已经计算过当前状态的概率,直接返回结果
if (f[nw][nb] > 0) return f[nw][nb];
double ans = 0;
// A抽中中奖票的概率
ans += 1.0 * nw / (nw + nb);
if (nb == 2)
// B第二次抽中中奖票、B第一次抽中不中奖票的概率
ans += 1.0 * nb / (nw + nb) * (nb - 1) / (nw + nb - 1) * dfs(nw - 1, nb - 2);
else if (nb >= 3)
// B抽中中奖票、B抽中不中奖票的概率,以及B抽中不中奖票、B抽中不中奖票的概率
ans += 1.0 * nb / (nw + nb) * (nb - 1) / (nw + nb - 1) *
(1.0 * nw / (nw + nb - 2) * dfs(nw - 1, nb - 2) + 1.0 * (nb - 2) / (nw + nb - 2) * dfs(nw, nb - 3));
// 将计算得到的概率存入数组,并返回结果
return f[nw][nb] = ans;
}
}