1. ==和equals有什么区别:从基本数据类型和引用数据类型的不同来回答
- ==
- 基本数据类型比较值
- 引用数据类型比较内存地址
- equals
- 基本数据类型不能使用equals
- 引用数据类型如果没有重写,比较值
2. hashcode方法做了什么,为什么重写equals必须要重写hashcode:因为要保证equals为true的同时hashcode也要相同,举例hashMap存储
- hashcode会返回调用者的hash值,一般来说hashcode会和equals一起使用,比如hashMap就是通过hashcode和equals来判断key是否重复。两个hashcode相同的对象,equals不一定为true。而equals为true的两个对象,其hashcode必须要相同。
- 比如hashMap的put方法,会在遍历链表的同时,通过equals和hashcode判断key是否重复,如果重复,进行替换。如果equals为true但是hashcode不相同,可能会造成key重复存储,违反了hashMap的key不重复原则。
3. 深拷贝和浅拷贝,Arraycopy和System.arraycopy
- 深拷贝:值传递,创建一个新对象存放基本数据类型和引用数据类型,修改一个不会影响另一个
- 浅拷贝:引用传递,只复制了基本数据类型和引用变量,引用指向没有复制,修改引用数据类型可能会影响原对象。
- Arrays.copyOf():创建一个新数组存放,然后调用System.arraycopy()进行复制。会修改原数组
- System.arraycopy():只拷贝已存在的数组元素,不会修改元素
4. 基本数据类型和引用数据类型:
- int,32位4字节
- byte,8位1字节
- char,16位2字节
- short,16位2字节
- long,64位8字节
- float,32位4字节
- double,64位8字节
- boolean,1位,0和1。
- 引用数据类型:包装类、String、数组、对象
5. 包装类的拆包和装包:int转Integer装包,Integer转int拆包
- 装包:Integer int1 = 1; //调用Integer.valueOf(1)
- 拆包:int int2 = new Integer(1) //拆包,调用int2.intValue()
6. Integer和int的==判断:Integer、int和缓存池比较
- Integer i1 = new Integer(123)和Iteger i2 = 123的区别
- new Integer(123)会在堆中分配空间创建123的Integer对象,每次都会创建一个不同内存地址的对象。
- 而i2 = 123会先查看包装类缓存池中是否存在123的Integer,如果有,i2变量名指向它。如果没有,新建一个123放入到缓存池中,然后i2变量名指向它。
- i1 == i2,返回false
- int和Integer的==判断
int int1 = 12;
int int2 = 12;
Integer integer1 = 12;
Integer integer2 = 12;
Integer i1 = 12;
Integer i2 = 128;
Integer i3 = 128;
int1 == int2; //true,基本数据类型,==比较值
int1 = integer1; //true,Integer拆箱,integer1.intValue(),基本数据类型,==比较值
integer1 == integer2; //false,堆中的两个不同对象,==比较内存地址
integer1 == i1; //false,integer1是堆中的,i1是包装类缓存池中的,不同的内存地址
i2 == i3; //false,Integer的范围-128~127,超出范围
- Integer与int比较时,调用intValue()拆箱
- Integer范围-128~127
7. String、StringBuilder(初始容量16)、StringBuffer比较:可变、运行速度、线程安全、使用场景来讲
- String是final类,是不可变的,线程安全,每次拼接都要创建一个新的对象来接收拼接后的字符串。效率低
- StringBuilder和StringBuffer可以通过append方法来拼接。效率高
- 运行速度:StringBuilder、StringBuffer、String
- 线程安全:StringBuffer的append方法用synchronized修饰
- 使用场景
- 单线程操作少:String
- 单线程数据量大:StringBuilder
- 多线程数据量大:StringBuffer
8. 字符串常量池:为什么要使用和如何使用
- 减少相同字符串的创建,节省空间
- String s1 = "123";
9. String s1 = "123"和String s2 = new String("123")的内存分析、String s2 = new String("123")创建了几个String对象
- String s1 = "123"时,先在栈中创建一个s1变量名,然后查看字符串缓存池中是否存在"123"字符串,如果有,s1变量名指向它,如果没有,创建一个"123"字符串放入缓存池,然后s1变量名指向它。
- String s2 = new String("123")时,先在栈中创建一个s2变量名,然后在堆中创建一个"123"的字符串对象,s2变量名指向它。
- s1 == s2; //false,引用数据类型,==比较内存地址,不相同
- s1在字符串缓存池创建了一个"123"字符串对象
- s2在堆中创建了一个"123"字符串对象
10. static关键字用法:修饰内部类、修饰方法、修饰变量、修饰代码块、全局变量和静态变量的区别
- 修饰内部类(不可继承):可以通过外部类.内部类
- 修饰方法:类名.方法名
- 修饰变量:类名.变量名,只加载一次,也就是内存中只存在一份副本。修改后的值就是最新值
- 修饰代码块:类加载时被执行,只执行一次
- 全局变量和静态变量
- 全局随着对象,静态随着类
- 全局只能被对象调用,静态可以被对象和类调用
- 全局变量在jvm的堆中,静态变量在jvm的方法区
11. 父子类加载过程
- 父类静态变量、父类静态代码块
- 子类静态变量、子类静态代码块
- 父类全局变量、父类全局代码块、父类构造
- 子类全局变量、子类全局代码块、子类构造
12. final关键字用法:修饰类、修饰方法、修饰变量、好处
- 修饰类:不可被继承
- 修饰方法:不可被重写
- 修饰变量:常量,一般与static连用,必须赋初值,不可修改。类常量存放在jvm方法区的常量池中。常量池1.6之前在方法区,1.7在堆,1.8在元空间
- 好处:
- 多线程下线程安全
- 常量池可以减少重复创建常量,减少内存开销
- final、finally、finalized
- final修饰符
- finally关键字,最终必定会执行代码块
- finalized是gc回收的前置工作
13. super和this的用法:this不能用于静态方法
- super用于调用父类的同名变量、同名方法、调用父类构造
- this访问子类的同名变量或者指代当前对象
- this不能用于静态方法。因为静态方法是随类加载而被加载的,而this指代的是调用静态方法的对象,但是静态方法加载时,调用对象还不一定存在。
14. 权限控制符:private、default、protected、public
- private当前类
- default当前包
- protected当前包和子类
- public所用
15. 三大特征:封装(实体类)、继承、多态
- 封装:将属性和方法写在一个类中。如实体类
- 继承:子类可以继承父类的非私有属性和方法。但不能继承父类的构造函数,仅可以通过super调用。java只支持单继承,一个子类只能有一个父类,一个父类可以有多个子类
- 多态
- 实现前提:继承或实现接口
- 表现形式:父类引用可以指向子类对象和接口引用指向实现类对象
- 编译时多态:重载,重载方法可以通过参数个数和参数类型在编译时就确定调用哪一个方法
- 运行时多态:重写,重写方法仅方法实现不同,编译时无法确定调用哪一个方法,只能在运行时从子类往上搜索
16. 重载和重写的区别
- 重载:参数类型和参数个数不同
- 重写:方法具体实现不同,子类权限必须大于父类
- 构造函数无法重写,但可以重载
17. 抽象类:定义、实现方式、构造函数、变量、方法、单继承
- 定义:使用abstract定义,extends继承,不能用private修饰
- 实现:子类必须重写抽象类的所有抽象方法
- 构造函数:抽象类可以有构造函数,但不能实例化
- 方法:抽象方法不能用private,可以有普通方法
- 变量:权限无要求
18. 接口:定义、实现方式、构造函数、变量、方法、多继承
- 定义:使用interface定义,implements实现,嵌套接口可以用private
- 实现:实现类必须重写接口所有非默认方法
- 构造函数:接口没有构造函数
- 方法:接口方法不能用private修饰,默认都是抽象方法
- 变量:默认是static final,必须赋初值
19. 抽象类和接口的区别:多继承、实现方式、构造函数、变量、方法、使用优先
- 多继承:抽象类只能实现单继承,接口可以实现多继承
- 实现:子类和实现类都必须重写方法
- 构造函数:接口没有构造函数,抽象类有
- 方法:接口方法默认是抽象方法,不能用private
- 变量:接口变量默认是static final,必须赋初值,且权限范围无要求
- 优先考虑使用接口
20. 各种变量在JVM中的位置:静态变量、局部变量(方法内)、全局变量(类中)
- 静态变量:jvm方法区
- 全局变量(类中方法外):无论是基本还是引用,都在堆中
- 局部变量(类中方法中):基本的变量名和值都在栈(局部变量表)。引用的变量名在栈(局部变量表),值在堆中
21. 多态:表现、好处、前提、运行时多态(重写)、编译时多态(重载)
- 表现:父类引用指向子类对象或者接口引用指向实现类的对象,从而可以访问他们的属性和方法2. 多态前提:继承和实现接口
- 好处:代码拓展性好
- 编译时多态:重载,编译时根据参数类型和参数个数确定调用哪个方法
- 运行时多态:重写,重写仅方法实现不同,编译时无法确定,运行时从子类开始往上找
22. 注解和反射:元注解、自定义注解、反射的含义、获取class实例的三种方式
- 元注解
- Target:value=Element.TYPE、METHOD
- Rentention:value=RententionPolicy.RUNTIME
- Document
- 自定义一个注解
1. 定义注解
@Target(value = Element.METHOD)
@Rentention(value = RententionPolicy.RUNTIME)
public @interface MyAnnotation{
String name() default "123";
int age();
}
2. 使用注解
public class Test{
@MyAnnotation(age = 18)
public void Test(){}
}
- 反射:当编译一个类之后,会产生一个.class文件,该文件内存放着class对象。类加载相当于class对象的加载,而反射可以在运行时通过.class字节码问题件生成类并实现对象的增强
- java.lang.reflect包提供了三个类
- Field:变量级别,get和set
- Method:方法级别,invoke进行增强
- Constructor:构造函数级别,newInstance创建实例
- 获取class对象的三种方法
- Class class = User.class
- Class class = User.getClass().getName();
- Classs class = Class.forName("...");可能出现路径异常
23. 异常:error和exception(受检和非受检)
- error是jvm错误
- exception是异常,分为受检异常和非受检异常
- 受检异常:try/catch
- 非受检异常:运行时发生异常
- try、catch、finally的执行顺序
- 如果没有异常,没有return。try和finally
- 如果有异常,没有return。try、catch、finally
- 如果有return,return返回所在模块的计算结果
//1. try中return
private int testReturn1() {
int i = 1;
try {
i++;
System.out.println("try:" + i);
return i;
} catch (Exception e) {
i++;
System.out.println("catch:" + i);
} finally {
i++;
System.out.println("finally:" + i);
}
return i;
}
//输出
try:2
finally:3
2
//2. catch中return
private int testReturn3() {
int i = 1;
try {
i++;
System.out.println("try:" + i);
int x = i / 0 ;
} catch (Exception e) {
i++;
System.out.println("catch:" + i);
return i;
} finally {
i++;
System.out.println("finally:" + i);
}
return i;
}
//输出
try:2
catch:3
finally:4
3
//3. finally中return
private int testReturn4() {
int i = 1;
try {
i++;
System.out.println("try:" + i);
return i;
} catch (Exception e) {
i++;
System.out.println("catch:" + i);
return i;
} finally {
i++;
System.out.println("finally:" + i);
return i;
}
}
//输出
try:2
finally:3
3
24. Linux内核IO模型
- 阻塞io:对应java中的bio,读写阻塞,线程发出io请求后,内核查看数据是否就绪,未就绪就阻塞,等到就绪之后,内核将数据拷贝到用户线程。返回结果给用户线程
- 非阻塞io:用户线程发起io请求后,先不等待结果,内核轮询查看数据,当数据就绪之后,将数据拷贝到用户线程并通知用户线程
- 多路复用io:对应java中的nio,selector线程不断轮询socket,当socket有连接时通知线程进行io。单线程管理多个socket,只关心活跃的socket连接。
- 信号驱动io:用户线程发起io请求后,socket注册一个信号函数,当数据就绪时,内核通知用户线程进行io操作
- 异步io:对应java中的Aio,用户线程只需要发起io请求,数据的拷贝交给内核完成,线程完全不阻塞
25. Java IO模型:同步和异步、BIO、NIO、AIO
- 同步异步、阻塞非阻塞
- 同步阻塞:线程发起io请求后需要等待结果才能继续执行
- 异步非阻塞:线程发起io请求后就可以执行其他操作,内核通过回调函数方式通知线程
- BIO:同步阻塞,一个socket对应一个线程,读写时线程被阻塞,无法进行其他操作
- 传统单线程模式,acceptor线程通过while true循环调用accept方法监听连接请求。一个socket对应一个线程,io时线程被阻塞,必须先完成一个请求才能继续执行下一个请求
- 伪异步多线程模式:服务器通过线程池接收连接请求
- 特点:
- 同步阻塞,一个线程一个连接。基于流
- NIO:同步非阻塞,同步指的是一个线程对应一个连接,非阻塞指的是selector多路复用器分发连接请求,channel是双向的
- selector:多路复用器,轮询监听多个channel的状态,基于buffer,可以同时进行读写。没有连接时会发生阻塞
- channel:双向通道,连接buffer
- buffer:缓冲区,数据的中转站
- 特点
- 同步:线程进行io时必须先完成一个请求才能继续下一个请求
- 非阻塞:selector多路复用器监听多个channel
- AIO:异步非阻塞,基于回调函数实现,线程只管发起请求,后续操作由内核完成,内核通过回调函数通知线程
26. select、poll、epoll
- select:监听一组fd_set,等待一个或多个fd成为就绪状态,完io
- 数据结构:数组,大小1024或2048
- 类型:read、write、except
- timeout超时函数
- 成功返回大于0,失败返回-1,超时返回0
- poll:监听一组pollfd
- 数据结构:数据,大小无上限
- 类型:多种pollfd
- timeout超时函数
- 成功返回大于0,失败返回-1,超时返回0
- epoll:注册fd_set,通过红黑树维护,回调函数通知线程
- 数据结构:红黑树
- 工作模式:LT水平、ET边缘
- LT:当epoll_wait检查到fd就绪时,通知线程,可以等到下一次触发时才处理。默认模式,阻塞和非阻塞
- ET:通知线程后必须马上执行,效率高,非阻塞
- 总结
- select
- 实现方式:遍历fd_set
- 数据结构:数组
- 时间复杂度:On
- 最大连接上限:1024,2048
- 使用场景:实时性好、移植性好
- poll
- 实现方式:遍历pollfd
- 数据结构:数组
- 时间复杂度:On
- 最大连接上限:无上限
- 使用场景:实时性要求不高
- epoll
- 实现方式:回调函数
- 数据结构:红黑树
- 时间复杂度:O1
- 最大连接上限:无上限
- 使用场景:Linux系统,长连接
26. 设计模式:单例、工厂、代理、手撕单例和简单工厂
1. 单例模式
- 用于封装工具类,使整个系统的数据统一。SpringBean的作用域singleTon用到了单例模式
- 特征:唯一实例、唯一构造、自己创建实例
- 饿汉模式:线程安全,类加载就创建实例,可能造成内存浪费,不适合创建大对象
public class Hungry{
private static Hungry hungry = new Hungry();
private static Hungry(){};
public static Hungry getInstance(){
return hungry;
}
}
- 懒汉模式:线程不安全,调用时才创建实例
public class Lazy{
private static Lazy lazy = null;
private static Lazy(){};
public static Lazy getInstance(){
if(lazy == null){
lazy = new Lazy();
}
return lazy;
}
}
- 双重检查:synchronized线程安全,volatile保证构造函数4指令不重排序
public class Double{
private static Double double = null;
/*
1. 申请空间
2. 初始化默认值
3. 执行构造
4. 连接引用
*/
private static volatile Double(){};
public static Double getInstance(){
//第一层null判断:减少进入synchronized代码块,提高性能。
if(double == null){
synchronized(Double.class){
//第二层null判断:避免创建重复单例
if(double == null){
double = new Double();
}
}
}
return double;
}
}
2. 工厂模式
- 创建者和调用者分类,如果创建对象时有重复代码,可以考虑使用工厂类创建
- SpringIOC的BeanFactory用到了工厂模式
- 简单工厂模式
public interface Car{
public void getName();
}
public class Wuling implements Car{
public void getName(){
System.out.println("五菱");
}
}
public class Factory{
public static Car getCarName(String name){
if(name.equals("五菱")){
return new Wuling();
}
}
}
public static void main(String[] args){
Car car = Factory.getCarName("五菱");
car.getName(); //输出五菱
}
3. 代理模式
- 三个对象
- 抽象对象:通过接口或抽象类定义真实对象需要实现的行为
- 代理对象:实现抽象对象,重写抽象对象的方法,提供具体实现给真实对象
- 真实对象:实现抽象对象,调用代理对象提供的方法实现抽象方法。
- 静态代理:手动编写的代理关系
- 动态代理:运行时由反射重写
- SpringAOP用到了动态代理
- 代理对象是实现类,使用jdk动态代理,重组字节码文件,由反射机制生成一个接口实现类
- 代理对象不是实现类,使用cglib动态代理,重写字节码文件,重写方法进行增强,final类不能使用cglib