JAVA面试题及答案
由于篇幅问题,这里的答案最只做最浅显的回答,更深入的答案自己百度
这些答案都是我本人总结出来的,如果有误,请回复我,我更正下,谢谢!
一、Java基础和高级
- String类为什么是final的;
主要是为了”安全性“和”效率“的缘故,因为:
- 由于String类不能被继承,所以就不会没修改,这就避免了因为继承引起的安全隐患;
- String类在程序中出现的频率比较高,如果为了避免安全隐患,在它每次出现时都用final来修饰,这无疑会降低程序的执行效率,所以干脆直接将其设为final一提高效率;
- HashMap的源码,实现原理,底层结构;
源码自己百度。
底层结构:JDK1.8之前是使用数组+链表,JDK1.8之后是使用数组+链表或红黑树。
- 反射中,Class.forName和Classloader的区别;
最重要的区别是 Class.forName 会初始化Class,而 Classloader 不会。
Classloader 只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。 forName("")得到的class是已经初始化完成的。
因此如果要求加载时类的静态变量被初始化或静态块里的代码被执行就只能用 forName,而用 Classloader 只有等创建类实例时才会进行这些初始化。
- session和cookie的区别和联系,session的生命周期,多个服务部署时session管理;
cookie存储在客户端,session存储在服务端,通常cookie中会持有session的句柄。
单客户端和服务端建立连接时创建session,通常在关闭连接后session失效,但是当客户端使用客户端保存的句柄重新连接时,能够重用session,这是需要主动刷新session,或者等服务端session过期。
- Java中的队列都有哪些,有什么区别;
-
未实现阻塞接口的:
LinkedList : 实现了Deque接口,受限的双向链表
PriorityQueue : 优先队列,本质维护一个有序列表。可自然排序亦可传递 comparator构造函数实现自定义排序。
ConcurrentLinkedQueue:基于链表 线程安全的队列。增加删除O(1) 查找O(n) -
实现阻塞接口的:
实现blockqueue接口的五个阻塞队列,其特点:线程阻塞时,不是直接添加或者删除元素,而是等到有空间或者元素时,才进行操作。
ArrayBlockingQueue: 基于数组的有界队列
LinkedBlockingQueue: 基于链表的无界队列
ProiporityBlockingQueue:基于优先次序的无界队列
DelayQueue:基于时间优先级的队列
SynchronousQueue:内部没有容器的队列 较特别 --其独有的线程一一配对通信机制
add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null
put 添加一个元素 如果队列满,则阻塞
take 移除并返回队列头部的元素 如果队列为空,则阻塞
- Java的内存模型以及GC算法;
内存模型:
JVM.png
GC算法:标记-清除(mark-and-sweep、Tracing)、标记-复制(Copying)、标记-整理(Compacting)
- Java7、Java8的新特性;
- jdk7,可以使用字符串控制switch语句;
- jdk7,带资源的try语句;
- jdk7,运用List<String> tempList = new ArrayList<>(); 即泛型实例化类型自动推断;
- jdk8,lambda表达式,函数式编程;
- ...
- Java数组和链表两种结构的操作效率,在哪些情况下(从开头开始,从结尾开始,从中间开始),哪些操作(插入,查找,删除)的效率高;
数组为连续空间,查找快,但是写入慢。链表是通过指针连接起来的非连续空间,查找慢,但是修改快。
- Java内存泄露的问题调查定位:jmap,jstack的使用等等;
- jmap用来查看堆内存使用状况
- jstack主要用来查看某个Java进程内的线程堆栈信息
- 开闭原则
开闭原则(OCP,The Open-Closed Principle)两个主要特征:
- 对扩展开放(open for extension):模块的行为的可以扩展的,当应用的需求改变时,可以对模块进行扩展。
- 对修改关闭(closed for modification):对模块进行扩展时,不必改动模块的源代码
开闭原则是面向对象设计中可复用设计的基石。
- 如何对集合进行排序
- Collections.sort(list),如果集合是对象,则需要继承Comparable<T>;
- Collections.sort(List<T> list, Comparator<? super T> c);
- 多线程中的忙循环是什么
忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存。
在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。
- 如何避免死锁
死锁的发生必须满足以下四个条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
两种种用于避免死锁的技术:
- 加锁顺序(线程按照一定的顺序加锁)
- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
- Runnable与Callable的区别
相同点:
- 两者都是接口;(废话)
- 两者都可用来编写多线程程序;
- 两者都需要调用Thread.start()启动线程;
不同点:
- 两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
- Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
二、spring框架
- spring框架中有哪些核心模块;
-
核心容器(Spring Core)
核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开。 -
应用上下文(Spring Context)
Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。 -
Spring面向切面编程(Spring AOP)
通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring框架中。所以,可以很容易地使 Spring框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。 -
JDBC和DAO模块(Spring DAO)
JDBC、DAO的抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理,和不同数据库供应商所抛出的错误信息。异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。 -
对象实体映射(Spring ORM)
Spring框架插入了若干个ORM框架,从而提供了ORM对象的关系工具,其中包括了Hibernate、JDO和 IBatis SQL Map等,所有这些都遵从Spring的通用事物和DAO异常层次结构。 -
Web模块(Spring Web)
Web上下文模块建立在应用程序上下文模块之上,为基于web的应用程序提供了上下文。所以Spring框架支持与Struts集成,web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。 -
MVC模块(Spring Web MVC)
MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。MVC容纳了大量视图技术,其中包括JSP、POI等,模型来有JavaBean来构成,存放于m当中,而视图是一个街口,负责实现模型,控制器表示逻辑代码,由c的事情。Spring框架的功能可以用在任何J2EE服务器当中,大多数功能也适用于不受管理的环境。Spring的核心要点就是支持不绑定到特定J2EE服务的可重用业务和数据的访问的对象,毫无疑问这样的对象可以在不同的J2EE环境,独立应用程序和测试环境之间重用。
- srpingMVC的原理;
- springMVC注解的意思;
@RequsetBody、@Autowired、@RequestMapping、@Controller、@Service
- Spring中BeanFactory和ApplicationContext的联系和区别;
BeanFactory在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化;
ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;
- spring注入的几种方式;
三种:
- 使用属性的setter方法注入 这是最常用的方式;
- 使用构造器注入;
- 使用Filed注入(用于注解方式).
- spring如何实现事物管理的;
- 编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。
- 基于 TransactionProxyFactoryBean的声明式事务管理
- 基于 @Transactional 的声明式事务管理
- 基于Aspectj AOP配置事务
- SpringIOC和AOP的原理;
IOC使用配置(XML、注解)将Bean的创建交给Spring容器控制,保证了松耦合;
AOP面向切面编程可以保证不同层面的逻辑分离,保证了代码的简洁,避免了业务的污染,例如:事务、日志;
- spring中循环注入的方式;
基于构造器的注入方式可能造成循环依赖,然后跑错,尽量避免这种用法。
- Spring的事务隔离级别,实现原理;
-
spring boot特性,优势,适用场景等;
简化编码、简化配置、简化部署、简化监控 -
spring boot的模块分类
- spring-boot-starters
- spring-boot-actuator
- spring-boot-starter-web
- spring-boot-starter-test
- spring-boot-starter-jdbc
- spring-boot-starter-security
- spring-boot-starter-aop
- ...
三、java多线程常见问题
- Java创建线程之后,直接调用start()方法和run()的区别;
run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用;
start()方法:启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程;
- 常用的线程池模式以及不同线程池的使用场景;
-
newCachedThreadPool:
底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
适用:执行很多短期异步的小程序或者负载较轻的服务器 -
newFixedThreadPool:
底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
通俗:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:执行长期的任务,性能好很多 -
newSingleThreadExecutor:
底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:一个任务一个任务执行的场景 -
newScheduledThreadPool:
底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
适用:周期性执行任务的场景
- newFixedThreadPool此种线程池如果线程数达到最大值后会怎么办,底层原理;
线程池大小不变。
-
多线程之间通信的同步问题,synchronized锁的是对象,衍伸出和synchronized相关很多的具体问题,例如同一个类不同方法都有synchronized锁,一个对象是否可以同时访问。或者一个类的static构造方法加上synchronized之后的锁的影响;
-
了解可重入锁的含义,以及ReentrantLock 和synchronized的区别;
可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁。
- 同步的数据结构,例如ConcurrentHashMap的源码理解以及内部实现原理,为什么他是同步的且效率高;
ConcurrentHashMap,在1.7中使用分段锁保证线程安全,在1.8中使用CAS原子操作保证线程安全。
- atomicinteger和volatile等线程安全操作的关键字的理解和使用;
- 进程间通信;
- 管道:速度慢,容量有限,只有父子进程能通讯;
- FIFO:任何进程间都能通讯,但速度慢;
- 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题;
- 信号量:不能传递复杂消息,只能用来同步;
- 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存;
四、网络通信
- http是无状态通信,http的请求方式有哪些,可以自己定义新的请求方式么;
OPTIONS、HEAD、GET、POST、PUT、DELETE、TRACE、CONNECT
- socket通信,以及长连接,分包,连接异常断开的处理;
- socket通信模型的使用,AIO和NIO;
- socket框架netty的使用,以及NIO的实现原理,为什么是异步非阻塞;
- 同步和异步,阻塞和非阻塞;
五、常用Linux命令
- 常用的linux下的命令;
- 大的log文件中,统计异常出现的次数、排序,或者指定输出多少行多少列的内容;
- linux下的调查问题思路:内存、CPU、句柄数、过滤、查找、模拟POST和GET请求等等场景;
- shell脚本;
六、数据库MySql
- MySql的存储引擎的不同;
- 单个索引、联合索引、主键索引;
- Mysql怎么分表,以及分表后如果想按条件分页查询怎么办(如果不是按分表字段来查询的话,几乎效率低下,无解);
- 分表之后想让一个id多个表是自增的,效率实现;
- MySql的主从实时备份同步的配置,以及原理(从库读主库的binlog),读写分离;
- 事物的四个特性,以及各自的特点(原子、隔离)等等,项目怎么解决这些问题;
七、设计模式(写代码)
- 单例模式:饱汉、饿汉。以及饿汉中的延迟加载;
- 工厂模式、装饰者模式、观察者模式等;
八、算法&数据结构&设计模式
- 使用随机算法产生一个数,要求把1-1000W之间这些数全部生成。(考察高效率,解决产生冲突的问题);
- 两个有序数组的合并排序;
- 一个数组的倒序;
- 计算一个正整数的正平方根;
- 说白了就是常见的那些查找排序算法;
- 数组和链表数据结构描述,各自的时间复杂度;
- 二叉树遍历;
- 快速排序;
- BTree相关的操作;
- 在工作中遇到过哪些设计模式,是如何应用的;
- hash算法的有哪几种,优缺点,使用场景;
- 什么是一致性hash;
- paxos算法;
九、分布式缓存
-
为什么用缓存,用过哪些缓存,redis和memcache的区别;
-
redis的数据结构;
string、hash、list、set、zset
- redis的持久化方式,以及项目中用的哪种,为什么;
RDB、AOF
- redis集群的理解,怎么动态增加或者删除一个节点,而保证数据不丢失。(一致性哈希问题);
如果要添加的新节点是一个主节点, 那么我们需要创建一个空节点(empty node), 然后将某些哈希桶移动到这个空节点里面。
另一方面, 如果要添加的新节点是一个从节点, 那么我们需要将这个新节点设置为集群中某个节点的复制品(replica)。
十、线程池、高并发、NIO
- 分析线程池的实现原理和线程的调度过程;
所有线程池的工作方式本质是一样的:有一个任务队列,一定数量的线程会从该任务队列获取任务然后执行。任务的结果可以发回客户端,或保存到数据库,或保存到某个内部数据结构中,等等。但是在执行完任务后,这个线程会返回任务队列,检索另一个任务并执行。
线程池有最小线程数和最大线程数。池中会有最小数目的线程随时待命,等待任务指派给它们。因为创建线程的成本非常高昂,这样可以提高任务提交时的整体性能。线程池的最小线程数称作核心池大小,考虑ThreadPoolExecutor最简单的情况,如果有个任务要执行,而所有的并发线程都在忙于执行另一个任务,就会启动一个新线程,直到创建的线程达到最大线程数。
-
线程池如何调优;
设置最大线程数、设置最小线程数、线程池任务大小、设置ThreadPoolExecutor的大小。
- 线程池的最大线程数目根据什么确定;
IO密集型=2Ncpu(可以测试后自己控制大小,2Ncpu一般没问题)(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)
计算密集型=Ncpu(常出现于线程中:复杂算法)
java中:Ncpu=Runtime.getRuntime().availableProcessors()
- 动态代理的几种方式;
JDK动态代理(Spring默认使用)、Cglib动态代理。
- HashMap的并发问题;
HashMap线程不安全,多线程需要自己控制并发,非常困难,可以使用ConcurrentHashMap替代。
-
了解LinkedHashMap的应用吗;
LinkedHashMap是HashMap的子类,所以特性和HashMap一致,不同的是LinkedHashMap是有序的,和你添加时的顺序一致。 -
反射的原理,反射创建类实例的三种方式是什么;
- Class clazz = p.getClass();
- Class clazz = Person.class;
- Class clazz = Class.forName("cn.xxx.Person");
-
cloneable接口实现原理,浅拷贝or深拷贝;
-
Java NIO使用;
-
hashtable和hashmap的区别及实现原理,hashmap会问到数组索引,hash碰撞怎么解决;
-
arraylist和linkedlist区别及实现原理;
-
反射中,Class.forName和ClassLoader区别;
-
String,Stringbuffer,StringBuilder的区别;
-
有没有可能2个不相等的对象有相同的hashcode;
-
简述NIO的最佳实践,比如netty,mina;
-
TreeMap的实现原理;
十一、JVM相关(面试必考)
-
JVM内存分代;
-
Java 8的内存分代改进;
移除了永久代,添加了元空间(Metaspace),元空间存放在本地内存空间。 -
JVM垃圾回收机制,何时触发MinorGC等操作;
当 eden 区内存无法为一个新对象分配内存时,就会触发 Minor GC。 -
jvm中一次完整的GC流程(从ygc到fgc)是怎样的,重点讲讲对象如何晋升到老年代,几种主要的jvm参数等;
-
你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms,g1;
-
新生代和老生代的内存回收策略;
-
Eden和Survivor的比例分配等;
8:1:1 -
深入分析了Classloader,双亲委派机制;
双亲委派解决类重复加载的问题。
classloader.png-
JVM的编译优化;
-
对Java内存模型的理解,以及其在并发中的应用;
-
指令重排序,内存栅栏等;
在计算机执行指令的顺序在经过程序编译器编译之后形成的指令序列,一般而言,这个指令序列是会输出确定的结果;以确保每一次的执行都有确定的结果。但是,一般情况下,CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序,在并发执行情况下,会发生二义性,即按照不同的执行逻辑,会得到不同的结果信息。
内存栅栏可以阻止指令重排序、强制将缓存的修改立即同步到主存。
- OOM错误,stackoverflow错误,permgen space错误;
stackoverflow通常出现在方法执行过程栈太深,到时栈内存被耗尽,比如:递归调用。
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,这一部分用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和存放Instance的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。
解决办法,调整JVM启动配置,加大永久代内存空间。
- JVM常用参数;
-Xms:初始堆大小
-Xms:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3表示年轻代和年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。
-XX:MaxPermSize=n:设置持久代大小
十二、分布式相关
-
Dubbo的底层实现原理和机制;
-
描述一个服务从发布到被消费的详细过程;
-
分布式系统怎么做服务治理;
-
接口的幂等性的概念;
-
消息中间件如何解决消息丢失问题;
-
Dubbo的服务请求失败怎么处理;
-
重连机制会不会造成错误;
-
对分布式事务的理解;
-
如何实现负载均衡,有哪些算法可以实现;
-
Zookeeper的用途,选举的原理是什么;
-
数据的垂直拆分水平拆分;
-
zookeeper原理和适用场景;
-
zookeeper watch机制;
一个Watch事件是一个一次性的触发器,当被设置了Watch的数据发生了改变的时候,则服务器将这个改变发送给设置了Watch的客户端,以便通知它们。 -
redis/zk节点宕机如何处理;
-
分布式集群下如何做到唯一序列号;
-
如何做一个分布式锁;
-
用过哪些MQ,怎么用的,和其他mq比较有什么优缺点,MQ的连接是线程安全的吗;
-
MQ系统的数据如何保证不丢失;
-
列举出你能想到的数据库分库分表策略;分库分表后,如何解决全表查询的问题;
分库分表后,如何解决全表查询的问题:冗余、业务系统组合
十三、数据库
-
MySQL InnoDB存储的文件结构;
-
索引树是如何维护的;
-
数据库自增主键可能的问题;
-
MySQL的几种优化;
-
mysql索引为什么使用B+树;
- B+TREE的磁盘读写低价更低;
- B+TREE的查询效率更加稳定;
- B+TREE更有利于对数据扫描;
- 数据库锁表的相关处理;
在联机事务处理系统中,造成死机主要有两方面原因。一方面,由于多用户、多任务的并发性和事务的完整性要求,当多个事务处理对多个资源同时访问时,若双方已锁定一部分资源但也都需要对方已锁定的资源时,无法在有限的时间内完全获得所需的资源,就会处于无限的等待状态,从而造成其对资源需求的死锁。
- 索引失效场景;
- 高并发下如何做到安全的修改同一行数据,乐观锁和悲观锁是什么,INNODB的行级锁有哪2种,解释其含义;
- 数据库会死锁吗,举一个死锁的例子,mysql怎么解决死锁;
十四、Redis&缓存相关
-
Redis的并发竞争问题如何解决了解Redis事务的CAS操作吗;
-
缓存机器增删如何对系统影响最小,一致性哈希的实现;
-
Redis持久化的几种方式,优缺点是什么,怎么实现的;
-
Redis的缓存失效策略;
-
缓存穿透的解决办法;
-
redis集群,高可用,原理;
-
mySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据;
限定 Redis 占用的内存,Redis 会根据自身数据淘汰策略,留下热数据到内存。所以,计算一下 20w 数据大约占用的内存,然后设置一下 Redis 内存限制即可,并将淘汰策略为volatile-lru或者allkeys-lru。
- 用Redis和任意语言实现一段恶意登录保护的代码,限制1小时内每用户Id最多只能登录5次;
- redis的数据淘汰策略;