Java基础
1、java 中的 Math.round(-11.3) 等于多少?
-11
Math提供了三个与取整有关的方法:ceil、floor、round
(1)ceil:向上取整;
Math.ceil(11.3) = 12;
Math.ceil(-11.3) = -11;
(2)floor:向下取整;
Math.floor(11.3) = 11;
Math.floor(-11.3) = -12;
(3)round:四舍五入;加0.5然后向下取整。
Math.round(11.3) = 11;
Math.round(11.8) = 12;
Math.round(-11.3) = -11;
Math.round(-11.8) = -12;
2、String str="xyz"与 String str=new String(“xyz”)一样吗?
两个语句都会先去字符串常量池中检查是否已经存在 “xyz”,如果有则直接使用,如果没有则会在常量池中创建 “xyz” 对象。
另外,String s = new String(“xyz”) 还会通过 new String() 在堆里创建一个内容与 “xyz” 相同的对象实例。
所以前者其实理解为被后者的所包含。
3、new String(“a”) + new String(“b”) 会创建几个对象?
如果字符串常量池中没有“a”和“b”,就是6个,否则就是4个
对象1:new StringBuilder()
对象2:new String(“a”)
对象3:常量池中的"a"
对象4:new String(“b”)
对象5:常量池中的"b"
深入剖析:StringBuilder中的toString():
对象6:new String(“ab”)
强调一下,toString()的调用,在字符串常量池中,没有生成"ab"
字符串常量池: JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
4、如何将字符串反转?
将对象封装到stringBuilder中,调用reverse方法反转。
5、String 类的常用方法都有那些?
(1)常见String类的获取功能
length:获取字符串长度;
charAt(int index):获取指定索引位置的字符;
indexOf(int ch):返回指定字符在此字符串中第一次出现处的索引;
substring(int start):从指定位置开始截取字符串,默认到末尾;
substring(int start,int end):从指定位置开始到指定位置结束截取字符串;
(2)常见String类的判断功能
equals(Object obj): 比较字符串的内容是否相同,区分大小写;
contains(String str): 判断字符串中是否包含传递进来的字符串;
startsWith(String str): 判断字符串是否以传递进来的字符串开头;
endsWith(String str): 判断字符串是否以传递进来的字符串结尾;
isEmpty(): 判断字符串的内容是否为空串"";
(3)常见String类的转换功能
byte[] getBytes(): 把字符串转换为字节数组;
char[] toCharArray(): 把字符串转换为字符数组;
String valueOf(char[] chs): 把字符数组转成字符串。valueOf可以将任意类型转为字符串;
toLowerCase(): 把字符串转成小写;
toUpperCase(): 把字符串转成大写;
concat(String str): 把字符串拼接;
(4)常见String类的其他常用功能
replace(char old,char new) 将指定字符进行互换
replace(String old,String new) 将指定字符串进行互换
trim() 去除两端空格
int compareTo(String str) 会对照ASCII 码表 从第一个字母进行减法运算 返回的就是这个减法的结果,如果前面几个字母一样会根据两个字符串的长度进行减法运算返回的就是这个减法的结果,如果连个字符串一摸一样 返回的就是0。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
6、String、StringBuffer和StringBuilder区别?
String:字符串对象是不可变对象,虽然可以共享常量对象,但是对于频繁字符串的修改和拼接操作,效率极低
后面两者都是可变字符串对象
StringBuffer:线程安全的(因为它的方法有synchronized修饰)效率低
StringBuilder:线程不安全的 性能高
小结:
(1)如果要操作少量的数据用 String;
(2)多线程操作字符串缓冲区下操作大量数据用 StringBuffer;
(3)单线程操作字符串缓冲区下操作大量数据用 StringBuilder。
String不可变的真正原因
1、字符串的本质是char数组(jdk9以后是byte[]),被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
2、String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
7、 字符串拼接问题
(1)常量+常量:结果是常量池(常量优化,因为编译期间就可以确定结果)
(2)常量与变量 或 变量与变量:结果是堆
(3)拼接后调用intern方法:结果在常量池
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";
String str4 = str1 + str2;
String str5 = "string";
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
8、什么是自动拆装箱? int和Integer有什么区别?以及以下程序运行结果。
基本数据类型,如int,float,double,boolean,char,byte,不具备对象的特征,不能调用方法。
装箱:将基本类型转换成包装类对象
拆箱:将包装类对象转换成基本类型的值
java为什么要引入自动装箱和拆箱的功能?主要是用于java集合中,List list=new ArrayList();
list集合如果要放整数的话,只能放对象,不能放基本类型,因此需要将整数自动装箱成对象。
实现原理:javac编译器的语法糖,底层是通过Integer.valueOf()和Integer.intValue()方法实现。
区别:
(1)Integer是int的包装类,int则是java的一种基本数据类型
(2)Integer变量必须实例化后才能使用,而int变量不需要
(3)Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
(4)Integer的默认值是null,int的默认值是0
包装类型的缓存机制
Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。
Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True or False。
如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
两种浮点数类型的包装类 Float,Double 并没有实现缓存机制。
public class Test01 {
public static void main(String[] args){
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a==b); //true
System.out.println(c==d); //false
Float i11 = 333f;
Float i22 = 333f;
System.out.println(i11 == i22);//false
Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);//false
}
}
建议:所有整型包装类对象之间值的比较,建议使用 equals 方法比较。
9、类和对象的关系
类是对同一类事物的描述,是抽象的
对象是一类事物的实现,是具体的
类是模板,对象是类的实例
10、怎么理解面向对象?
继承:描述的是事物之间的所属关系,is-a,子类继承父类的特征和行为,复用性 扩展性
封装:内部属性私有化,对外提供公共的访问方式,高内聚,低耦合
多态:同一个行为有不同的表现形式,多态存在3个条件:①继承②重写③父类引用指向子类实例 如List list = new ArrayList();
14、接口与抽象类的区别?
1、成员变量
抽象类:既可以是常量也可以是变量
接口:一定是常量
2、构造
抽象类:有构造
接口:没有构造
3、成员方法
抽象类:既可以是普通方法,也可以是抽象方法
接口:JDK1.8之前,必须是抽象方法(JDK8之后出现了默认方法和静态方法,JDK9出现了私有方法)
4、设计理念
抽象类:作为一个继承体系顶层,将共性行为和属性被继承下去,体现is-a的关系 单继承
接口:作为一个功能进行扩展 多实现(子类可以实现多个接口)
15、重载与重写有什么区别?
1、重载发生在本类,重写发生在父类与子类之间;
2、重载的方法名必须相同,重写的方法名相同且返回值类型必须相同;
3、重载的参数列表不同,重写的参数列表必须相同。
4、重写的访问权限不能比父类中被重写的方法的访问权限更低。
5、构造方法不能被重写
16、为什么要使用克隆?如何实现对象克隆?深拷贝和浅拷贝区别是什么?
(1)为什么要使用克隆?
想对一个对象进行复制,又想保留原有的对象进行接下来的操作,这个时候就需要克隆了。
(2)如何实现对象克隆?
1、实现Cloneable接口,重写clone方法;
2、实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆。
BeanUtils,apache和Spring都提供了bean工具,只是这都是浅克隆。
(3)深拷贝和浅拷贝区别是什么?
浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
17、java 中 IO 流分为几种?
所谓的IO就是实现数据从磁盘的读取和写入。
实际上,除了磁盘以外,内存、网络都可以作为 I/O 流的数据来源和目的地。
在 Java 里面,提供了字符流和字节流两种方式来实现数据流的操作。
Java 里面提供了 Socket的方式来实现数据的网络传输。
18、 常见的IO模型:BIO、NIO、AIO
BIO
定义:同步阻塞IO,传统的IO模型,实现数据从磁盘中的读取以及写入。
特点:简单使用方便,但并发处理能力低。
NIO
定义:同步非阻塞 IO,是传统 IO 的升级,它是支持面向缓冲的,基于通道的 I/O 操作方法。了
特点:多路复用,减少CPU的小号,适用于高并发。
AIO
定义:异步非阻塞IO,是 NIO 的升级,也叫 NIO2,是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
特点:异步非阻塞
19、什么是 java 序列化?怎么实现序列化
序列化:把内存中的java对象转换为二进制字节流,用来实现存储或传输
反序列化:将从文件或者网络上获取到的对象的字节流转化为对象
序 列 化 的 实 现 :
只有实现了Serializable和Externalizeble的接口的类才能实现序列化。
Java.IO.ObjectOutputStream代表对象输出流。writeObject(Object obj)方法可对参数指定的obj对象进行序列化。
Java.IO.ObjectInputStream代表对象输入流,它的readObject()方法可以反序列化。
如果类中每个成员变量不想被序列化,可以用transient关键字修饰。
20、final、finally、finalize的区别?
final:修饰符(关键字)有三种用法:修饰类、变量和方法。修饰类时,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。修饰变量时,该变量使用中不被改变,必须在声明时给定初值,在引用中只能读取不可修改,即为常量。修饰方法时,也同样只能使用,不能在子类中被重写。
finally:通常放在try…catch的后面构造最终执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。
finalize:Object类中定义的方法,Java中允许使用finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作。
21、Object中有哪些方法?
(1)protected Object clone()--->创建并返回此对象的一个副本。
(2)boolean equals(Object obj)--->指示某个其他对象是否与此对象“相等”。
(3)protected void finalize()--->当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
(4)Class<? extendsObject> getClass()--->返回一个对象的运行时类。
(5)int hashCode()--->返回该对象的哈希码值。
(6)void notify()--->唤醒在此对象监视器上等待的单个线程。
(7)void notifyAll()--->唤醒在此对象监视器上等待的所有线程。
(8)String toString()--->返回该对象的字符串表示。
(9)void wait()--->导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
void wait(long timeout)--->导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll()方法,或者超过指定的时间量。
void wait(long timeout, int nanos)--->导致当前的线程等待,直到其他线程调用此对象的 notify()
22、异常相关
在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:
Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (非受检查异常,可以不处理)。
Error:Error 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获。例如 Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
1、编译时异常:
IOException 输入输出流异常
FileNotFoundException 文件找不到的异常
ClassNotFoundException 类找不到异常
DataFormatException 数据格式化异常
NoSuchFieldException 没有匹配的属性异常
NoSuchMethodException 没有匹配的方法异常
SQLException 数据库操作异常
TimeoutException 执行超时异常
2、运行时异常(RuntimeException 及其子类都统称为非受检查异常)
ArrayIndexOutofBoundsException 数组越界异常
ClassCastException 类型转换异常
NullPointerException 空指针异常
IllegalAccessException 非法的参数异常
NumberFormatException 字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。
ArithmeticException 算术异常
23、hashcode是什么?有什么作用?
hashcode是一种编码方式,在Java中,每个对象都会有一个hashcode,Java可以通过这个hashcode来识别一个对象。hashcode方法返回该对象的哈希码值,该方法为哈希表提供一些优点,就是通过这个哈希实现快速查找键对象
1、如果equals相等,hashcode一定相同
2、但hashcode相等,equals不一定相等(hash碰撞)
3、重写equals方法一定要重写hashcode方法(两个相等的对象hashcode一定相同)
HashCode的存在主要是为了查找的快捷性,HashCode是用来在散列存储结构中确定对象的存储地址的。如hashmap、hashtable
24、谈谈你对反射的理解?
(1)反射机制:
所谓的反射机制就是java语言在运行时拥有一项自观的能力。通过这种能力可以彻底的了解自身的情况为下一步的动作做准备。
Java的反射机制的实现要借助于4个类:class,Constructor,Field,Method;
其中class代表的时类对 象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象。通过这四个对象我们可以粗略的看到一个类的各个组 成部分。
(2)Java反射的作用:
在Java运行时环境中,对于任意一个类,可以知道这个类有哪些属性和方法。对于任意一个对象,可以调用它的任意一个方法。这种动态获取类的信息以及动态调用对象的方法的功能来自于Java 语言的反射(Reflection)机制。
(3)Java 反射机制提供功能
在运行时判断任意一个对象所属的类。
在运行时构造任意一个类的对象。
在运行时判断任意一个类所具有的成员变量和方法。
在运行时调用任意一个对象的方法
优点
- 增加程序的灵活性,可以在运行的过程中动态对类进行修改和操作
- 提高代码的复用率,比如动态代理,就是用到了反射来实现
缺点:
- 破坏了代码本身的抽象性
- 反射会涉及到动态类型的解析,所以 JVM 无法对这些代码进行优化,导致性能要比非反射调用更低。
25、linux常用命令
三个简单
ls:列出目录中的目录
cd:切换目录
mkdir :创建文件夹
cp:复制文件
mv:移动文件
cat、more:查看文件
三个复杂
ifconfig:查询当前网卡配置信息
tail -f xx.out 动态查看日志
netstat -anp | grep 端口号:查看端口号
top:查看内存
ps aux:查看进程
kill -9 进程号:杀死进程
26、docker常用命令
systemctr start/stop/restart docker 开启/停止/重启docker
docker images 查看所有镜像
docker pull 拉取镜像
docker rmi -f 删除镜像
docker ps 查看正在运行的容器
docker ps -a 查看所有的容器
docker run 启动容器
docker stop 停止容器
docker exec -it ‘容器名称或容器ID’ bash
docker logs -f 容器id/容器名称 实时查看日志
docker logs --tail=500 [容器id] 查看最后500行日志
docker logs -f --since “2022-06-22” [容器id或服务名称] 查看某个时间至今的日志
集合
1、数组和集合的区别
- 相同点:都是容器,可以存储数据
- 不同点:
1、数组长度是不可变的,集合长度可变
2、数组可以存储基本数据类型和引用数据类型,集合只能存储引用数据类型,如果要存基本数据类型,要转换为其对应的包装类
3、数组转集合:Arrasys.asList()
4、集合转数组:list.toArray(new String[list.size()])
2、说一下集合体系?
3、说一下ArrarList和LinkedList区别?
ArrayList
- ArrayList:底层是动态数组,与Java中的数组相比,它的容量能动态增长。查询效率高(有索引)
- 时间复杂度:查询时O(1),增删是O(n)
LinkedList
- linkedList:底层是双向链表,增删效率高,也要看实际情况,对于单条数据的增删,ArrayList的效率反而优于linkedList。对于批量增删,linkedList大大优于ArrayList
- 时间复杂度:查询O(n),增删O(1)
- 都不是线程安全的
4、ArrayList和Vector的底层原理和扩容机制
- ArrayList:是线程不安全的动态数组,JDK1.7后初始化为空数组,在添加第一个元素时初始化为长度为10的数组,如果容量满了,按照1.5倍扩容。支持foreach和Iterator遍历。
- Vector:是线程安全的动态数组,初始化为长度为10的数组,如果容量满了,按照2.0倍扩容。除了支持foreach和Iterator遍历,还支持Enumeration迭代。
5、HashMap底层原理?
1、当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标 。
2、存储时,如果出现hash相同的key,有可能会先两种情况,
- 如果key之前是存在的,则覆盖原始值;
- 如果key不同则将当前的key-value放入链表中 。(jdk1.7头插法,jdk1.8尾插法)
3、获取时,通过hash直接找到对应的index,在进一步判断key是否相同,从而找到对应的值
JDK1.8之前
拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
JDK1.8
jdk1.8是数组+链表+红黑树。当链表长度大于阈值(默认为8) 时且数组长度大于64时,将链表转化为红黑树,以减少搜索时间。当链表长度大于8且数组长度小于64时开始扩容 ,红黑树拆分成的树的结点数小于等于临界值6个,则退化成链表。
6、HashMap中put方法具体流程?
HashMap的底层结构在jdk1.7中由数组+链表实现,在jdk1.8中由数组+链表+红黑树实现,以数组+链表的结构为例。
JDK1.8之前Put方法:
(1)当第一次put时,数组初始化为一个长度为16的Entry数组
(2)特殊考虑:如果key为null,index直接是[0],hash也是0
(3)如果key不为null,会对key的hashCode()值做一个hash(key)再次哈希的运算,这样可以使得Entry对象更加散列的存储到table中
(4)计算当前对象的元素在数组中的下标index = table.length-1 & hash;
(5)如果table[index]为空,创建Entry对象,把value存到数组中,size++
(6)如果table[index]不为空,判断是否出现hash值相同的key
- 如果key相同,则覆盖原始值;
- 如果key不同(出现冲突),则将当前的key-value放入链表中 (头插法)
JDK1.8之后Put方法:
(1)当第一次put时,数组初始化为一个长度为16的table数组
(2)特殊考虑:如果key为null,index直接是[0],hash也是0
(3)如果key不为null,会对key的hashCode()值做一个hash(key)再次哈希的运算
(4)计算当前对象的元素在数组中的下标index = table.length-1 & hash;
(5) 如果table[index]==null,那么直接创建一个Node结点存储到table[index]中即可,size++
(6)如果table[index]不为空,判断是否出现hash值相同的key
- 如果key相同,则覆盖原始值;
- 如果key不同(出现冲突),则将当前的key-value放入链表中 (尾插法)
与jdk1.7不同的是,当链表的长度达到8并且数组长度达到64时,此时将链表转化为红黑树去存储,可以提高性能和减少搜索时间。
如果链表长度大于8,并且数组长度小于64时,并不会树化,而是数组扩容,因为红黑树涉及到左旋、右旋、变色等操作。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
7、HashMap扩容机制?
jdk1.7:
if(size >= threshold && table[index]!=null){
①会扩容
②会重新计算key的hash
③会重新计算index
}
jdk1.8:
if(size > threshold ){
①会扩容
②会重新计算key的hash
③会重新计算index
}
扩容之后会重新计算key的hash,会重新计算index
rehash: 扩容时在转移元素的过程中,
如 rehash为true,那么该元素转移之后,就有可能被放在新数组任意一个位置。
若 rehash为false,那么该元素转移之后,就会和老数组在同一个位置或者会被转移到元素在老数组位置所在索引 +老数组的长度
8、HashMap的寻址算法?
1、计算出key的hashCode赋值给h
2、hash = key.hashCode() ^ (h >>> 16) 按位异或
3、index = hash & (table.length-1) 按位与
&(按位与运算):相同的二进制数位上,都是1的时候,结果为1,否则为0。
4)^(按位异或运算):相同的二进制数位上,数字相同,结果为0,不同为1。
9、HashMap 多线程操作导致死循环问题
JDK1.7 及之前版本的 HashMap 在多线程环境下扩容操作可能存在死循环问题,这是由于当一个桶位中有多个元素需要进行扩容时,多个线程同时对链表进行操作,头插法可能会导致链表中的节点指向错误的位置,从而形成一个环形链表,进而使得查询元素的操作陷入死循环无法结束。
为了解决这个问题,JDK1.8 版本的 HashMap 采用了尾插法而不是头插法来避免链表倒置,使得插入的节点永远都是放在链表的末尾,避免了链表中的环形结构。
但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在数据覆盖的问题。并发环境下,推荐使用 ConcurrentHashMap 。
10、哈希表的底层数组长度为什么是 2 的 n 次方?
因为 2 的 n 次方-1 的二进制值是前面都 0,后面几位都是 1,这样的话,与 hash 进行 &运算的结果就能保证在[0,table.length-1]范围内,而且是均匀的。
保证index可以更散列均匀分布[0,table.length-1],减少hash冲突。
11、HashMap和HashTable区别?
1、HashMap是线程不安全的,HashTable是线程安全的(synchronized修饰的);
2、HashMap底层是数组+链表(jdk1.7)数组+链表+红黑树(jdk1.8),HashTable底层是数组+链表
3、HashMap中允许键和值为null,HashTable不允许;
4、HashMap的默认容器是16,为2倍扩容,HashTable默认是11,为2N+1扩容;
HashMap的线程安全问题可以使用Collections的synchronizedMap(Map<K,V> m) 方法解决。
红黑树:左旋、右旋、变色
12、哪些集合类是线程安全的?
Vector:就比Arraylist多了个同步化机制(线程安全)。
Stack:栈,也是线程安全的,继承于Vector。
Hashtable:就比Hashmap多了个线程安全。
CopyAndWriteList:写时复制,采用ReentrantLock
ConcurrentHashMap:是一种高效且线程安全的集合。
13、Hashmap树化链表长度为什么是8?负载因子为什么是0.75?
链表长度符合泊松分布,各个长度的命中概率逐渐递减,当长度为8时,hash碰撞的概率为千万分之6.
负载因子是计算扩容的阈值,作用就是节省时间和空间,当加载因子为0.75的时候,空间利用率很高,而且避免了相当多的hash冲突,使得底层的链表和红黑树的长度更低,提升了空间利用率。
14、JDK7与JDK8中HashMap的不同点?
1、数据结构
jdk7是数组+链表,jdk8是数组+链表+红黑树
2、链表插入方式
jdk7:头插法,扩容转移元素的时候也是使用的头插法,头插法速度更快,无需遍历链表,但是在多线程扩容的情况下使用头插法会出现循环链表的问题,导致CPU飙升
jdk8:尾插法,反正要去计算链表当前结点的个数,反正要遍历的链表的,所以直接使用尾插法
3、hash算法
jdk7hash算法更复杂,这样hash值越散列,通过二次hash,jdk8没有这个逻辑,jdk8还可以通过红黑树降低hash冲突,提升查询效率
4、扩容机制
jdk7:size >= threshold && table[index]!=null&&老数组的容量没有达到integer最大值。JDK7是每次转移一个元素
jdk8:size >= threshold ,JDK8是先算出来当前位置上哪些元素在新数组的低位上,哪些在新数组的高位上,然后在一次性转移
15、ConcurrentHashMap是怎么保证并发安全的?
jdk1.7 :采用Segments 分段锁,底层是由Segments + HashEntry 链表实现,Segment 继承了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。
jdk1.8:数据结构和hashmap类似。取消了 Segment 分段锁的设计,采用 Node + CAS + synchronized 保证线程安全,锁粒度更细,synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,就不会影响其他 Node 的读写,效率大幅提升
16、fail-safe和fail-fast有了解吗?
是多线程并发操作集合时的一种失败处理机制
fail-fast:表示快速失败,在集合遍历中,一旦发现容器中数据修改了,就会抛出异常
java.util 包下的集合类都是快速失败机制的,常见的的使用 fail-fast 方式遍历的容器有 HashMap 和 ArrayList 等。
fail-safe:表示失败安全,在集合遍历中,集合中元素被修改,也不会抛出异常。是因为采用这种机制的集合在遍历时,是先复制原有的集合,在拷贝上的集合进行遍历。
java.util.concurrent 包下的容器都是安全失败的,可以在多线程下并发使用,并发修改。
使用 fail-safe方式有ConcurrentHashMap 和CopyOnWriteArrayList
JavaWeb
1、Javaweb的三大组件
1、Servlet是用来接收处理客户端请求的动态资源,响应数据
2、filter主要负责拦截请求和放行。如:权限控制、统一编码处理、敏感字符处理等等.
3、Listener表示服务器的事件监听器,用于监听三个域对象的状态(对象、对象的属性)变化
2、JSP 和 Servlet 的区别
- JSP的本质就是Servlet,Web容器将JSP的代码编译成JVM能够识别的Servlet
- jsp更擅长表现于页面显示,servlet更擅长于逻辑控制.
- Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到.
- Jsp是Servlet的一种简化,使用Jsp只需要完成程序员需要输出到客户端的内容,Jsp中的Java脚本如何镶嵌到一个类中,由Jsp容器完成。而Servlet则是个完整的Java类,这个类的Service方法用于生成对客户端的响应。
3、拦截器和过滤器的区别
1、实现原理不同
过滤器 是基于函数回调的,拦截器 则是基于Java的反射机制==(动态代理)==实现的。自定义的过滤器中都会实现一个 doFilter()方法,这个方法有一个FilterChain 参数,而实际上它是一个回调接口
2、使用范围不同
过滤器依赖于servlet容器,只能在web程序中
拦截器是spring提供的,不依赖于servlet容器,可以单独使用,可以获取ioc容器中的各个bean
3、触发时机不同
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。
4、拦截的请求范围不同
拦截器只拦截action请求,而过滤器则几乎拦截所有的请求起作用。
4、Http 常见的状态码有哪些?
200 OK //客户端请求成功
301 Moved Permanently(永久移除),请求的 URL 已移走。Response 中应该包含一个 Location URL, 说明资源现在所处的位置
302 found 重定向
400 Bad Request //客户端请求有语法错误,不能被服务器所理解
401 Unauthorized //请求未经授权,这个状态代码必须和 WWW-Authenticate 报头域一起使用
403 Forbidden //服务器收到请求,但是拒绝提供服务 权限/跨域问题
404 Not Found //请求资源不存在,eg:输入了错误的 URL
405:请求方式错误
500 Internal Server Error //服务器发生不可预期的错误
503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常
5、GET 和POST 的区别?
① 浏览器和表单的默认提交方式是get,get请求效率比post高
② get请求参数在url地址后拼接,所以有以下特点:
请求报文没有请求体
少了和请求体相关的请求头参数
参数在url地址中拼接,上传参数大小有限制,不能用来上传文件,相对post请求不安全
③ post请求参数在请求报文的请求体中携带,有以下特点:
请求报文有请求体,相对安全
请求头多了和请求体相关的参数
请求体数据没有大小限制可以用来上传文件
6、Cookie 和Session 的区别
session 的工作原理是客户端登录完成之后,服务器会创建对应的 session,session 创建完之后,会把 session 的 id 发送给客户端,客户端再存储到浏览器的cookie中。这样客户端每次访问服务器时,都会带着 sessionid,服务器拿到 sessionid 之后,在内存找到与之对应的 session 这样就可以正常工作了。
Cookie:cookie是服务端生成发送给客户端,保存在客户端的临时的少量的数据。下次请求同一网站时会携带cookie
Token:Token是服务端生成的一串字符串,当作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token并将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
Cookie 和session 的不同点:
- 存储位置 :cookie在客户端浏览器;session在服务器;
- 存储容量:cookie一般不超过4K,一个站点最多保留20个cookie;session存储在服务器上可以任意存储数据。当 session存储数据太多时,服务器可选择进行清理。
- 数据类型 :cookie只能保存ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据;session中能存储任何类型的数据,包括并不局限于String、integer、list、map等;
- 安全性 :cookie对客户端是可见的,可以被删除和篡改,不安全;session存储在服务器上,安全;
- 存储多样性 :session 可以存储在Redis中、数据库中、应用程序中;而 cookie 只能存储在浏览器中
- 跨域支持cookie支持跨域,session不支持跨域;
7、转发和重定向的区别?(至少写3个)
1、请求次数: 转发一次,重定向两次
2、浏览器地址栏:转发不变,重定向改变
3、使用request域共享数据:转发是一次请求共享数据,重定向两次请求,不能共享数据
4、相对路径: 转发地址不变会造成转发后的页面中的相对位置发生改变引起相对路径失效,重定向不会
5、效率:转发浏览器一次请求效率高,重定向效率低
6、WEB-INF下资源:转发可以访问,重定向不可以
7、跳转限制:重定向可以跳转到任意URL,转发只能跳转本站点资源
8、发生行为:重定向是客户端行为,转发是服务器行为
8、说出几种vue当中的指令及其用法?
v-model双向数据绑定;
v-for循环;
v-if v-show 显示与隐藏;
v-on绑定事件;
v-once: 只绑定一次。
9、说出vue的声明周期?(vue2、vue3)
当Vue对象创建之前触发的函数(beforeCreate)
Vue对象创建完成触发的函数(Created)
当Vue对象开始挂载数据的时候触发的函数(beforeMount)
当Vue对象挂载数据的完成的时候触发的函数(Mounted)
Vue对象中的data数据发生改变之前触发的函数 (beforeUpdate)
Vue对象中的data数据发生改变完成触发的函数(Updated)
Vue对象销毁之前触发的函数 (beforeDestroy)
Vue对象销毁完成触发的函数(Destroy)
10、thymeleaf是什么?常用的标签
Thymeleaf是一个模板引擎,可以替代jsp。开箱即用
Th:text:文本替换
Th:value:属性赋值
Th:href:链接地址
Th:if:条件判断
Th:action:表单提交的地址
11、什么是 XSS 攻击,如何避免?
XSS 攻击:即跨站脚本攻击,它是 Web 程序中常见的漏洞。原理是攻击者往 Web 页面里插入恶意的脚本代码(css 代码、Javascript 代码等),当用户浏览该页面时,嵌入其中的脚本代码会被执行,从而达到恶意攻击用户的目的,如盗取用户 cookie、破坏页面结构、重定向到其他网站等。
预防 XSS 的核心是必须对输入的数据做过滤处理。
12、 什么是 CSRF 攻击,如何避免?
CSRF:Cross-Site Request Forgery(中文:跨站请求伪造),可以理解为攻击者盗用了你的身份,以你的名义发送恶意请求,比如:以你名义发送邮件、发消息、购买商品,虚拟货币转账等。
防御手段:
- 验证请求来源地址;
- 关键操作添加验证码;
- 在请求地址添加 token 并验证。
Spring
1、谈谈你对Spring的理解
Spring是一个IOC和AOP 开源的轻量级容器框架,为简化企业级应用开发而生
Spring 容器的主要核心组件是:BeanFactory
控制反转(IOC),传统的 java 开发模式中,当需要一个对象时,我们会自己使用 new 或者 getInstance 等直接或者间接调用构造方法创建一个对象。而在 spring 开发模式中,spring 容器使用了工厂模式为我们创建了所需要的对象,不需要我们自己创建了,直接调用spring 提供的对象就可以了,这是控制反转的思想。对于 spring 框架来说,就是由 spring 来负责控制对象的生命周期和对象间的关系。
依赖注入(DI),spring 使用 javaBean 对象的 set方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程,就是依赖注入的思想。
面向切面编程(AOP),是面向切面编程的一种思想,是对OOP进行增强,可以将与业务无关的,却为业务模块所共同调用的非核心代码封装成(比如事务管理、日志管理、权限控制等等)一个个切面,然后在运行时通过动态代理注入到核心业务功能中。减少系统中的重复代码,降低了模块间的耦合度,同时 提高了系统的可维护性。AOP 底层是动态代理,如果是接口采用 JDK 动态代理,如果是类采用CGLIB 方式实现动态代理。
在我们的项目中我们自己写AOP的场景其实很少 , 但是我们使用的很多框架的功能底层都是AOP , 例如 :
1、统一日志处理
2、spring中内置的事务处理
2、SpringAOP和AspectJ的区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多
3、介绍一下Spring bean 的生命周期?
(1)默认情况下,IOC容器中bean的生命周期分为五个阶段:
- 调用构造器 或者是通过工厂的方式创建Bean对象(实例化)
- 给bean对象的属性注入值
- 调用初始化方法,进行初始化, 初始化方法是通过init-method来指定的.
- 使用
- IOC容器关闭时, 销毁Bean对象.
(2)当加入了Bean的后置处理器后,IOC容器中bean的生命周期分为七个阶段:
- 调用构造器 或者是通过工厂的方式创建Bean对象
- 给bean对象的属性注入值
- 执行Bean后置处理器中的 postProcessBeforeInitialization
- 调用初始化方法,进行初始化, 初始化方法是通过init-method来指定的.
- 执行Bean的后置处理器中 postProcessAfterInitialization
- 使用
- IOC容器关闭时, 销毁Bean对象
4、介绍一下Spring bean 的依赖注入方式?
- setter 属性注入
- 构造方法注入
- 注解方式注入
5、介绍一下Spring bean 的作用域?
理论上来说,常规的生命周期只有两种:
singleton:spring ioc 容器中只存在一个 bean 实例,bean 以单例模式存在,是系统默认值;
prototype:每次从容器调用 bean 时都会创建一个新的实例,既每次 getBean()相当于执行 new Bean()操作;
但基于Spring在Web 环境下,增加了一个会话维度来控制Bean的生命周期,主要有以下三种:
request:每次 http 请求都会创建一个 bean;
session:同一个session 共享一个 bean 实例;
global-session:针对全局 session 纬度,共享同一个 Bean 实例
「注意:」 使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。
6、请描述一下Spring 的事务管理
(1)声明式事务管理:,在 Spring 配置文件中声明或注解@Transactiona来处理事务。其本质是通过AOP实现,将事务管理代码从业务方法中抽离了出来,非侵入式的编程方式
(2)编程式事务控制:需要使用TransactionTemplate来进行实现,这种方式实现对业务代码有侵入性,因此在项目中很少被使用到。
7、Spring事务的传播方式?
首选,所谓的事务传播行为,就是多个声明了事务的方法相互调用的时候,这个事务应该如何传播。
比如说,methodA()调用 methodB(),两个方法都开启了事务。那么 methodB()是开启一个新事务,还是继续在 methodA()这个事务中执行?
PROPAGATION_REQUIRED:默认的事务传播行为,指的是如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
PROPAGATION_REQUIRES_NEW:不管是否存在事务,都会创建一个新的事务,如果当前存在事务,则把当前事务挂起。
8、Spring事务失效场景?
因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时, 那么这个注解才会⽣效 , 如果使用的是目标对象调用, 那么@Transactional会失效
事务不生效(7种)
1、自定义的事务方法(即目标方法)访问权限是非public的
2、方法用final或static修饰,不会生效
3、同一个类中的方法直接内部调用,会导致事务失效
4、@Transactional注解所在的类未被Spring管理
5、多线程调用事务会失效。spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。在多线程中获取到的数据库连接不一样,是不同的事务。
6、数据表(MyISAM)不支持事务
7、未开启事务,springboot通过DataSourceTransactionManagerAutoConfiguration类默认开启事务
事务不回滚(5种)
1、指定的传播特性错误。目前只有这三种传播特性才会创建新事务:REQUIRED,REQUIRES_NEW,NESTED。
2、手动 try…catch 捕获了异常
3、手动抛出别的异常。
①开发者没有手动捕获异常,但是抛得异常不正确,spring 事务也不会回滚
②开发捕获异常,又手动抛出了异常:Exception,事务也不会回滚。
4、自定义了回滚异常。
如 @Transactional(rollbackFor = BusinessException.class),而BusinessException是我们自定义的异常,报错的异常不属于BusinessException,所以事务也不会回滚。
5、嵌套事务回滚多了
8、spring中的BeanFactory和FactoryBean的区别是什么?
BeanFactory
- Spring 里面的核心功能是 IOC 容器,所谓 IOC 容器,本质上就是一个 Bean 工厂。
- 它能够根据 xml 里面声明的 Bean 配置进行 bean 的加载和初始化,然后BeanFactory 来生产我们需要的各种各样的 Bean。
- BeanFactory它是一个工厂类(接口),用于管理 Bean 的一个工厂。在 Spring 中,BeanFactory 是 IOC 容器的核心接口,它为 Spring 的容器定义了一套规范,并提供像 getBean 这样的方法从容器中获取指定的 Bean 实例。
- BeanFactory 在产生 Bean 的同时,还提供了解决 Bean 之间的依赖注入的能力,也就是所谓的 DI。
FactoryBean
- FactoryBean 是一个工厂 Bean,它是一个接口,主要的功能是动态生成某一个类型的 Bean 的实例,也就是说,我们可以自定义一个 Bean 并且加载到 IOC 容器里面。
- 它里面有一个重要的方法叫== getObject()==,这个方法里面就是用来实现动态构建Bean 的过程。
- Spring Cloud 里 面 的 OpenFeign 组 件 , 客 户 端 的 代 理 类 , 就 是 使 用 了FactoryBean 来实现的。
9、Spring的Bean对象的定义方式有几种?
1、通过xml配置bean
2、@CompontScan+@Component
3、@Configuration+@Bean
4、@Import:导入配置类或者普通的Bean
5、使用FactoryBean工厂bean ,动态构建一个Bean 实例
6、实现ImportBeanDefinitionRegistrar接口,可以动态注入Bean实例
7、实现ImportSelector接口,动态批量注入配置类或者Bean对象
10、什么是BeanDefinition?BeanDefinition中有哪些属性?
简单说就是对Bean信息的定义。
描述一个bean的全部信息,比如他的class类型、Bean的作用域、是否懒加载…
spring中每一个被扫描到的bean都会生成一个BeanDefinition。
BeanDefinition的主要作用是为了在只解析一次类的情况下,最大程度的拿到这类的信息。防止重复解析导致效率变低。
spring采用ASM(字节码解析的工具)技术去得到BeanDefinition。
常用的属性:
beanClass:表示Bean类型,未加载类的时候存放Bean的名字,加载类后存放Bean的class信息。
scope:表示Bean的作用域,一般值为单例或者原型。
lazyInit:表示Bean是否是懒加载。
initMethodName:Bean初始化需要执行的方法。
destroyMethodName:Bean销毁时要执行的方法。
factoryBeanName:创建当前Bean的工厂。
11、@Autowired和@Resource有什么区别?
这两个注解都能完成Bean对象依赖注入
1、来源不同:@Autowired 来自 Spring 框架,而 @Resource 来自于(Java)JSR-250;
2、依赖查找的顺序不同:
- @Autowired 先ByType,如果需要ByName,要结合@Primary 或者@Qualifier
- @Resource 先ByName再ByType;
3、支持的参数不同:@Autowired 只支持设置 1 个参数,而 @Resource 支持设置 7 个参数;
4、依赖注入的用法支持不同:@Autowired 既支持构造方法注入,又支持属性注入和 Setter 注入,而 @Resource 只支持属性注入和 Setter 注入;
5、编译器 IDEA 的提示不同:当注入 Mapper 对象时,使用 @Autowired 注解编译器会提示错误,而使用 @Resource 注解则不会提示错误。
12、Spring中常用的设计模式?
(1)代理模式——spring 中两种代理方式,若目标对象实现了若干接口,spring 使用jdk 的java.lang.reflect.Proxy类代理。若目标兑现没有实现任何接口,spring 使用 CGLIB 库生成目标类的子类。
(2)单例模式——在 spring 的配置文件中设置 bean 默认为单例模式。
(3)模板方式模式——用来解决代码重复的问题。
比如:RestTemplate、JdbcTemplate、JpaTemplate
(2)工厂模式——在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用同一个接口来指向新创建的对象。Spring 中使用 beanFactory 来创建对象的实例