1、list与set有什么不同?
首先list与set都是继承于Collection接口
1、list是有序的,set是无序的,list是一个有序的容器,保持了每个元素的插入顺序。即输出顺序就是输入顺序,而set方法是无序容器,无法保证每个元素的存储顺序,TreeSet通过
Comparator 或者 Comparable 维护了一个排序顺序 2、list里面允许有重复的数据,set里不允许有重复的数据。
3、list里面允许插入多个null形式的数据,set只允许插入一个null元素
4、常用的实现类
list方法常用的实现类有ArrayList、LinkedList 和 Vector。
其中ArrayList最为流行,它提供了使用索引的随意访问,而LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适,Vector表示底层数组,线程安全
Set方法中最流行的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap实现的HashSet;
TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare()和compareTo() 的定义进行排序的有序容器
Java中的集合共包含三大类,它们分别是Set(集),List(列表)以及Map(映射)。它们都处在java.util中并且都为接口。它们各自都有各自的实现类。Set的实现类主要有HashSet和TreeSet,List的实现类主要有ArrayList等。
延申:
list与map的不同
list是存储单列数据的集合,map是存储键和值这样的双列数据的集合, list中存储的数据是有顺序,并且允许重复; Map中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。
二、缓存四种场景并如何解决
缓存击穿:
key很热门,在缓存的时间失效的一瞬间,去数据库中查找,在高并发场景下,如果某一个key被高并发访问,没有被命中,出于对容错性考虑,会尝试去从后端数据库中获取,从而导致了大量请求达到数据库。(可以设置永不过时)
缓存穿透:
存储的数据实体消失redis与数据库中的这条信息都不存在,在请求数据时,找不到这个数据,(那为什么找不到呢?),数据库中也不存在这条数据。也就是当该key对应的数据本身就是空的情况下,这就导致数据库中并发的去执行了很多不必要的查询操作,从而导致巨大冲击和压力。
缓存雪崩:
设置的缓存时间失效了,在这一瞬间,部分缓存或者是所有的缓存一下子都没了,直接请求数据库,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。(高并发场景下就很可怕,就会直接导致服务器宕机)
缓存在高并发场景下常见的一些问题:
https://www.cnblogs.com/dinglang/p/6133501.html
如何解决:
缓存雪崩:
解决方案:缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
关键字: 时间岔开,确保大家的key不会落在同一个expire点上。
同时我们可以通过限流、降级、熔断等手段来降低影响,也可以通过多级缓存来避免这种灾难。
缓存击穿:
导致问题的原因是同一时间查,同一时间写缓存,导致并发下缓存也没用,所以考虑使用单线程等方法将写缓存保证只有一个去查了写,其他的使用缓存。
业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。在redis2.6.1之前版本未实现setnx的过期时间。
缓存穿透
解决方案:有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
来源地址:https://blog.csdn.net/doujinlong1/article/details/82024340
三、redis使用场景
可以先看一下这个文档:
https://blog.csdn.net/hguisu/article/details/8836819
1.少量数据存储,高速读写访问。此类产品通过数据全部in-momery 的方式来保证高速访问,同时提供数据落地的功能,实际这正是Redis最主要的适用场景。
2.海量数据存储,分布式系统支持,数据一致性保证,方便的集群节点添加/删除。
3.这方面最具代表性的是dynamo和bigtable 2篇论文所阐述的思路。前者是一个完全无中心的设计,节点之间通过gossip方式传递集群信息,数据保证最终一致性,后者是一个中心化的方案设计,通过类似一个分布式锁服务来保证强一致性,数据写入先写内存和redo log,然后定期compat归并到磁盘上,将随机写优化为顺序写,提高写入性能。
这里有一个简书的文章,写的很不错https://www.jianshu.com/p/40dbc78711c8
四、jvm调优
1.栈的大小
Java中,栈的大小通过-Xss来设置,当栈中存储数据比较多时,需要适当调大这个值,否则会出现java.lang.StackOverflowError异常。常见的出现这个异常的是无法返回的递归,因为此时栈中保存的信息都是方法返回的记录点。
2.对象缓存
Java默认会对-128到127的Byte、Short、Integer和Long对象进行缓存,当创建的对象在这个数值范围则返回已缓存的对象,否则创建新的对象。Character缓存的范围是0~127,而Float和Double没有自动装箱池。这种设计了享元设计模式,详见享元模式。
然而,我们可以通过getAndRemoveCacheProperties方法来获取或移除JDK对Integer设置的缓存属性,同时可以通过调整虚拟机选项-XX:AutoBoxCacheMax来调整“自动装箱池”的大小 。
分代的垃圾回收策略
这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。
试想,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,同时,因为每次回收都需要遍历所有存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有效果的,因为可能进行了很多次遍历,但是他们依旧存在。因此,分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。
青年代和老年代可以自己看这篇文章:https://www.cnblogs.com/andy-zhou/p/5327288.html
持久代用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。
为什么要调整栈的大小呢,看一下这篇文章:
https://www.cnblogs.com/andy-zhou/p/5327288.html
sql优化
如何激活垃圾回收机制
从上面的图可以看出, JVM区域总体分两类,heap区和非heap区。
1.heap区又分为:
- Eden Space(伊甸园)、
- Survivor Space(幸存者区)、
- Old Gen(老年代)。
2.非heap区又分:
- Code Cache(代码缓存区);
- Perm Gen(永久代);
- Jvm Stack(java虚拟机栈);
- Local Method Statck(本地方法栈);
详情看这篇文章:https://www.cnblogs.com/haitaofeiyang/p/8392268.html
什么情况下触发垃圾回收
由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full GC。
Scavenge GC
一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
Full GC
对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:
· 年老代(Tenured)被写满 · 持久代(Perm)被写满 · System.gc()被显示调用 ·上一次GC之后Heap的各域分配策略动态变化
索引应该加在哪
微软sqlserver定义了两个索引方式:
聚集索引(clustered index,也称聚类索引、簇集索引)和非聚集索引(nonclustered index,也称非聚类索引、非簇集索引)
聚集索引就是类似于通过字典的字母顺序去查具体的字,我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。
我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。先找到目录中对应的偏旁部首,然后再去查找对应的主体内容。
1、表的主键、外键必须有索引;
2、数据量超过300的表应该有索引;
3、经常与其他表进行连接的表,在连接字段上应该建立索引;
4、经常出现在Where子句中的字段,特别是大表的字段,应该建立索引;
5、索引应该建在选择性高的字段上;性别字段不能设置索引。
6、索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;
7、复合索引的建立需要进行仔细分析;尽量考虑用单字段索引代替:
A、正确选择复合索引中的主列字段,一般是选择性较好的字段;
B、复合索引的几个字段是否经常同时以AND方式出现在Where子句中?单字段查询是否极少甚至没有?如果是,则可以建立复合索引;否则考虑单字段索引;
C、如果复合索引中包含的字段经常单独出现在Where子句中,则分解为多个单字段索引;
D、如果复合索引所包含的字段超过3个,那么仔细考虑其必要性,考虑减少复合的字段;
E、如果既有单字段索引,又有这几个字段上的复合索引,一般可以删除复合索引;
8、频繁进行数据操作的表,不要建立太多的索引;
9、删除无用的索引,避免对执行计划造成负面影响;
以上是一些普遍的建立索引时的判断依据。一言以蔽之,索引的建立必须慎重,对每个索引的必要性都应该经过仔细分析,要有建立的依据。因为太多的索引与不充分、不正确的索引对性能都毫无益处:在表上建立的每个索引都会增加存储开销,索引对于插入、删除、更新操作也会增加处理上的开销。另外,过多的复合索引,在有单字段索引的情况下,一般都是没有存在价值的;相反,还会降低数据增加删除时的性能,特别是对频繁更新的表来说,负面影响更大
其他文章链接:https://blog.csdn.net/WuLex/article/details/69540136
mybatis占位符与赋值符
也就是#{ }与 $ { }的区别
经常碰到这样的面试题目:#{}和${}的区别是什么?
网上的答案是:
#{}是预编译处理,$ {}是字符串替换。
mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
mybatis在处理 $ { } 时,就是把 ${ } 替换成变量的值,也是对sql的一种拼接,比如获取表、orderby 后面接的字段名称。
使用 #{} 可以有效的防止SQL注入,提高系统安全性。
对于这个题目我感觉要抓住两点:
(1) $ 符号一般用来当作占位符,常使用Linux脚本的人应该对此有更深的体会吧。既然是占位符,当然就是被用来替换的。知道了这点就能很容易区分$和#,从而不容易记错了。
(2)预编译的机制。预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。
最后想说的是,对于mybatis 以及 sql 而言,每一个考点背后都是有一个深刻的思想存在的,应该好好的体会。这样才能真正的做到技术提升,成为技术大牛。
inner join与left join的查询效率
inner join 是只需要返回两个表的交集部分,left join多返回了一部分左表没有返回的数据
①大家都知道,sql尽量使用数据量小的表做主表,这样效率高,如果使用数据量大的表做主表,此时使用left join就会比较慢,即使关联条件有索引。但如果使用inner join速度就较快。因为inner join在执行的时候回自动选择最小的表做基础表,效率高,总之相比之下inner join不管从效率还是速度上都优于left join,毕竟leftjoin 会多一部分逻辑运算
②选择inner join还有个好处,不会产生null,有些表我们在定义的时候某些字段不允许存在null,如果用left join就可能会产生null,此时软件就会报错,而inner join可以避免
③在实际运用中选择inner join还是left join这个需要根据实际场景进行选择,并不是所有的地方都能用inner join的,建议能用则用
https://blog.csdn.net/LJFPHP/article/details/88635755
注册中心运行机制
三个注册中心的区别
https://blog.csdn.net/weixin_33970449/article/details/92348278
事务的具体操作
这里介绍了事务的几个特性
https://blog.51cto.com/janniexx/1409797
这篇文章详细的介绍了事务是什么、干什么、怎么用
https://blog.csdn.net/zhao_miao/article/details/89396608
spring ioc与aop
ioc原理:
spring ioc: https://www.cnblogs.com/xdp-gacl/p/4249939.html
aop的具体操作:
spring aop: https://www.cnblogs.com/joy99/p/10941543.html
如何引入第三方jar包
地址:https://www.cnblogs.com/netcorner/p/10962981.html
二分法查找
深入理解递归与循环
https://www.cnblogs.com/Pushy/p/8455862.html
二分法折半查找(循环和递归两种实现)
https://blog.csdn.net/weixin_42103983/article/details/112093333
排序 头尾互换
行转列
单例模式 加锁的话 是锁全局还是锁类
可以看这篇文章 介绍了懒汉与饿汉的两种模式
https://www.cnblogs.com/qifenghao/p/8986538.html
OKhttp
springboot start类为啥叫start 有什么作用
springboot application的开始标签由哪几部分组成
feign是如何进行负载均衡的
feignclient的实现原理
vue组件js有那些属性
nacos服务与注册中心原理 与其他服务注册中心有啥不同
autowired与resource
这篇文章讲的很详细:
https://blog.csdn.net/weixin_40423597/article/details/80643990
多线程五种状态:
新建状态:当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态:当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态:当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态:处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期。