一:Java模拟面试(9.4)
1.哪些问题是HTTPS无法解决的?
1.网络延迟:HTTPS是基于TCP协议的,因此会受到TCP握手和TLS挥手带来的时间延迟,会导致加载的页面时间变长。
2.数据包大小:由于HTTPS相对于HTTP的头部信息相对较大,而HTTPS无法对头部信息进行压缩,因此每个请求的数据包相对来说比较大。
3.并发请求限制:在早期的HTTP/1.1中,每个HTTPS链接同一时间只能处理一个请求,但随着HTTP/2引入了多路复用,但还是在一些请求中无法解决并发访问的限制。
4.性能影响:HTTPS的加密和解密过程需要消耗CPU的资源,会降低处理器的响应速度。
2.谈一谈对双亲委派模型的理解
所谓的双亲委派模型就是如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。
因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
好处:
使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。 例如 java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中所有的 Object 都是这个 Object。
避免了多份同样字节码的加载,内存是宝贵的,没必要保存相同的两份 Class 对象,例如 System.out.println() ,实际我们需要一个 System 的 Class 对象,并且只需要一份,如果不使用委托机制,而是自己加载自己的,那么类 A 打印的时候就会加载一份 System 字节码,类 B 打印的时候又会加载一份 System 字节码。而使用委托机制就可以有效的避免这个问题。总结就是使用的双亲委派模型是避免类的重复加载和对类的加载更加安全
3. TCP协议和UDP协议有什么区别
标准回答
首先UDP协议和TCP协议都是运输层协议,都是为应用层程序服务,都具有复用(不同的应用层协议可以共用UDP协议和TCP协议)和分用(将数据报解析之后分发给不同的应用层程序)的功能。UDP提供面向无连接基于数据报的不可靠传输,TCP提供面向连接基于字节流的可靠传输。UDP在很多实时性要求高的场景有很好的表现,而TCP在要求数据准确、对速度没有硬性要求的场景有很好的表现。
加分回答
具体的区别详细描述可以是:
- UDP协议:面向无连接(不需要三次握手和四次挥手)、尽最大努力交付、面向报文(每次收发都是一整个报文段)、没有拥塞控制不可靠(只管发不管过程和结果)、支持一对一、一对多、多对一和多对多的通信方式、首部开销很小(8字节)。优点是快,没有TCP各种机制,少了很多首部信息和重复确认的过程,节省了大量的网络资源。缺点是不可靠不稳定,只管数据的发送不管过程和结果,网络不好的时候很容易造成数据丢失。又因为网络不好的时候不会影响到主机数据报的发送速率,这对很多实时的应用程序很重要,因为像语音通话、视频会议等要求源主机要以恒定的速率发送数据报,允许网络不好的时候丢失一些数据,但不允许太大的延迟,UDP很适合这种要求。
- TCP协议:是TCP/IP体系中非常复杂的一个协议,面向连接(需要三次握手四次挥手)、单播(只能端对端的连接)、可靠交付(有大量的机制保护TCP连接数据的可靠性)、全双工通讯(允许双方同时发送信息,也是四次挥手的原由)、面向字节流(不保留数据报边界的情况下以字节流的方式进行传输,这也是长连接的由来。)、头部开销大(最少20字节)。优点是可靠、稳定,有确认、窗口、重传、拥塞控制机制,在数据传完之后,还会断开连接用来节约系统资源。缺点是慢,效率低,占用系统资源高,在传递数据之前要先建立连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接。在要求数据准确、对速度没有硬性要求的场景有很好的表现,比如在FTP(文件传输)、HTTP/HTTPS(超文本传输),TCP很适合这种要求。
4.select的原理及缺点
以下解答来自牛客网(因为本人连题目都没有读明白)
select 是 一种 IO 多路复用技术,它的主旨思想是:
首先要构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中,这个文件描述符的列表数据类型为 fd_set,它是一个整型数组,总共是 1024 个比特位,每一个比特位代表一个文件描述符的状态。比如当需要 select 检测时,这一位为 0 就表示不检测对应的文件描述符的事件,为 1 表示检测对应的文件描述符的事件。
调用 select() 系统调用,监听该列表中的文件描述符的事件,这个函数是阻塞的,直到这些描述符中的一个或者多个进行 I/O 操作时,该函数才返回,并修改文件描述符的列表中对应的值,0 表示没有检测到该事件,1 表示检测到该事件。函数对文件描述符的检测的操作是由内核完成的。
select() 返回时,会告诉进程有多少描述符要进行 I/O 操作,接下来遍历文件描述符的列表进行 I/O 操作。
select 的缺点:
- 每次调用select,都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大;
- 同时每次调用 select 都需要在内核遍历传递进来的所有 fd,这个开销在 fd 很多时也很大;
- select 支持的文件描述符数量太小了,默认是 1024(由 fd_set 决定);
- 文件描述符集合不能重用,因为内核每次检测到事件都会修改,所以每次都需要重置;
- 每次 select 返回后,只能知道有几个 fd 发生了事件,但是具体哪几个还需要遍历文件描述符集合进一步判断。
5.算法题
5.1:需要几个主持人
有 n 个活动即将举办,每个活动都有开始时间与活动的结束时间,第 i 个活动的开始时间是 starti ,第 i 个活动的结束时间是 endi ,举办某个活动就需要为该活动准备一个活动主持人。
一位活动主持人在同一时间只能参与一个活动。并且活动主持人需要全程参与活动,换句话说,一个主持人参与了第 i 个活动,那么该主持人在 (starti,endi) 这个时间段不能参与其他任何活动。求为了成功举办这 n 个活动,最少需要多少名主持人。
解题思路
将活动开始时间写入一个列表starts,进行排序。
将活动结束时间写入一个列表ends,进行排序。
每次活动开始时,需要增加一个主持人上场,每次活动结束时候可以释放一个主持人。
所以按照时间先后顺序对starts进行遍历,每次有活动开始count++,每次有活动结束count--
在count最大的时候,即是需要主持人最多的时候
代码
public int minmumNumberOfHost (int n, int[][] startEnd) {
int[] starts = new int[n];
int[] ends = new int[n];
for (int i = 0; i < n; i++) {
starts[i] = startEnd[i][0];
ends[i] = startEnd[i][1];
}
Arrays.sort(starts);
Arrays.sort(ends);
int i = 0, j = 0, count = 0, res = 0;
for (int time : starts) {
while (i < n && starts[i] <= time) {
i++;
count++;
}
while (j < n && ends[j] <= time) {
j++;
count--;
}
if (res < count) {
res = count;
}
}
return res;
}
5.2:实现一个缓存结构(LFU)
一个缓存结构需要实现如下功能。
- set(key, value):将记录(key, value)插入该结构
- get(key):返回key对应的value值
但是缓存结构中最多放K条记录,如果新的第K+1条记录要加入,就需要根据策略删掉一条记录,然后才能把新记录加入。这个策略为:在缓存结构的K条记录中,哪一个key从进入缓存结构的时刻开始,被调用set或者get的次数最少,就删掉这个key的记录;
如果调用次数最少的key有多个,上次调用发生最早的key被删除
这就是LFU缓存替换算法。实现这个结构,K作为参数给出
若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
对于每个操作2,返回一个答案
解题思路:
用一个 LinkedHashMap进行保存, key为待存数据key, value整型数组 为 值和出现次数
代码:
import java.util.*;
public class Solution {
/**
* lfu design
* @param operators int整型二维数组 ops
* @param k int整型 the k
* @return int整型一维数组
*/
public int[] LFU (int[][] operators, int k) {
// write code here
Map<Integer, Integer[]> map = new LinkedHashMap<>(k);
List<Integer> res = new ArrayList<>();
for(int [] ops: operators) {
if(ops[0] == 1) {
// set
set(map, ops[1], ops[2], k);
}else if(ops[0] == 2) {
// get
res.add(get(map, ops[1]));
}
}
return res.stream().mapToInt(x -> x).toArray();
}
// set
private void set(Map<Integer, Integer[]> map, int key, int val, int cap) {
if(map.size() == cap) {
remove(map);
}
Integer[] value = map.get(key);
if(value != null) {
// exists
map.put(key, new Integer[]{val, value[1] + 1});
}else {
map.put(key, new Integer[]{val, 1});
}
}
// get
private Integer get(Map<Integer, Integer[]> map, int key) {
Integer[] value = map.get(key);
if(value == null) {
return -1;
}else {
map.put(key, new Integer[]{value[0], value[1] + 1});
return value[0];
}
}
// remove
private void remove(Map<Integer, Integer[]> map) {
int key = 0, min = Integer.MAX_VALUE;
for(Map.Entry<Integer, Integer[]> entry: map.entrySet()) {
if(entry.getValue()[1] < min) {
min = entry.getValue()[1];
key = entry.getKey();
}
}
map.remove(key);
}
}