1、string为什么是不可变的,有什么好处
原因:
1、因为String类下的value数组是用final修饰的,final保证了value一旦被初始化,就不可改变其引用。
2、此外,value数组的访问权限为 private,同时没有提供方法去修改这个数组。
3、而且String类是用final修饰的,不可以被继承,进而避免了子类修改String。
2、
②节省空间–字符串常量池通过使用常量池,内容相同的字符串可以使用同一个对象,从而节省内存空间。
③线程安全,String 对象是不可修改的,如果线程尝试修改 String 对象,会创建新的String,所以不存在并发修改同一个对象的问题
④性能:String 被广泛应用于 HashMap、Hashset 等哈希类中,当我们对一个String类型的key值求hashcode时,String的 hashcode 只需要计算1次后就可以缓存起来,因此在哈希类中使用 String 对象可以提升性能。
2、sychronized和locked的区别?
1、synchronized是一个关键字而,lock是一个接口
2、使用 synchronized 时,退出同步代码块锁会自动释放,而使用 Lock 时,需要手动调用 unlock 方法释放锁
3、synchronized可以作用在方法和代码块上,而lock只能作用在代码块上。
4、synchronized采用的是monitor对象监视器,lock的底层原理是AQS.
5、synchronized是非公平锁,而lock可以是公平锁也可以是非公平锁。
6、Lock 提供了许多 synchronized 不具备的功能,例如获取等待状态、公平锁、可打断(等待获取锁的时候被打断)、可超时(即可以设置等待锁的最长时间,超过了这个时间就不会获取锁了)、多条件变量(即给锁设置不同的条件变量,线程可以对条件变量进行等待和释放,相当于信号量的机制)
3、接口和抽象类的区别?
1、抽象类只能单继承而,接口可以多实现。
2、抽象类可以有构造方法,而接口中不能有构造方法,
3、抽象类中可以有成员变量而接口中没有成员变量只有常量(public static final)
4、抽象类中的方法类型可以是任意修饰符,Java8之前接口中的方法只能是public,Java9支持private类型
4、Redis的基本数据类型?
字符串String、哈希表Hash、列表List、集合Set、有序集合Zset。
(1)字符串String:可以用来做最简单的数据,可以缓存某个简单的字符串也可以缓存某个json格式的字符串,Redis分布式锁的实现就利用了这种数据结构,还包括可以实现计数器、Session共享、分布式ID。
(2)哈希表Hash:可以用来存储一些key-value对,更适合用来存储对象。
(3)列表List:通过命令的组合,既可以当作栈,也可以当作队列来使用可以用来缓存类似微信公众号、微博等消息流数据。
(4)集合Set(String字符串类型的无序集合):和列表类似,也可以存储多个元素,但是不能重复,集合可以进行交集、并集、差集操作,从而可以实现类似,我和某人共同关注的人、朋友圈点赞等功能。
(5)有序集合Zset(Zset是String类型的有序集合):不可重复,有序集合中的每个元素都需要指定一个分数,根据分数对元素进行升序排序。
5、Java的基本数据类型
6、SpringMVC的实现逻辑?
候选人:
嗯,这个知道的,它分了好多步骤
1、用户发送出请求到前端控制器DispatcherServlet,这是一个调度中心
2、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。
3、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
4、DispatcherServlet调用HandlerAdapter(处理器适配器)。
5、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。
6、Controller执行完成返回ModelAndView对象。
7、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。
8、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。
9、ViewReslover解析后返回具体View(视图)。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet响应用户。
7、SpringBoot如何实现依赖注入?
1、使用 XML 配置依赖注入
在 Spring Boot 中,使用 XML 配置依赖注入(DI)时,需要使用元素来定义 bean,并使用元素来为 bean 的属性注入值或依赖对象。
2、使用 Java 配置类实现依赖注入(推荐)
使用Java Config 实现依赖注入可以通过@Configuration和@Bean注解来实现。
3、使用注解来进行依赖注入(推荐)
可以使用注解来进行依赖注入,常用的注解有@Autowired和@Qualifier。
8、为什么MySQL选择B+树索引?
1、因为B+树的非叶结点只存放索引,数据都存放在叶子结点,而B树的所有结点既存放索引又存放数据,因此在数据量一定的情况下,B+树的高度较小,IO次数比较少。
2、B+树的查找都是到达叶子结点才算查找结束,因此B+树的检索效率比较稳定。
3、B+树的叶子节点有一条指针指向与它相邻的叶子节点,形成一个链表,因此更适用于范围查询以及排序
9、where和having的区别?
SELECT customer_id, SUM(total_price) AS total FROM orders GROUP BYcustomer id HAVING SUM(total price)>100;
1、WHERE 子句在执行查询之前筛选原始行数据,而 HAVING 子句基于聚合函数计算的结果进行筛选。
2、WHERE 出现在 FROM 子句之后,GROUP BY 子句之前,而 HAVING 出现在 GROUP BY 子句之后、ORDER BY 子句之前。
3、WHERE 可以使用比较操作符和逻辑操作符来指定过滤条件,而 HAVING不仅可以使用比较操作符和逻辑操作符,还可以使用聚合函数。
10、什么是线程池?
线程池是一种并发编程的机制,用于管理和复用线程,以提高程序的性能和资源利用率。
在多线程环境中,频繁地创建和销毁线程会带来较大的开销,而线程池通过事先创建一组线程并将它们置于待命状态,有效地减少了这种开销。
11、hashmap扩容介绍,什么时候开始扩容?
因为HashMap的容量必须是2的N次方,当我们在构造函数中传入初始容量的时候,HashMap会根据传入的容量计算一个大于等于该容量的最小的2的N次方。如果不传入初始容量,会在第一次添加数据的时候把数组的容量扩容为16。
(1)当HashMap中的元素数量达到数组长度乘以加载因子时,就会触发扩容操作。加载因子默认为0.75,可以通过构造方法设置。
(2)当需要扩容时,HashMap 会创建一个新的数组,其大小为原数组的两倍。
(3)对于原数组中的每个非空位置(即存储了键值对的位置),会重新计算它们在新数组中的位置。
- 没有hash冲突的节点,则直接使用 <font style="color:#DF2A3F;">e.hash & (newCap - 1) 计算新数组的索引位置</font>
- 如果是红黑树,走红黑树的添加
- 如果是链表,则需要遍历链表,有可能需要拆分链表,<font style="color:#DF2A3F;">即判断(e.hash & oldCap)是否为0</font>,如果为0,说明它的位置没有超过原来的数组,该<font style="color:#DF2A3F;">元素的位置下标还是和老数组的下标一样</font>,如果不为0,则移动到(<font style="color:#DF2A3F;">原下标+原数组容量</font>)这个位置上。
(5)当数组中的所有元素都被放入新的位置后,新数组取代了原来的数组成为新的存储结构。
(6)扩容操作完成后,原数组会被垃圾回收。
12、Redis是单线程的,但是为什么还那么快?
1、完全基于内存的,C语言编写,读写速度比较快。
2、采用单线程,避免不必要的上下文切换可竞争条件。
3、Redis 内置了多种优化过后的数据结构,性能非常高
4、Redis通过I/0多路复用程序来监听来自客户端的大量连接。
I/0多路复用是指利用单个线程来同时监听多个Socket,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。目前的I/O多路复用都是采用的epoll模式实现,它会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能。
13、Redis内存淘汰策略?
这个在redis中提供了8种淘汰策略,默认是noeviction,不删除任何数据,内部不足直接报错。
淘汰策略是可以在redis的配置文件中进行设置的,里面有两个非常重要的概念,一个是LRU,另外一个是LFU
LRU的意思就是最少最近使用,用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。
LFU的意思是最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。
14、类加载流程?
类加载的过程包括:加载、验证、准备、解析、初始化、使用、卸载,其中验证、准备、解析统称为连接。
(1)加载:通过一个类的全限定名来获取定义该类的二进制字节流,在内存中生成一个代表这个类的java.lang.Class对象.
(2)验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
(3)准备:为静态变量分配内存并设置静态变量初始值,这里所说的初始值“通常情况”下是数据类型的零值。
(4)解析:将常量池内的符号引用替换为直接引用,
(5)初始化:到了初始化阶段,才真正开始执行类中定义的Java初始化程序代码。主要是静态变量赋值动作和静态语句块(static})中的语句。
(6)使用:JVM开始从入口方法开始执行用户的程序代码。
(7)卸载:当用户程序代码执行完毕后,JVM便开始销毁创建的Class对象。
15、jvm内存区域,虚拟机栈里面是什么,堆里面的数据什么时候进入老年代?
Java虚拟机栈: 线程私有的,每个线程都有一个私有的Java虚拟机栈,用于存储方法调用的局部变量、操作数栈、动态链接、方法出口等信息。每个方法的调用都会创建一个栈帧,方法执行完毕后栈帧会被销毁。如果线程请求的栈深度大于虚拟机允许的深度,将抛出栈溢出异常。
堆:用于存储对象实例和数组。
①Young GC后,如果对象太大无法进入Survivor区,则会通过分配担保机制进入老年代。
②对象每在Survivor区中“熬过”一次Young Gc,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁,可以通过-XX:MaxTenuringThreshold设置),就将会被晋升到老年代中。
16、程序计数器是干什么的?
程序计数器:用于存储当前线程执行的字节码指令的地址。在多线程环境下每个线程都有自己独立的程序计数器,以保证线程切换后能恢复到正确的执行位置。
17、方法执行的时候栈里面是怎么样的?
每个方法的调用都会创建一个栈帧,方法执行完毕后栈帧会被销毁。如线程请求的栈深度大于虚拟机允许的深度,将抛出栈溢出异常。
18、Java虚拟机的方法区了解吗?
在JDK1.7中,方法区也叫作永久代,存放着已被加载的类的信息,常量,静态变量。在JDK1.8中,方法区不存在了,被元空间所取代了,原来的方法区被分为两个部分,1、加载的类信息,2、运行时常量池。加载的类信息被保存在元空间中,运行时常量池保存在堆中。
19、synchronized怎么用的,原理?
synchronized可以被用在代码块和方法上。
- Synchronized采用互斥的方式让同一时刻至多只有一个线程能持有对象锁,synchronized 属于悲观锁。
- 它的底层由对象监视器monitor实现的,monitor是jvm级别的对象( C++实现),使用的时候会把对象锁关联monitor。关联的过程是对象的对象头中的MarkWord字段,记录了指向对象监视器Monitor的指针
- 在monitor内部有三个属性,分别是owner、entrylist、waitset
- 其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程
20、TCP的三次握手?
- 一次握手:客户端发送带有 SYN=1同步标志和SEQ序号=x 的数据包 -> 服务端,该报文不包含应用层数据,然后客户端进入 SYN_SENT 状态,等待服务端的确认;
- 二次握手:服务端发送带有 SYN=1和ACK=1的标志位以及确认应答号x+1+自己的序号y 的数据包 –> 客户端,该报文也不包含应用层数据,然后服务端进入 SYN_RECD 状态;
- 三次握手:客户端发送带有 ACK=1的标志位和确认应答号ACK=y+1的数据包 –> 服务端,这次报文可以携带客户到服务端的数据,然后客户端和服务端都进入ESTABLISHED 状态,完成 TCP 三次握手。
20、为什么要三次握手?
三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。
- 第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
- 第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
- 第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常