目录
选择题
1.
耗时的操作由一个线程单独运行,那这个耗时的操作就可以和其他操作同时执行了。
进程是资源分配的基本单位,一个进程包含多个线程,线程使用的内存是属于进程的,所以对于是否耗内存来说,都是线程占用了所在进程的一部分内存,并不存在提高内存利用率这一说法。
一个进程包含多个线程,有了多个线程之后,就可以同时利用这些 CPU。
多个客户端创建多个线程,当多个客户端同时请求时,多个线程也能第一时间响应,能提高响应速度。
2.
文件是按块存储的,如果块大小设置的大一点,读取的时候一次性就读取的更多,磁盘吞吐量提升,但是文件可能不能占满整个块,导致利用率下降。
3.
并发性:指多个进程实体同存于内存中,且在一段时间内同时运行。并发性是进程的重要特征,同时也成为操作系统的重要特征。
动态性:进程的实质是进程实体的一次执行过程,动态性是进程最基本的特征。
独立性:进程实体是一个独立运行,独立分配资源,和独立接受调度的基本单位。
异步性:指进程按各自独立的,不可预知的速度向前推进,或者说实体按异步方式运行。
4.
等待队列:用于使线程等待某一特定的事件发生而无需频繁的轮询,进程在等待期间睡眠,在某件事发生时由内核唤醒。
作业后备队列:操作系统首先从外存的后备队列中选取某些作业调入内存,并为它们创建进程、分配必要的资源。然后再将新创建的进程插入就绪队列,准备执行。
5.
Cahce 是缓存。
Cache 出现的原因就是为了解决 CPU 与主存之间的速度匹配问题,CPU 速度 > Cache 速度 > 主存速度。
程序访问的局部性原理是一个程序在运行的某一时段,它访问的代码或数据大部分是集中在某一块区域的。(感觉跟代码要高内聚差不多)
Cache 是 CPU 缓存,Cache 的地址与主存的地址是两码事,不统一编址,也没有从属关系。
Cache 是由硬件实现。
6.
页面的频繁更换,导致整个系统效率急剧下降,这个现象称为内存抖动。
抖动一般是内存分配算法不好,内存太小或者程序的算法不佳,引起的页面频繁从内存调入调出。
可以减少进程来防止抖动。
7.
响应时间是任务到达和任务开始被处理(响应)之间的时间。
周转时间是到达和处理结束之间的时间。
实时调度算法:指系统能够在限定的响应时间内提供所需水平的服务,如果系统的时间约束条件得不到满足,将会发生系统错误。
短任务优先算法:执行时间短的任务优先执行。
时间片轮转算法:由系统调度就绪队列中的进程,并为每个进程分配一段时间片,如果时间片结束但进程还没完成,则会回到就绪队列末尾,等待下一次调度。
先来先服务算法:按照顺序执行。
以上调度算法,时间片轮转算法是抢占式算法。
采用短任务优先算法的平均等待时间,平均周转时间最短。
8.
9.
页的划分是操作系统做的。
死锁必须满足四个条件:①互斥 ②循环等待 ③不剥夺 ④请求保持 所有进程都挂起不一定形成了循环等待的关系,有些进程定时器结束后就会自动唤醒。
系统调用就是调用库函数,可以被优先级更高的线程中断。时间轮转调度算法,时间片内线程未完成,则线程会被中断,然后返回到就绪队列末尾等待下一次的系统调度。
有静态优先级调度(优先级是固定不变的)。
10.
用缓存可以提高数据的访问速度。
计算密集型:比如使用加减乘除等操作的数量很多。
I/O 密集型:比如读写数据这种 I/O 操作的数量很多。
因为 IO 密集型的一个操作(一个IO)会让线程进入等待,就算用多线程了也依然等待,所以并不能用来程序调优。如果是 IO 密集型的多个操作应用(多个IO),那就可以用多线程。
数据库连接池可以反复使用,而数据库必须得先建立连接然后再使用最后还得关闭连接,其性能没有数据库连接池好。
递归优化方案:① 迭代 ② 尾递归
一个远程调用只能发送一次文件,多个远程调用就要多次发送文件,将多个远程调用合并成一个可以提高速率,减少等待时间。
如果大家都有冗余数据,如果能共享的话,那就能提高访问冗余数据的速度。
编程题
1. Pre-Post
我们可以下个有道翻译,不会的单词就搜一下。
译文:
我们都非常熟悉二叉树的前序遍历,中序遍历和后序遍历,有一个在数据结构类的普遍的问题,就是给你中序遍历和后序遍历,来去找二叉树的前序遍历。或者,给你中序遍历和前序遍历,让你找后序遍历。然而,当给你前序遍历和后序遍历时,你不能确定二叉树的中序遍历。看看下面的四个二叉树:
这些二叉树都有相同的前序遍历和后序遍历,这种现象不局限于二叉树,可以把它推广到 m 叉树。
输入描述:有多组输入,每个实例由一行 m s1 s2 这个格式,m 就是 m 叉树,s1 是前序遍历结果,s2 是后序遍历结果,遍历结果全都是小写字母,1 <= m <= 20,s1 的长度和 s2 的长度在 1 到 26 之间。如果 s1 长度 = s2 长度 = k,字母表的前 k 个字符会被用于字符串中,输入 0 的话表示结束。
输出描述:对于每个实例,你应该输出一行,一个数字包含树的可能个数(根据前序和后序的结果来确认),数的范围要在 int 范围之内,对于每个实例,可以保证至少有一个树具有给定的前序遍历和后序遍历。
总结:用递归,先分离子树:去前序遍历中找子树的根节点,然后再去后序遍历中找根节点所在的位置,然后计算出子树的总节点个数 count,然后返回前序遍历让下标往后走 count ,继续重复以上过程,直到越界。此时就能得知根节点一共有多少个孩子,并且能知道子树的前序遍历后后序遍历,然后再计算以 m 为底,挑选孩子个数的组合,然后遍历子树集合。继续去寻找每个子树的可能性,并将子树可能性的组合相乘计算出总的可能性,最后返回总可能性即可。
代码实现:
import java.util.*;
class SubTree {
String prev;
String post;
public SubTree(String prev, String post) {
this.prev = prev;
this.post = post;
}
}
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
// 计算 n 的阶乘
public static long fac(int n) {
int ans = 1;
for (int i = 1; i <= n; i++) {
ans *= i;
}
return ans;
}
// 计算组合
public static long calcCom(int m, int n) {
// 组合就是用 m * m-1 * m-2 *... *(m-n + 1)
// 在用上面结果除以 n 的阶乘
long ans = 1;
for (int i = m; i >= m - n + 1; i--) {
ans *= i;
}
return ans / fac(n);
}
// 分离子树
public static List<SubTree> subTreeFunc(String prev, String post) {
// 首先初始化一个 list 来存储返回的子树对象
List<SubTree> subTreeList = new ArrayList<>();
// 前序遍历:根节点 子树1 子树2 ... 子树n
// 后序遍历:子树1 子树2 子树3 ... 根节点
// 在前序遍历中,子树的开始节点下标
int prevFirstIndex = 1;
// 在后序遍历中,子树的开始节点下标
int postFirstIndex = 0;
// 然后从前序遍历中找子树的根节点
while (prevFirstIndex < prev.length()) {
char root = prev.charAt(prevFirstIndex);
// 从后序遍历中找该子树的根节点,并计算出该子树的节点个数
int postSubTreeRootIndex = post.indexOf(root);
// 子树节点总个数
int subTreeCount = postSubTreeRootIndex - postFirstIndex + 1;
// 截取子树的前序遍历结果,以及子树的后序遍历结果
String subTreePrev = prev.substring(prevFirstIndex,
prevFirstIndex + subTreeCount);
String subTreePost = post.substring(postFirstIndex,
postFirstIndex + subTreeCount);
// 将子树的前序遍历和后序遍历存到对象中, 并放入 list 里
SubTree subTree = new SubTree(subTreePrev, subTreePost);
subTreeList.add(subTree);
// 继续寻找下一个子树
prevFirstIndex = prevFirstIndex + subTreeCount;
postFirstIndex = postFirstIndex + subTreeCount;
}
return subTreeList;
}
// 根据先序遍历和后序遍历,计算所有树的可能性
public static long calcTreePossiable(int m, String prev, String post) {
// 首先判断,如果节点只有一个,或者没有节点,那可能性为 1
if (prev.isEmpty() || prev.length() == 1) {
return 1;
}
// 树非空时
// 首先要分离子树, 可以构建个对象来存储子树的前后遍历结果
List<SubTree> subTreeList = subTreeFunc(prev, post);
// 计算子树的组合
long result = calcCom(m, subTreeList.size());
for (SubTree e : subTreeList) {
// 计算每棵子树的可能性
result *= calcTreePossiable(m, e.prev, e.post);
}
// 然后返回可能性
return result;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
while (in.hasNextInt()) { // 注意 while 处理多个 case
int m = in.nextInt();
if (m == 0) {
// 如果 m 为 0,说明要结束输入
break;
}
String prev = in.next();
String post = in.next();
// 根据树的前序遍历和后序遍历,计算树的可能性
long count = calcTreePossiable(m, prev, post);
System.out.println(count);
}
}
}
2. Rational Arithmetic(有理数运算)
译文:题目要求计算两个有理数的加减乘除。
输入描述:对于每个测试用例,会输入一行,包括两个有理数 ,以"a1/b1 a2/b2" 的形式。分子和分母都会在 long 范围内。负号一定会在分子的前面,分母确保一定不是 0。
输出描述:每个测试用例,需要输出四行,分别是两个有理数的加减乘除,每行格式是:
"num1 操作符 num2 = 结果",有理数必须为最简格式如"k a/b",k 是整数部分, a/b 是分数的最简式,如果数字为负数,则整个有理数都要被括号括起来,如果分母是 0,就得输出"Inf" 来作为结果。能保证所有的数字都在 long 范围内。
思路:这个加减乘除的实现并不难,难的是输出有理数是的化简,需要分多种情况讨论。
代码实现:
import java.util.*;
class Rational {
// 有整数部分 + 分数部分
// 负号,是否为 0
public long integer;// 整数
public long numerator;// 展示的分子
public long denominator;// 分母
public boolean negative;// 是否是负数
public boolean isZero;// 如果分母是 0
public long calcNumerator;// 计算时使用的分子
// 解析分子 2/3
public static long parseNumerator(String str) {
int index = str.indexOf("/");
return Long.parseLong(str.substring(0, index));
}
// 解析分母 2/3
public static long parseDenominator(String str) {
int index = str.indexOf("/");
return Long.parseLong(str.substring(index + 1, str.length()));
}
// 初始化
public Rational(long n, long d) {
// 输入时,分母不可能为 0,但是经过计算后,分母可能为 0
if (d == 0) {
this.isZero = !isZero;
return;
}
// 如果分子小于 0,那就是负数
if (n < 0) {
this.negative = !negative;
}
// 经过计算后,分母可能小于 0
if (d < 0) {
this.negative = !negative;
}
// 将 5/3 ---> 1 2/3
this.integer = n / d;
this.numerator = n - integer * d;
this.denominator = Math.abs(d);
if (this.numerator > 1 || this.numerator < -1) {
// 找 n 和 d 的最大公约数, 化为最简式
// 6/9 3
long gcd = getGCD(Math.abs(this.numerator), this.denominator);
// 如果公约数存在,就得化简
if (gcd > 0) {
this.denominator = this.denominator / gcd;
this.numerator = this.numerator / gcd;
}
}
// 计算时需要用到假分数
this.calcNumerator = this.integer * this.denominator + this.numerator;
}
// 求 a 和 b 的最大公约数
public static long getGCD(long a, long b) {
while (a % b != 0) {
long c = a % b;
a = b;
b = c;
}
return b;
}
// 加 5/3 + 1/2 = (5*2 + 1*3) / (3 * 2)
public Rational add(Rational r2) {
// 先通分,然后再相加
long calcNumerator = this.calcNumerator * r2.denominator + this.denominator * r2.calcNumerator;
long denominator = this.denominator * r2.denominator;
return new Rational(calcNumerator, denominator);
}
// 减 与加同理,改个符号即可
public Rational sub(Rational r2) {
// 先通分,然后再相减
long calcNumerator = this.calcNumerator * r2.denominator - this.denominator * r2.calcNumerator;
long denominator = this.denominator * r2.denominator;
return new Rational(calcNumerator, denominator);
}
// 乘 1/2 * 3/4 = 1*3 / (2*4)
public Rational mul(Rational r2) {
// 分子相乘,分母相乘
return new Rational(this.calcNumerator * r2.calcNumerator, this.denominator * r2.denominator);
}
// 除 1/2 / 3/4 = 1/2 * 4/3
public Rational div(Rational r2) {
return new Rational(this.calcNumerator * r2.denominator, this.denominator * r2.calcNumerator);
}
public String toString() {
StringBuilder s = new StringBuilder();
// 如果分母为 0,则输出 Inf
if (isZero) {
s.append("Inf");
return s.toString();
}
// 整数部分为 0 并且分子也为 0,说明整个有理数都是 0
if (this.integer == 0 && this.numerator == 0) {
s.append("0");
return s.toString();
}
// 输出 Rational ,包括整数部分和小数部分
// 负数要加括号
if (this.negative) {
s.append("(-");
}
// 整数部分
if (this.integer != 0) {
// 前面负数时已经加了负号,所以这里应该要用绝对值
s.append(Math.abs(this.integer));
// 整数部分和分数部分需要用空格隔开
if (numerator != 0) {
s.append(" ");
}
}
// 分数部分 可能存在,也可能不存在
if (this.numerator != 0) {
// 前面负数时已经加了负号,所以这里应该要用绝对值
s.append(Math.abs(this.numerator) + "/" + this.denominator);
}
// 负数要加括号
if (this.negative) {
s.append(")");
}
return s.toString();
}
}
// 注意类名必须为 Main, 不要有任何 package xxx 信息
class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
while (in.hasNext()) { // 注意 while 处理多个 case
String n1 = in.next();
String n2 = in.next();
// 创建个对象,来表示有理数
Rational r1 = new Rational(Rational.parseNumerator(n1), Rational.parseDenominator(n1));
Rational r2 = new Rational(Rational.parseNumerator(n2), Rational.parseDenominator(n2));
// 依次打印 加减乘除即可
System.out.println(r1 + " + " + r2 + " = " + r1.add(r2));
System.out.println(r1 + " - " + r2 + " = " + r1.sub(r2));
System.out.println(r1 + " * " + r2 + " = " + r1.mul(r2));
System.out.println(r1 + " / " + r2 + " = " + r1.div(r2));
}
}
}