2020面试题题目和答案

什么事情都有开始,人生中第一篇博客。梳理知识,再接再厉

1、设计一个黑白名单工具类,尽可能存储多ip
请实现一个IP白名单过滤算法,实现以下接口
boolean addWhiteIpAddress(String ip);
boolean isWhiteIpAddress(String ip);
要求如下:
占用空间尽量少
运算效率尽量高
在内存中完成查询及判断
接口可能被并发询问
尽量能存储整个IP地址空间
代码可运行,且包含单测
2、子类实现了finalize的方法,理解finalize只执行一次

/**

  • 执行结果:
    */
    public class FinalizeEscapeGC {
    public static FinalizeEscapeGC instance = null;

public void isAlive() {
System.out.println(“yes,i am still alive”);
}

@Override
protected void finalize() throws Throwable {
// super.finalize();
System.out.println(“finalize methode executed”);
instance = this;
}

public static void main(String[] args) throws InterruptedException {
instance = new FinalizeEscapeGC();

instance = null;
System.gc();
Thread.sleep(1000);
instance.isAlive(); //在没有重写finalize方法时,肯定是会报nullpointerException的

instance = null;
System.gc();
Thread.sleep(1000);
if (instance == null) {
  System.out.println("i am dead;");

} else {
  System.out.println("i am still  live ;");
}

}
}

3、如何实现接口的冥等,有哪些解决方案?
算法题目,合并2个list,要求时间复杂度为2n.

public class Demo {

public static List> merge(List> List1, List> List2, String filedName) {
Map> map = new HashMap<>();
int len1 = List1.size();
int len2 = List2.size();

for (int i = 0; i < len1; i++) {
  Map tmpMap = List1.get(i);
  map.put(tmpMap.get(filedName).toString(), tmpMap);
}

for (int i = 0; i < len2; i++) {
  Map tmp = List2.get(i);
  if (map.containsKey(tmp.get(filedName))) {
    Map map1 = map.get(tmp.get(filedName));
    map1.putAll(tmp);
  }
}
return List1;

}

}

4、实现一一个使用读写锁实现的缓存,用在写少读多的场景,考察锁升级和降级
Implement a cache class (CachedData) that caches the target data, and implement a method (processCachedData), which process the data if the cache is valid, if not, first update the cache object according to its data source, then process it.

Requirments:

  • Use ReentrantReadWriteLock to implement above requirements.

  • Use lock downgrading

锁降级和升级
lock实现机制

package com.demos;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CachedData {
private static Map cacheData = new HashMap();//构造缓存对象
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();//构造读写锁

public Object processCachedData(String key) {
Object value = null;
try {
rwl.readLock().lock();//当线程开始读时,首先开始加上读锁
value = cacheData.get(key);//获取值
if (value == null) {//判断是否存在值
try {
rwl.readLock().unlock();//在开始写之前,首先要释放读锁,否则写锁无法拿到
rwl.writeLock().lock();//获取写锁开始写数据
/* 再次判断该值是否为空,因为如果两个写线程如果都阻塞在这里,当一个线程
* 被唤醒后value的值不为null,当另外一个线程也被唤醒如果不判断就会执行两次写
*/
if (value == null) {
value = “queryDB”;
cacheData.put(key, value);
}
rwl.readLock().lock();//写完之后重入降级为读锁
} finally {
rwl.writeLock().unlock();//最后释放写锁
}
}
} finally {
rwl.readLock().unlock();//释放读锁
}
return value;
}
}
参考知识点:
也就是说,在另一个线程(假设叫线程1)修改数据的那一个瞬间,当前线程(线程2)是不知道数据此时已经变化了,但是并不意味着之后线程2使用的数据就是旧的数据,相反线程2使用还是被线程1更新之后的数据。也就是说,就算我不使用锁降级,程序的运行结果也是正确的(这是因为锁的机制和volatile关键字相似)。
那么为什么还要锁降级呢,其实目的是为了减少线程的阻塞唤醒。明显当不使用锁降级,线程2修改数据时,线程1自然要被阻塞,而使用锁降级时则不会。“感知”其实是想强调读的实时连续性,但是却容易让人误导为强调数据操作。

首先你没理解读写锁的意义,读锁的存在意味着不允许其他写操作的存在。
按照你提供的例子,可能存在一个事务线程不希望自己的操作被别的线程中断,而这个事务操作可能分成多部分操作更新不同的数据(或表)甚至非常耗时。如果长时间用写锁独占,显然对于某些高响应的应用是不允许的,所以在完成部分写操作后,退而使用读锁降级,来允许响应其他进程的读操作。只有当全部事务完成后才真正释放锁。
按你的理解如果当中写锁被其他线程占用,那么这个事务线程将不得不中断等待别的写锁释放。

所以总结下锁降级的意义应该就是:在一边读一边写的情况下提高性能。

5、手写一个队列,主要考察数组和锁机制,乐观锁和悲观锁都可以使用

Implement a Queue of your own design with an array or stack respectively, including push, pop, peek, empty methods.

synchronized
volatile
pop
peek

package com.demos;

public class StackDemo {
private Object[] array;
private volatile int topIndex = -1;

public StackDemo(int length) {
array = new Object[length];
}

public synchronized void push(Object obj) {
if (topIndex == (array.length - 1)) {
System.out.println("Stack is full ");
return;
}
array[++topIndex] = obj;
}

public synchronized Object peek() {
if (topIndex == -1) {
System.out.println("Stack is empty ");
return null;
}else{
return array[array.length - 1];
}

}

public synchronized Object pop() {
if (topIndex == -1) {
System.out.println("Stack is empty ");
return null;
}
Object topObject = array[topIndex];
array[topIndex] = null;
topIndex–;
return topObject;
}

public synchronized boolean empty() {
int size = array.length;
for (int i = 0; i < size; i++) {
array[i] = null;
}
return true;
}

}

6、
static final 分别修饰变量和类变量的区别
设计一个字符串转换器—springboot转换器 convertor,统一转换,比如日期戳转成日期格式
设计一个用户行为分析轨迹,比如某个用户操作了哪些菜单
如何做用户行为路径分析?前端埋点,后端行为日志
设计一个错误登录次数
设计登录鉴权系统
springmvc原理

7、
数据库索引前置规则:
多列索引情况下,按照前面的索引顺序组合

事务异常回滚机制,可配置哪些异常回归
Spring 事务的回滚机制主要采用异常回滚,如果某个方法不需要回滚就采用try catch机制捕获异常
另外一种方式是配置相关的异常,不让回滚

8、
JVM怎样判断两个类是否相同,对比Class对象
考察:对比2个Class,需要了解java类加载机制,不同的加载器加载相同的类,在JVM中呈现的Class对象是不同的,所以2个类比较除了权限名要一致以外,还需要是同一个类加载器加载

类加载和热加载机制
redis实现数据备份的机制,redis如何实现分布式锁
redis实现数据持久化的2中机制:
1.类似数据库中的写日志
2.快照方式
线程启动的方式和区别
1、通过start机制启动;
2、通过线程池启动
区别是线程池直接使用while调用队列中的task,然后直接调用run方法运行

volatile作用
内存可见,就是每次线程自己的缓存中的数据读取都取主存中读取。

linux docker机制
docker实现机制
k8s基本组件
synchronized和cAS区别
二分归并算法
双向链表编程实现排序插入

常用的设计模式:

创建型模式

单例 双重锁实现方式
简单工厂
抽象工厂

组合模式:

行为模式

代理
动态代理
适配器模式
责任链模式
门面模式
装饰器模式

嵌套类和内部类的访问范围报考
抽象类和接口的区别,是否可以包括private
动态加载类的方式

实现一个采集系统架构图,hash一致性算法实现负载均衡
包括:网关,采集服务,发送命令服务,鉴权,负载均衡

实现负载均衡有哪些算法?
kafka的高可用设计方式,topic partion
k8s高可用实现方式

你可能感兴趣的:(2020面试题题目和答案)