赞
踩
有的面试会被问到有没有写博客,这时候我尴尬,不知道怎么回答,所以这篇文章仅仅是把我面试JAVA的遇到的问题记录下来而已,也算是我写博客迈出的第一步,起码,以后被问到:有没有写博客?我可以回答,我写过。 (最主要的是以后换工作我不用频繁百度常见面试题了。。。。)
1,别把我太当回事,我是个LJ;
2,说得不对的地方请多多包涵,想看更详细的请百度官方文档和其他大佬的文章;
3,如果有被问到了,我这上面没有的话,也可以提出来,我去学习,补上去,谢谢各位大哥。
浏览器提交http请求–>提交到DispatcherServlet–>根据配置文件找到请求对应的HandlerMapping–>找到对应的Controller–>Controller执行Service层业务代码–>返回结果封装到ModelAndView–>再次根据DispatcherServlet找到对应的视图解释器–>数据封装到Model中–>返回给JSP–>http请求返回给浏览器
IOC简称:控制反转,又被叫做依赖注入(DI),其作用是把创建对象交由Spring工厂完成,避免了程序员创建对象的麻烦。
其实现大致分为以下四个步骤:
1,加载Spring容器BeanFactory创建Resource对象。
加载Spring的容器BeanFactory在其构造方法创建实现了Resource接口的对象的实例,Resource对象创建成功后会调用getConfiguration()方法获取到Spring配置文件的位置。
Resource的实现分为三种FileResource,UrlResource,ClassPathResource,分别对应获取文件,网络资源,项目路径下资源。
Resource创建如果有父类就使用父类的容器,如果没有就创建新的容器。
2,提取验证模式
BeanFactory会调用getInputStream()方法获取到配置文件的输入流,在加载前,会以DTD或XSD,通过头标签验证配置文件的正确性和完整性。
3,提取内容
把配置文件转换成一个document对象,这个document对象把类的内容转换成Spring的特殊结构BeanDefinition,BeanDefinition中包涵了Bean的所有信息,例如:是否懒加载,是否单例,是否抽象类,是否私有类等等。
4,注册
把所有的Bean装到ConcurrentHashMap中,以BeanName作为key,BeanDefinition作为Value。其中,如果BeanName重复,并且Spring不允许重复的话,那么就会报错,否则就会覆盖。
AOP简称:面向切面编程,其主要应用在业务贯穿了整个系统的时候,例如事务的控制,权限,安全,日志。
它的核心归纳为:切面,切点,目标对象,连接点,增强,引入,植入。
通知的方式又分为五种:前置通知,后置通知,环绕通知,异常通知,返回后通知。
加载的方式有两种:动态加载和cglib加载。
我利用AOP做过日志的管理,采用监听方法的调用去实现的。但是有缺点:每个人写的方法名不一样,有的时候监听不到方法的调用,所以我当时规定了起名规则,它的优点就是不需要去写自定义注解,可以少些代码,因为方法名的约束在所以提高代码后续的可读性。
第二种方法就是自定义注解去实现日志录入,优点就是灵活多变,缺点就是每个方法都要加这个注解。
mybatis会加载SqlSessionFactory容器,每次请求都会获取到一个SqlSession,由SqlSession建立起与数据库的会话,并把sql传入数据库执行,数据库执行后把结果返回,其中,也涉及到了mybatis一级缓存的调用(如果表的结构或数据没发生改变,并且SQL重复执行,那么查询的时候优先把一级缓存中的数据返回,而不是请求数据库查询SQL语句),每次查询的结果其实都会存到一级缓存中,mybatis默认开启了一级缓存,二级缓存则需要通过修改配置文件开启,二级缓存是针对Mapper的,主要是多个
SqlSession共享同一个Mapper。(这里我也把mybatic的一级缓存和二级缓存大概讲了一下)
数据库方面也涉及到了CAP三大定律,即:一致性(C),可用性(A),分区容忍性(P)。
其中:传统型数据库,如MySql遵循的是CA,保证了一致性和可用性;KV结构的数据库满足AP,可用性与分区容忍性。
传统型数据库的优势在于:高性能,高拓展,高可用,能始终保证数据的一致性。缺点是当表数据量过大(五百万条或2.5G大小)的时候要考虑采用缓存,或者主从复制,读写分离,异构复制等数据库层次的优化操作。
NoSQL优势在于:体积小,速度快,成本低;缺点是很难保证数据的一致性,只能在业务层的代码做约束,但是根据墨菲定律,不管业务做得再严谨,一定会有脏数据产生,所以要用数据检查去补偿。其结构为Base模型(基本可用,软状态)。
事务的四大特性为:原子(事务的操作是原子性的),一致(数据前后保持一致),隔离(事务的操作互不影响),持久(一旦持久化则不可回滚)。
事务的隔离级别为:脏读,不可重复读,幻读。
数据库事务的又分为:Default(默认级别),uncommit,commit(解决脏读),read(解决不可重复读),Serializable (解决幻读),事务的隔离级别设置得越高,数据库的性能就越低,所以一般只设置到commit级别。
1、关于字段的类型
人类的岁数设置为tinyint,龟的岁数设置为smallint,行星的寿命设置为int,宇宙的寿命设置为bigint;
涉及到金钱的使用decimal,因为fload,double有精度的缺失;
尽量不适用text,除非字节数超过了5000,否则都是使用varchar;
2、关于字段名
判断的字段的起名为is_xxx;
3、关于索引
索引我也不会多建,过多的索引会对查询速度产生影响
4、关于设置默认值
推荐字段设置默认值,可以很好避免程序运行时的空指针异常
5、关于必有字段
创建时间,修改时间,修改人
索引常见的有主键索引、普通索引、唯一索引、全文索引、聚合索引。
聚合索引在使用的时候where后字段的顺序尽量与索引的顺序一致,否则可能会失效,并且聚合索引不能为null;
使用聚合索引,应把标识读最高的字段放在最左边,因为字段的匹配从左到右;
索引在使用的时候要注意where后字段的类型是否与索引的字段的类型一致,不一致会失效,例如索引的字段类型是varchar,但是where后的字段不用单引号包起来,这样索引会失效;
不在where后直接参与运算;
一条SQL的join不应该超过五条,否则可能会影响查询速度;
SQL的优化可以用解释计划去分析,主要看以下两列:
type 这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const(最好)、eq_reg、ref(标准)、range(最低要求)、indexhe和ALL
Extra 关于MYSQL如何解析查询的额外信息。这里如果出现Using filesort和Using temporary,就要优化了。前者是数据库需要进行额外的操作发现如何对返回的行排序,后者是MYSQL需要创建一个临时表来存储结果,这通常发生在对不同的列集进行ORDER BY上,而不是GROUP BY上
1,不要用*号,而是查什么字段就写什么字段;
2,用>=,<=代替>,<;
3,用exists代替in;
4,用union代替or;
5,>=,<=不直接写在XML文件中,而是用转义字符代替;
6,#和$的区别就是#能防止SQL注入,另一个则不能,常用语表名;
7,在XML中获取当前时间不能使用NOW(),而是传时间戳进来;
8,尽量不要用count(1)或count(字段)代替count(星号)去统计行数,因为count(1)或count(字段)不会统计字段为空的行,并且也不符合数据库范式
9,尽量不要joib超过3张表,并且joib的字段保证有索引
秒杀的步骤为:
1,验证时间;
2,输入验证码,这一步主要是时间换取空间,换取后台代码的执行时间;
3,用户点击按钮后,按钮变灰,主要是防止用户重复点击,也只能对‘小白’起作用,其中还有限制IP,限制账号,分析用户行为等等;
4,验证库存,秒杀成功后库存-1,其中使用的是乐观锁,主要不认为会产生数据的冲突,因为并发量没那么高(如果出现商品超卖,就使用redis自旋锁或jedis的分布式锁配合数据库的乐观锁,考虑使用 sentinel对接口进行限流减少并发量);
5,用户秒杀成功后,但是五分钟内不付款,视为放弃放弃本次优惠,库存+1,解决办法有三种。第一种:使用rabbitMQ的延时队列,设置消息的发送时间为5分钟后,接收到消息后,根据订单ID查询最新的订单状态,如果未支付,则库存+1,逻辑删除订单。第二种:采用定期数据检查,常见的就是xxl-job每隔30S或60S查询一次,如果未支付,则库存+1,逻辑删除订单。第三种:懒策略方式,用户查询订单列表时,在返回数据前,对所有的订单进行判断,如果订单过期并且状态为未支付,库存+1,修改订单状态为逻辑删除,返回的LIST删除这条记录。
在多线程中,可能会出现并发和并行。
并行:真正意义上的同一时间,两个或两个以上的线程争夺资源;
并发:根据CPU的调度算法, 使得用户觉得是在同一时间出现了争夺资源,但其实不是同一时间。
线程的状态有两种sleep和wait。
sleep:不放弃资源,等到时间后会再次执行业务代码;
wait:完全放弃资源,除非被notify或者notifyAll重新给予资源才会执行业务代码。
实现线程的方式分为:继承Thread,实现Runnable或Callable接口。
Java中单一继承多实现,所以一般不会继承Thread;Runnable和Callable的区别则是Callable可以捕获到异常。
加锁可以使用:threadlocal或synchonized。
threadlocal解决数据的一致性问题,因为访问的是镜像副本,不是同一个数据源;synchonized解决数据的同步问题。
String的底层是数组结构,长度一旦确定不可改变,出现更改也是创建一个新的模型,然后把新的数据装进去,并且指针指向这个地址;
StringBuilder是线程不安全的容器,存储的数据超过了其默认的容器大小则会自动扩容;
StringBuffer与StringBuilder机制一样,但是StringBuffer是线程安全的容器;
线程安全的容器:vector,HashTable,ConcurrentHashMap等
线程不安全的容器:ArrayList,HashMap,StringBuilder,LinkList等
使用不安全的容器例如:ArrayList,循环插入一百次记录,ArrayList可能会出现角标越界异常。可以通过Collections.synchronizedList(XXX)把它变成线程安全的容器。
五种,分别是List,String,Set,SortSet,Hash。
1,memcache只能存储KV结构的数据或音像文件,Redis除了KV数据还有上面说的五种可以存;
2,memcache必须设置数据的过期时间,但Redis对数据的过期时间不强制要求设置;
3,在容灾上memcache的数据丢了就没了,Redis可以主从复制
4,Redis可以持久化数据到本地文件里
Session的生命周期在访问JSP,Servlet等动态资源的时候创建,访问HTML,CSS等静态资源不会创建,除非强行创建;在关闭浏览器或当前窗口消失;服务器会定期清理掉不再活跃的Session,以tomcat为例是每30分钟清理一次;
Cookies默认的age为-1,可以修改持久化到本地文件,可以存数据。
Linux系统下查询日志有tail查询实时日志,sed根据时间筛选日志。Jenkies在浏览器也可以查询实时日志,用以复现BUG。腾讯云的日志每一个只有100M大小,一天只有十个日志文件。根据公司的日志架构与BUG的复杂度决定采用什么方式去查日志,能复现的尽量复现,不能复现的查日志。让负责该业务的人员进行BUG的修改,或者协助其修改。
Zookeeper是一个分布式协调中心。
Dubbo是一个高性能,轻量级的JAVA的RPC框架,其核心分为三类,1:远程调用;2,智能容错和负载平衡;3,服务的注册与发现。Dubbo的服务调用分为,1,消费者直连生产者;2,服务注册到注册中心,消费者到注册中心请求获取服务,注册中心返回服务给消费者,监控中心则是监控时间段内的服务调用与提供次数等记录。
eureka是SpringCloud的注册中心,它有自己的容错机制,eureka不会移除掉未监听到心跳的服务,网络波动时返回最近的数据,网络恢复正常会获取最新的服务;Dubbo采取过半节点的选举方式容错。
SpringBoot具有内置的服务器,其加载方式为:加载所有的Class文件,如果有SpringMVC的JAVA文件,会加载SpringMVC的容器,当所有文件加载完成后,可以直接访问项目。
SpringMVC需要配置web.xml文件,要配置jsp等页面资源,但SpringBoot省略了这部分的配置,因为它是http+rest+API模式,更符合微服务的思想。
Feign:动态代理,默认集成Ribbon,具有负载均衡;
Hystrix:断容器,主要是业务是由多个服务组成的时候,某个服务出现异常,为了防止‘雪崩’,servlet资源耗尽,会返回给用户一个fallBack;
Ribbon:负载均衡;
eureka:起到注册中心的作用;
Zuul:网关;
Steam流,Lambada,LocalDateTime,List.of(),Map.forEach(),switch的新语法;
Docker主要由仓库,镜像,容器三部分组成;它们间的关系为,镜像可以pull获取到仓库的资源,push把镜像提交到仓库;镜像通过run或start创建容器(run是新建容器并启动,start是启动已有的但停止的容器),容器通过commit生成镜像。
FROM 命令说明镜像来源于哪里;
run rm -rf 命令删除容器;
copy 命令把某位置的文件拷贝到哪里;
还有一些其他常用的命令,如docker image,docker ps ,docker log 等等
执行多段命令可以使用DockerFile,DockerFile需要去掉文件后缀。
一般来说,对数据的修改在主库,读在从库;其原理大致为:数据在主库发生更改,会有binlog记录本次的操作,当从库知道主库更改了数据,会从binlog拿到这次操作的记录并进行同样的操作,所以,每次当主库发生了数据的更改,从库也会同步进行更改。
类加载的顺序是:
加载–>验证–>准备–>解析–>初始化–>使用–>销毁
分别对应:
1,把JAVA文件加载成二进制数据;
2,验证文件的正确性和完整性;
3,为静态变量分配内存;
4,对符号进行转义;
5,初始化类,由ClassLoader及其子类完成;
6,使用这个类;
7,GC
先跟自己比–>再比较类型(是不是String类型)–>再比较长度–>再比较字节;
TCP:安全,建立请求需要三次握手,断开要四次,速度慢,常用于视频,下载等;
UDP:不安全,不需要握手,速度快,常用于发送图片,文字等。
最好不要(其实是强制)在XML的SQL中写>=或者<=或者&,要使用转义字符
按照生命周期从强到弱,分别为:强引用、软引用、弱引用、虚引用。
强引用对象哪怕JVM内存溢出都不会被JVM回收;
软引用对象会在内存不足时被回收,所以常用于缓存;
弱引用对像会在JVM垃圾回收被回收,也可以用来做缓存;
虚引用对像被回收时间未知,生存时间也未知,所以用途也是未知。
共有五种,堆、栈、方法区、程序计数器、本地方法栈
它们的作用分别是:
堆:用来存储对象本身以及数组(数组引用是存放在Java栈中的)。堆是被所有线程共享的,在JVM中只有一个堆。
栈:Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法。
方法区:与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息、静态变量、常量以及编译器编译后的代码等。
程序计数器:保证程序的正常执行次序
本地方法栈:与栈类似,区别是栈为执行Java方法服务的,而本地方法栈则是为执行本地方法服务的
新域、旧域、永久域
新域:刚创建对象时,其所处的区域;
旧域:被一次引用后,继续被使用,没有被GC的对像所处的区域;
永久域:固定大小,初始为4M;运行程序时,会动态调整大小以满足需求,每次调整,JVM会对堆进行一次完全的垃圾收集。
为了能够更好的进行GC
缓存穿透的场景就是,某个用户用id=-1或请求一个不存在的缓存。
解决办法是:
1、对于id<=0的请求直接拦截;
2、对不存在的缓存可以设置一个其值等于null,然后过期时间设置30秒。
缓存击穿的场景就是,缓存中没有数据,但是数据库有数据,这时候并发又特别多,从而请求数据库,导致数据库压力过大,出现这种情况,一般都是缓存中数据过期。
解决办法是:
1、对热点数据设置不过期时间
2、加一个互斥锁,如果缓存中没有数据,请求数据库时,其他请求要进行等待,等待缓存写入后再继续获取数据。
缓存雪崩的场景就是,某个时间段内,大批缓存失效了,是因为,这些缓存都设置同一个过期时间,到期后,缓存统一失效,大量请求直接请求数据库,导致数据库压力过大。
解决办法是:
1、对缓存数据设置随机的过期时间,避免同一时间大批量缓存过期;
2、如果数据库是分布式部署,就把热点数据均匀地分布在不同的数据库;
3、设置热点数据永不过期
Finchley版本
如果是RabbitMQ,生产者开启confirm模式,开启RabbitMQ持久化,关闭RabbitMQ的自动ack
如果是kafka,设置ack=all,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。
主要是保证多条请求进来只处理一条请求即可,可以考虑选择互斥锁,例如redis就是天然的幂等;
只要你能保证一个队列只被一个消费者消费,自然就可以保证消息的顺序性
临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据。等快速消费完积压数据之后,得恢复原先部署架构
如果是因为消息积压导致的消息过期,例如RabbitMQ设置了过期时间。那么,就只能够写个程序把这些数据重新查询出来再写入到mq里去消费。
三个,生产者,消费者,代理。
生产者是消息的创建者,负责创建和推送消息到消息服务器;
消费者是消息的接收方,负责处理数据和接收消息;
代理是RabbitMQ本身,只负责扮演快递的角色,本身不负责生产消息
ConnectionFactory(链接管理器):应用程序与RabbitMQ建立链接的管理器,程序代码中使用
Channel(信道):消息推送使用的通道
Exchange(交换器):用于接收,分配消息
Queue(队列):用于存储生产者的消息
RoutingKey(路由键):用于把生产者的数据分配到交换器上
BindingKey(绑定键):用于把交换器的信息绑定到队列上
三种,分别是:
fanout:广播,绑定到RabbitMQ的接收者都能接收到消息
direct:通过RoutingKey和Exchange决定唯一的Queue可以接收消息
topic:所有符合表达式的RoutingKey所bind的Queue可以接收消息
kafka 不能脱离 zookeeper 单独使用,因为kafka是分布式的,所以需要zookeeper 管理和协调它的节点服务器。
kafka 有两种数据保存策略:
1,按照过期时间保留
2,按照存储的消息大小保留
1,给定了分区号,直接将数据发送到指定的分区里面去
2,没有给定分区号,给定数据的key值,通过key取上hashCode进行分区
3,既没有给定分区号,也没有给定key值,直接轮循进行分区
4,自定义分区
不是。它默认仅对RuntimeException和Error发生回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚
1、List、Set都是继承自Collection接口,Map则不是
2、List特点:和数组类似,List可以动态增长,查询快,增删慢,因为会引起其他元素位置改变。 元素有放入顺序,可重复 ,Set特点:查询慢,增删快,增删不会引起元素位置改变。 元素无放入顺序,不可重复,重复元素会覆盖掉
3、Map适合储存键值对的数据
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作,LinedList优于ArrayList,因为ArrayList要移动数据。
本质是JVM得到class对象之后进行反编译,从而获取对象的各种信息。
优点:在运行时获得类的各种内容,能够让我们很方便的创建灵活的代码。
缺点:反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
静态代理只能针对对应的类进行代理,如果类很多就需要很多的代理。 动态代理就是弥补了静态代理的这个缺陷。动态生成一个持有RealObject、并实现代理接口的Proxy,同时注入我们相同的扩展逻辑。哪怕你要代理的RealObject是不同的对象,甚至代理不同的方法,都可以动过动态代理,来扩展功能。
假设A依赖B,B依赖A(注意:这里是set属性依赖)分以下步骤执行:
1、A依次执行doGetBean、查询缓存、createBean创建实例,实例化完成放入三级缓存singletonFactories中,接着执行populateBean方法装配属性,但是发现有一个属性是B的对象。
2、因此再次调用doGetBean方法创建B的实例,依次执行doGetBean、查询缓存、createBean创建实例,实例化完成之后放入三级缓存singletonFactories中,执行populateBean装配属性,但是此时发现有一个属性是A对象。
3、因此再次调用doGetBean创建A的实例,但是执行到getSingleton查询缓存的时候,从三级缓存中查询到了A的实例(早期引用,未完成属性装配),此时直接返回A,不用执行后续的流程创建A了,那么B就完成了属性装配,此时是一个完整的对象放入到一级缓存singletonObjects中。
4、B创建完成了,则A自然完成了属性装配,也创建完成放入了一级缓存singletonObjects中。
5、Spring三级缓存的应用完美的解决了循环依赖的问题
throw会把异常抛出,而throws只会把异常向上传,而不会处理
final:可以用来修饰类,变量,方法。修饰类:该类不能被继承,修饰方法:该方法不能重写,修饰变量:表明该变量是一个常量,不能重新赋值
finally:用于try-catch,不管有没有捕获到异常,最后必定会执行的代码,一般用于关闭资源。
finalize :是一个方法,当我们调用System的gc()方法的时候,由垃圾回收器调用finalize(),回收垃圾。
catch、finally都可以省略,但不能同时省略,要么是try-catch,要么是try-finally
会执行,会在return前执行
1,jsp经编译后就变成了Servlet
2,jsp更擅长表现于页面显示,servlet更擅长于逻辑控制。
3,Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到。
分别是page,request,session,application
pgae:当前页的对象和属性
request:当前请求的对象和属性
session:当前会话的对像和属性
application:整个web客户端的对像和属性
不能,因为session是通过sessionID去判断对应的服务器,而sessionID存放在cookie中,所以如果cookie被禁用,获取不到sessionID,那么session也不能使用。
1,拦截机制的不同。struts 是类拦截机制,而springmvc是方法级别的拦截机制。
2,底层框架的不同。struts是基于filter,springmvc基于Servlet。
3,性能方面springmvc优于struts,因为struts类拦截机制,所以每次请求都要获取一个新的aciton,而springmvc基于方法拦截,只要加载一次单例bean。
4,springmvc是基于spring的,安全性上比struts要高。
1,构造方法注入
2,setter注入
3,基于注解的注入
1,隐式的bean发现机制和自动装配
2,在java代码或者XML中进行显示配置
标记-清除算法
标记-整理算法
复制算法
分代算法
拦截器基于函数回调,过滤器基于反射;拦截器依赖servlet,过滤器不依赖servlet;拦截器拦截所有请求,过滤器只针对action请求。
两个或两个以上的进程,因为互相竞争资源,而导致的阻塞现象,若无外力因素,它们将无法推进下去。
满足死锁的四大条件,分别有:
互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源
请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放
不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放
环路等待条件:是指进程发生死锁后,若干进程之间形成一种头尾相接的循环等待资源关系
只要不满足以上其中任何一个条件,都不会出现死锁。
第一:配置核心线程数,这是线程池初始化的线程数量
第二:配置缓冲队列,这是核心线程数满了后使用的线程数量
第三:配置最大线程数,这是缓冲队列满了后使用的线程数量
第四:配置允许的线程空闲时间
第五:配置线程池前缀
第六:配置线程池满了后的拒绝策略
有四种,分别是:
1、默认的,什么都不处理
2、会跑出一个异常
3、会直接执行提交的任务
4、会把队列中最老的任务丢弃,然后执行提交的任务
1、少创建实例
2、缓存快速获取
3、方便回收
如果有索引:两个效率相同
如果没有索引:distinct效率比group by 高,因为distinct和group by都会进行分组操作。但是group by 可能触发file sort 对返回的数据进行排序,导致sql执行效率低下
运行时异常:空指针异常,角标越界异常,类型转换异常;
非运行时异常:IO异常,SQL异常
1、redis随机抓取数据,但不带条件,所以可能会抓取到有过期时间和没有设置过期时间的数据。
2、判断抓取到的数据是否设置了过期时间并且是否过期,如果过期,就删除。(这也就是为什么redis内存还会存在过期的数据,因为运气好没有被redis抓取到)
3、懒删除策略,用户查询时,对返回给用户的数据进行一次判断(如果过期,就删除数据,并且返回null;如果没有过期,就返回数据)
简单来说:红黑树是一种不完美的平衡二叉树。虽然我们希望一次查找能够在logN次比较内结束,但是因为数据动态地插入,导致树的完美平衡代价过高,所以,只能退而求次,希望能够获得在一个对数时间内完成查找的数据结构。这时候,红黑树就站了出来。
红黑树需要满足以下5个条件:
1、根节点必须是黑色的
2、相邻两个节点不能是红色
3、NIL节点或空节点必须是黑色
4、从任意节点到叶节点的路径都包含相同数量的黑色节点
5、节点必须是红色或黑色
持续更新中…
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。