java基础
java基础
1. 什么是 Java?特点?
- Java 是一门面向对象的编程语言
- 面向对象(封装,继承,多态),跨平台(一次编写,到处运行)
2. JVM、JDK、JRE区别
- JVM:Java Virtual Machine,Java 虚拟机,Java 程序运行在 Java 虚拟机上。针对不同系统的实现(Windows,Linux,macOS)不同的 JVM,因此 Java 语言可以实现跨平台。
- JRE: Java 运⾏时环境。包括 Java 虚拟机(JVM)和Java 类库
- JDK: Java Development Kit,它是功能⻬全的 Java SDK(软件开发工具包)。包括了 JRE 以及编译器(javac)、Java 文档生成工具(Javadoc)、Java 调试器等开发工具。为开发者提供了开发、编译、调试 Java 程序的一整套环境。
- JDK 包含 JRE,JRE 包含 JVM。
3. java跨平台实现原理
- 跨平台性,是指Java语言编写的程序,一次编译后,可以在多个系统平台上运行。
- 实现原理:Java程序是通过Java虚拟机在系统平台上运行的,只要该系统可以安装相应的 Java 虚拟机就可以运行 java 程序
4. java数据类型有哪些?
基本数据类型 | 八种 | 字节数 | 数据表示范围 | 默认值 | 包装类 |
---|---|---|---|---|---|
整型 | byte | 1 | -2^7^-1,2^7^-1 | 0 | Byte |
short | 2 | -2^15^,2^15^-1 | 0 | Short | |
int | 4 | -2^31,2^31-1 默认是int | 0 | Integer | |
long | 8 | -2^63^,2^63^-1 +L | 0 | Long | |
浮点型 | float | 4 | -3.403E38~3.403E38 E科学计数10^38 128-127次方+F 1位符号位,8位指数位(偏移量127,实际值应该减去127),23位尾数位,7位有效数字 | 0.0f | Float |
double | 8 | -1.798E308~1.798E308 E科学计数10^308 1024-1023次方+D 1位符号位,11位指数位,52位尾数位,16位有效数字,默认是双精度 | 0.0d | Double | |
布尔型 | boolean | 1 | true、false | false | Boolean |
字符型 | char | 2 | ''包裹,只包含一字符,运算按ASCII码对应的整数运算 | '' | Character |
引用数据类型 | 数组、类、接口 | String是对象 | null |
5. char能不能存一个中文汉字?
- Java中无论汉字还是英文字母都用Unicode编码(2字节)表示。char类型占2个字节。所以可以存储一个中文汉字
6. 存在数字i加1小于i或者i减1小于i?√
- 整型类型是有范围的,如果发生越界,那么将出现该情况
7. 什么是自动类型转换与强制类型转换?
- Java所有的数值型变量可以相互转换,当把一个表数范围小的数值或变量直接赋给另一个表数范围大的变量时,可以进行自动类型转换;反之,需要强制转换。
- 数据类型数据范围从小到大:byte -> short -> int -> long -> float -> double
- float f=3.4(×)3.4 是双精度数。需要强制类型转换float f =(float)3.4;或者写成float f =3.4F
- short s1 = 1; s1 = s1 + 1; 编译出错,由于1是int类型,因此s1+1运算结果也是int型,需要强制转换类型才能赋值给short
- short s1 = 1; s1 += 1;编译正确,因为s1+= 1相当于s1 = short(s1 + 1);有隐含强制类型转换。
8. 自动装拆箱和设计理由?
- 自动装箱是Java编译器在基本数据类型和对应的包装类之间做转化。比如:把int转化成Integer。反之就是自动拆箱。
- 为了简化代码,因为jdk1.5需要手写代码才能完成基本数据类型转成包装类放入集合中
9. &&运算符和&运算符区别?
- 都是表示与的逻辑运算符and,当两边的表达式都为true,结果才为true
- &&:有短路功能,当第一个表达式的值为false的时候,则不再计算第二个表达式;
- &:2个表达式都会执行。当两边的表达式不是Boolean类型则表示按位操作
10. switch表达式的值?
- byte、short、int、char、enum枚举、String
11. break,continue,return区别及作用?
- break跳出整个循环,不再执行循环
- continue跳出本次循环,继续执行下次循环
- return结束当前的方法 直接返回
12. 用最有效率的方法计算2乘以8?
- 2<<3。位运算,二进制位左移三位相当于乘以2的三次方。
13. 自增自减运算相关问题
- 当运算符放在变量之前时(前缀),先自增/减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增/减。
- i=i++结果i不变,因为JVM对自增运算会定义一个临时变量来接收i值,然后进行自增,等价于int temp = i;i++;i = temp;
14. Java 中的 Math.round(-1.5) 等于多少?
- round表示'四舍五入',算法为Math.floor(x+0.5)即加0.5后再向下取整,所以Math.round(1.5)=2,Math.round(-1.5)=-1
15. 面向对象OOP和面向过程OPP区别?
- 面向对象,它将现实世界中的事物抽象成对象,并通过类和对象的概念来模拟真实世界中的各种关系和操作。
- 面向过程,分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的一次调用就可以
- 区别
- 关注点不同:面向对象关注现实世界中的对象及其关系,而面向过程关注解决问题的步骤和过程。
- 数据和函数的关系不同:在面向对象中,数据和操作数据的方法被封装在对象中,而在面向过程中,数据和函数是分离的
- 代码组织方式不同:面向对象通过类和对象来组织代码,具有更好的模块化和可重用性;而面向过程通常按照功能或步骤来组织代码,可能导致代码结构混乱
- 扩展性和可维护性不同:面向对象通过封装、继承和多态等特性提高了代码的扩展性和可维护性;而面向过程在这方面表现相对较差
16. 面向对象有哪些特性?
- 封装:把对象的属性私有化,同时提供可以被外界访问的属性的方法.代码复用、安全性高
- 继承: 以已存在的类的定义作为基础建立新类。新类的定义可以增加新的属性或新的方法,也可以继承父类的属性和方法。通过继承可以很方便地进行代码复用,扩展性强,但耦合度增强
- 一个类只能继承一个类,所有的类都直接或者间接的继承了Object类
- 子类自动拥有父类所有有属性和方法,但只能访问父类非私有属性和方法
- 默认访问子类的[同名]成员变量/方法,不存在则访问父类非私有[同名]成员变量/方法。即使在父类构造方法中调用也能自动调子类方法
- 多态:指程序中定义的引⽤变量所指向的具体类型和通过该引⽤变量调用的⽅法在编程时并不确定,⽽是在程序运⾏期间才确定,即⼀个引⽤变量到底会指向哪个类的实例对象,该引⽤变量发出的⽅法调⽤到底是哪个类中实现的⽅法,必须在由程序运⾏期间才能决定。实现方式:子类继承父类、子类重写父类的方法、父类引用指向子类的对象或者类实现接口、类实现接口的方法,类引用指向接口
17. 重载和重写区别?√
- 方法重载overload:同一个类中多个名称相同、参数类型或个数或顺序不同的方法
- 方法重写override:父子类中方法名、参数列表、返回值相同、但方法体不同的方法
- 返回值、异常小于等于,少于等于父类
- 访问修饰符大于等于父类
- static方法,final方法,private方法,构造器Constructor不能被继承,因此不能重写Override
18. private,default,protected,public访问控制符范围
- private本类,default同包、protected同包及其子类,public所有
19. this关键字有什么作用?
- this代表对象本身
- 作用:
- 对象本身的引用
- 区分重名的形参和成员变量
- 引用本类构造函数
20. 抽象类和接口有什么区别?如何选用?√
- 相同点
- 都可以包含抽象方法
- 都不能直接实例化对象;直到抽象方法都覆盖完了才可以创建对象
- 不同点
- 一个类只能继承一个直接父类(可能是抽象类),却可以实现多个接口;(接口弥补了单继承)
- 抽象类可以定义构造函数;接口不能
- 抽象类可以包含具体方法;接口在jdk8才有默认方法
- 抽象类可以包含私有方法,接口在jdk9才有
- 二者的选用
- 优先选用接口,尽量少用抽象类
- 需要定义子类的行为,又要为子类提供共性功能时才选用抽象类
21. 成员变量与局部变量的区别有哪些?
- 从语法形式上看:成员变量是属于类的,⽽局部变量是在⽅法中定义的变量或是⽅法的参数;成员变量可以被 public , private , static 等修饰符所修饰,⽽局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
- 从变量在内存中的存储⽅式来看:如果成员变量是使⽤ static 修饰的,那么这个成员变量是属于类的,如果没有使⽤ static 修饰,这个成员变量是属于实例的。对象存于堆内存,如果局部变量类型为基本数据类型,那么存储在栈内存,如果为引⽤数据类型,那存放的是指向堆内存对象的引⽤或者是指向常量池中的地址。
- 从变量在内存中的⽣存时间上看:成员变量是对象的⼀部分,它随着对象的创建⽽存在,⽽局部变量随着⽅法的调⽤⽽⾃动消失。
- 成员变量如果没有被赋初值:则会⾃动以类型的默认值⽽赋值(⼀种情况例外:被 final 修饰的成员变量也必须显式地赋值),⽽局部变量则不会⾃动赋值。
22. 静态变量和实例变量的区别?静态方法和实例方法的区别?
- 静态变量: 被static修饰符修饰的变量,属于类,一个类不管创建多少个对象,静态变量在内存中有且仅有一个副本。
- 实例变量: 属于某一对象,需要先创建对象然后通过对象才能访问到它。
- 类/静态方法不能访问非静态方法和变量,实例方法可以访问类的所有成员变量和方法
23. final、finally、finalize区别?
- final是修饰符,修饰类不能被继承,修饰方法不能被重写,修饰变量不能被修改且必须在声明时初始化值。不可变指的是变量的引用不可变,不是引用指向的内容的不可变。
- finally只能在try/catch语句中,无论try块中的代码是否抛出异常,finally块中的代码一定会执行。常用于释放资源(I/O)。不会被执行的情况:在finally语句块中发生了异常;在代码中用了System.exit()退出程序;
- finalize:在Object类中的方法,在垃圾收集器删除对象之前对这个对象调用一次。finalize被调用不一定会立即回收该对象,所以有可能调用finalize后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会再次调用finalize了,进而产生问题,因此不推荐使用。
24. ==和equals区别?
- ==:如果比较的对象是基本数据类型,则比较的是数值是否相等;如果比较的对象是引用数据类型,则比较的是对象的地址值是否相等
- equals方法:没有重写相当于==,重写后比较两个对象的内容是否相等。String、Integer重写了
25. hashcode方法作用?两个对象的hashCode方法相同,则equals方法也一定为true吗?
- hashCode 方法主要用来获取对象的哈希码,哈希码是由对象的内存地址或者对象的属性计算出来的,它是⼀个int类型的整数,通常是不会重复的,因此可以用来作为键值对的键,以提高查询效率。
- 不一定。因为存在哈希碰撞,在散列表中,hashCode()相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等
- 为了解决哈希冲突的问题,哈希表在处理键时,不仅会比较键对象的哈希码,还会使用 equals 方法来检查键对象是否真正相等。如果两个对象的哈希码相同,但通过 equals 方法比较结果为 false,那么这两个对象就不被视为相等。
26. 为什么重写equals方法就一定要重写hashCode方法?
- 保证在equals相同的情况下hashcode值必定相同:如果只重写equals方法,那么被认为相等的对象可能会有不同的哈希码(默认是对象的地址,因此所有对象都是不同的哈希码),存储在集合不同的位置,导致集合出现重复元素或者无法找到对应元素(因为equals是根据对象的特征进行重写)
- 提升性能:hashCode能提高hashmap等集合去重的性能,如果没有重写hashCode()方法,那么集合每次添加元素都要遍历已有元素调用equals方法,性能太差,重写hashcode能减少equals方法调用次数
27. Java中的参数传递时传值呢还是传引用?
- java基本类型作为参数被传递时是值传递;引用类型作为参数被传递时也是值传递,参数值是对象的引用。
28. 深拷贝和浅拷贝?
- 浅拷贝仅拷贝被拷贝对象的基本数据类型变量的值和引用数据类型变量的地址值,而对于引用类型变量指向的堆中的对象不会拷贝
- 深拷贝完全拷贝一个对象,拷贝被拷贝对象的成员变量的值,堆中的对象也会拷贝一份。
- 浅拷贝实现Cloneable接口并重写Object类中的clone()方法;
- 深拷贝实现Serializable接口,通过对象的序列化和反序列化,或者使所有引用类型属性实现Cloneable接口
29. Java中创建对象的几种方式?
- 使用new关键字
- 使用Class类的newInstance方法,Class.forName.newInstance()
- 使用clone方法
- 反序列化,比如调用ObjectInputStream类的readObject()方法
30. Java对象初始化过程√
- 父类静态属性
- 父类静态代码块
- 子类静态属性
- 子类静态代码块
- 父类非静态属性
- 父类构造代码块
- 父类构造方法
- 子类非静态属性
- 子类构造代码块
- 子类构造方法
31. 怎么精确表示任意精度的整数和任何精度的定点数或者货币?√
- BigInteger任意精度的整数
- BigDecimal任何精度的定点数->货币
- 转成分用int运算
32. String是Java基本数据类型吗?可以被继承吗?
- 引用类型,String被final修饰不能被继承
33. String是不可变类吗?字符串拼接是如何实现的?
- String是不可变的,1.8以前+的拼接操作,如果字符串在常量池中不存在,则会生成新的对象。1.8时被优化为基于StringBuilder的append方法进行处理
34. 不同String创建方法分别创建了几个对象?√
- 1.String str1 = "abc";2.String str2 = new String("abc")
- 1在内存的字符串常量池中存储abc字符串对象
- 2在内存的字符串常量池中找abc字符串对象,没有则创建;在堆中创建str2对象,引用指向abc字符串对象
35. String为什么是不可变的?设计原因?√
- 不可变:一旦String对象被创建,它所包含的字符串内容是不可改变的。
- 不可变是因为引用的char数组(jdk1.9后改为byte数组)被final修饰。字符串的值本身不能改变,但引用字符串的变量中记录的地址值是可以改变的。每次修改操作(如拼接、替换等)都会产生新对象。
- 避免内存浪费:当创建一个String对象时,如果字符串值在常量池中已经存在则不会创建,只是引用已经存在的对象
- 性能优化:字符串不变性保证了hash码的唯一性,允许String对象缓存频繁使用的HashCode(如HashMap),不必每次都去计算新的哈希码
- 安全性:String被许多的Java类用来当做参数,例如:网络连接地址URL、文件路径path、反射机制的String参数等, 如果String不是固定不变的,将会引起各种安全隐患。
36. String、StringBuilder、StringBuffer 的区别?√
- String:类的对象是不可变的;适用于字符串内容不经常改变的场景。在使用字符串常量或进行少量的字符串操作时使用。每次对String对象进行修改操作(如拼接、替换等)实际上都会生成一个新的String对象,而不是修改原有对象。大量字符串连接情况下,产生太多对象浪费内存。线程安全
- StringBuilder:适用于单线程环境下需要频繁修改字符串内容的场景,比如在循环中拼接或修改字符串,使用字符数组char[]保存字符串,可变类,每次对String对象进行修改操作(如拼接、替换等)都是直接在原有字符串对象的底层数组上进行,不产生新对象,线程不安全;效率高
- StringBuffer:适用于多线程环境下需要频繁修改字符串内容的场景,使用字符数组char[]保存字符串,可变类,方法加同步锁synchronized,线程安全;效率低
- 多线程强制使用StringBuilder()
public class StringBuilderHolder {
private final StringBuilder sb;
public StringBuilderHolder(int capacity) {
sb = new StringBuidler(capacity);
}
//3.避免重复构造String,重用StringBuilder
public StringBuilder resetAndGetStringBuilder() {
sb.setLength(0);
return sb;
}
}
//2.ThreadLocal避免多线程冲突
private static final ThreadLocal<StringBuilderHolder> threadLocalStringBuilderHolder = new ThreadLocal<StringBuilderHolder>() {
protected StringBuilderHolder initialValue() {
//1.设置好初始长度,超过char[]默认16调用System.arraycopy成倍复制扩容太浪费资源
return new StringBuilderHolder(256);
}
}
StringBuilder sb = threadLocalStringBuilderHolder.get().resetAndGetStringBuilder();
37. intern 方法有什么作用?
- 如果当前字符串内容存在于字符串常量池,直接返回字符串常量池中的字符串;否则,将此String对象添加到池中,并返回String对象的引用
38. int和Integer有什么区别?√
- integer是int的包装类,属于引用数据类型,而int是Java的基本数据类型
- Integer默认值是null,int的默认值是0
- Integer是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
- Integer变量必须实例化后才可以使用,而int不需要。
39. ==比较Integer、new Integer、int的值√
除以下情况外,==比较都是true
- new Integer(100) != new Integer(100) 因为new生成的两个integer指向堆中的地址,而引用数据类型比较时实际是比较地址是否相同。
- new Integer(100) != Integer i=100 因为非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象
- Integer i = 128 != Integer j=128(Integer缓存-128-127的Integer对象,自动装箱时不会new新的Integer对象,而是直接引用缓存池中的Integer对象)
40. String转成Integer原理?
- String转成Integer:Integer.parseInt(String s)Integer.valueOf(String s)
- 最终调用Integer类中的parseInt(String s, int radix)方法,字符串遍历计算负的值累减,1314=-1 * (-1+-310+-1100)
41. Error和Exception的区别?
![Exception.jpg](https://290ff162.telegraph-image-eg9.pages.dev/file/e35afb80114c9ff59b1db.jpg)
- 受检异常:编译器会检查并要求必须处理的异常。
- 非受检异常:编译器不会检查也不要求必须处理的异常.
- Throwable:所有错误或异常的超类,表示程序中可能会产生的异常
- Error(非受检异常)系统级的错误;运行环境中的内部错误或者硬件问题,会导致应用程序中断.如:系统崩溃、虚拟机错误、内存空间不足、方法调用栈溢出等
- Exception:程序可以处理的异常
- 编译期异常Checked Exception(受检异常)Exception中除RuntimeException及其子类之外的异常。通常不会自定义该类异常,而是直接使用系统提供的异常类。
- 运行时期异常RuntimeException及其子类异常(非受检异常),出现原因大多因为代码本身有问题应该从逻辑上去解决并改进代码
42. 异常的处理方式?
- throw,throws抛出,自己不处理,抛给调用者处理(throws异常类用在方法上,可以跟多个异常类;throw异常对象用在方法内)
- try catch捕获异常;可选择加上finally语句块,finally 语句块不管程序是否正常执行,最终它都会必然执行。
43. 异常处理经典题?
//tryreturn准备->catch->finally->tryreturn
public class TryDemo {
public static void main(String[] args) {
System.out.println(test1());//执行结果31。在return前会先执行finally,所以是先输出3,再输出1
System.out.println(test2());//执行结果3。try返回前先执行finally,结果finally直接return了,就走不到try里面的return了
System.out.println(test3());//执行结果2.执行finally之前将i的结果暂存,finally执行完毕后返回之前暂存的结果,而不是返回3
}
public static int test1() {
try {
return 1;
} catch (Exception e) {
return 2;
} finally {
System.out.print("3");
}
}
public static int test2() {
try {
return 2;
} finally {
return 3;
}
}
public static int test3() {
int i = 0;
try {
i = 2;
return i;
} finally {
i = 3;
}
}
}
44. 什么是java IO?
- Java IO流的40多个类都是从如下4个抽象类基类中派生出来的。
- InputStream/Reader: 所有输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
45. InputStream为什么不能重复读取?如何实现重复读取?√
- 因为inputStream设计就是这样的
- 使用ByteArrayOutputStream
public class InputStreamCacher {
private ByteArrayOutputStream byteArrayOutputStream = null;
public InputStreamCacher(InputStream inputStream) {
byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1 ) {
byteArrayOutputStream.write(buffer, 0, len);
}
byteArrayOutputStream.flush();
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
public InputStream getInputStream() {
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
}
}
InputStreamCacher cacher = new InputStreamCacher(inputStream);
InputStream stream = cacher.getInputStream();
46. 字节流和字符流区别?
- Java用Unicode编码存储字符
- 其实字符流是由 Java 虚拟机将字节转换得到的,为了方便在不知道编码的情况下操作字符,提高读写效率
- 字节流按8位传输(操作字节和字节数组、数字_)以字节为单位输入输出数据
- 字符流按16位传输(由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的)对多国语言支持性比较好
- 音频文件、图片、歌曲使用用字节流,中文文本的,使用字符流
- 不管文件读写还是网络发送接收,信息的最小存储单元都是字节
47. BIO,NIO,AIO 区别
- BIO(Block IO):同步阻塞。服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善.适用于连接数较少的场景。基于 Socket 和 ServerSocket 进行网络通信。
- NIO(non-blocking IO)同步非阻塞。服务器实现模式为一个IO请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理.适用于连接数多但连接时间短的场景。基于 SocketChannel 和 ServerSocketChannel 进行网络通信。服务器可以用一个线程处理多个客户端连接,通过 Selector 监听多个 Channel 来实现多路复用,极大地提高了网络编程的性能。
- AIO(Asynchronous I/O)异步不阻塞。服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.适用于连接数较多且连接时间较长的应用,适用于连接数多且连接时间长的场景。
48. 什么是序列化和反序列化?
- 序列化 (Serialization)把Java对象转为二进制流,方便存储和传输.反序列化就是把二进制流恢复成对象。
- 场景:将内存中的对象状态保存到一个文件中或者数据库中;套接字在网络上传送对象;通过RMI传输对象
- Serializable接口作用:只是一个标记,无作用,但是如果不实现这个接口,在有些序列化场景会报错,推荐实现
- serialVersionUID作用:用来验证序列化的对象和反序列化对应的对象ID是否一致。如果没有显示指定serialVersionUID,则编译器会根据类的相关信息自动生成一个。如果你没有定义serialVersionUID, 结果序列化对象之后,在反序列化之前把对象的类的结构改了,比如增加了一个成员变量,则此时的反序列化会失败。
- 序列化不包含transient修饰变量和静态变量。transient只能修饰变量,不能修饰类和方法。
- 序列化方法:
- Java对象序列化:通过对象输出流ObjectOutputStream和对象输入流ObjectInputStream。
- Json序列化:使用jackson包,通过ObjectMapper类操作,比如将对象转化为byte数组或者将json串转化为对象。
- ProtoBuff序列化:ProtocolBuffer是一种轻便高效的结构化数据存储格式,ProtoBuff序列化对象可以很大程度上将其压缩,可以大大减少数据传输大小,提高系统性能。
49. 什么是零拷贝?
DMA:直接内存访问(Direct Memory Access)是一种硬件设备(网卡、显卡等)绕开CPU独立直接访问内存的机制。提高了CPU效率。DMA和硬件交互并且将数据从磁盘缓冲区拷贝到内核缓冲区
传统数据交互模式
- 读数据过程:涉及2次空间切换(内核态<=>用户态)、1次DMA拷贝(磁盘=>内核缓冲区)、1次CPU拷贝(内核缓冲区=>用户缓冲区);
- 应用程序要读取磁盘数据,调用read()函数实现用户态切换内核态,这是第1次状态切换;
- DMA控制器将数据从磁盘拷贝到内核缓冲区,这是第1次DMA拷贝;向CPU发起I/O中断,报告CPU数据已经Ready了;
- CPU收DMA的I/O中断之后,CPU将数据从内核缓冲区复制到用户缓冲区,这是第1次CPU拷贝;
- CPU完成拷贝之后,read()函数返回实现内核态切换用户态,这是第2次状态切换;
- 写数据过程:涉及2次空间切换(内核态<=>用户态)、1次DMA拷贝(内核缓冲区=>socket缓冲区)、1次CPU拷贝(用户缓冲区=>内核缓冲区);
- 应用程序要向网卡写数据,调用write()函数实现用户态切换内核态,这是第1次切换;
- CPU将用户缓冲区数据拷贝到内核缓冲区,这是第1次CPU拷贝;
- DMA控制器将数据从内核缓冲区复制到socket缓冲区,这是第1次DMA拷贝;
- 完成拷贝之后,write()函数返回实现内核态切换用户态,这是第2次切换;
- 读数据过程:涉及2次空间切换(内核态<=>用户态)、1次DMA拷贝(磁盘=>内核缓冲区)、1次CPU拷贝(内核缓冲区=>用户缓冲区);
零拷贝技术:在应用程序不对数据做修改前提下,减少从内核缓冲区到用户缓冲区,再从用户缓冲区到内核缓冲区两次数据拷贝(需要CPU参与)和用户态与内核态的两次切换,直接在内核态完成数据拷贝
实现拷贝方法:mmap+write(RocketMQ),sendfile(Kafa),sendfile+DMA收集,splice方式
mmap是Linux提供的一种内存映射文件的机制,将内核中读缓冲区地址与用户空间缓冲区地址进行映射,实现内核缓冲区与用户缓冲区的共享。效果是将读写2个CPU拷贝改为1个内核缓冲区到socket缓冲区的CPU拷贝,mmap对大文件传输有优势,而小文件可能出现碎片;当你的程序map了一个文件,然后被另一个进程截断(truncate)时, write系统调用会因为访问非法地址而被SIGBUS信号终止。SIGBUS信号默认会杀死你的进程并产生一个coredump并中止服务;不可靠,写到mmap中的数据并没有被真正的写到硬盘,操作系统会在程序主动调用flush的时候才把数据真正的写到硬盘
sendfile方式,建立了两个文件之间的传输通道。效果是少了(读数据时内核到用户和写数据时用户到内核)2次状态切换和将读写2个CPU拷贝改为1个内核缓冲区到socket缓冲区的CPU拷贝,由于数据不经过用户缓冲区,因此该数据无法被修改。只有2次状态切换、1次CPU拷贝、2次DMA拷贝。但是sendfile在内核缓冲区和socket缓冲区仍然存在一次CPU拷贝
sendfile+DMA收集,需要硬件DMA控制器的配合。升级后的sendfile将内核空间缓冲区中对应的数据描述信息(文件描述符、地址偏移量等信息)记录到socket缓冲区中。DMA控制器根据socket缓冲区中的地址和偏移量将数据从内核缓冲区拷贝到网卡中,只有2次状态切换、0次CPU拷贝、2次DMA拷贝,但是仍然无法对数据进行修改,并且需要硬件层面DMA的支持,并且sendfile只能将文件数据拷贝到socket描述符上,有局限性
splice无需硬件支持,并且不再限定于socket上,实现两个普通文件之间的数据零拷贝。splice系统调用可以在内核缓冲区和socket缓冲区之间建立管道来传输数据,避免了两者之间的CPU拷贝操作。splice的两个文件描述符参数中有一个必须是管道设备
50. 泛型和类型擦除原理?
- 泛型(generics)提供了编译时类型安全检测机制,本质是参数化类型,所操作的数据类型被指定为一个参数。
- 好处是编译时检查类型安全,并且所有的强制转换都是自动和隐式的,不需要使用显式转换和instanceOf操作符,提高代码重用率
- 泛型擦除是指编译器在编译时擦擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息
- 为什么需要泛型擦除?为了向下兼容
//泛型类
//在实例化泛型类时,必须指定T的具体类型Generic<Integer> genericInteger = new Generic<Integer>(123456);
public class Generic<T>{
private T key;
public Generic(T key) {this.key = key;}
public T getKey(){return key;}
}
//泛型接口
public interface Generator<T> {
public T method(T key);
}
//实现泛型接口,指定类型:
class GeneratorImpl<T> implements Generator<String>{
@Override
public String method() {
return "hello";
}
}
//泛型方法
public static <E> void printArray(E[] inputArray)
{
for (E element:inputArray){
System.out.printf("%s", element);
}
System.out.println();
}
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray( intArray );
printArray( stringArray );
51. List中泛型的区别?
- List<? extends T>接受任何继承自T的类型的List
- List<? super T>接受任何T的父类构成的List
52. 说一下你对注解的理解?
- 注解本质上是一个标记,注解可以标记在类上、方法上、属性上等,标记自身也可以设置一些值
- 注解生命周期有三大类,分别是:
- RetentionPolicy.SOURCE:给编译器用的,不会写入class文件@Override,lombok注解
- RetentionPolicy.CLASS:会写入class文件,在类加载阶段丢弃,也就是运行的时候就没这个信息了
- RetentionPolicy.RUNTIME:会写入class文件,永久保存,可以通过反射获取注解信息@Autowired.AOP注解
53. 什么是反射?优缺点?应用场景?
- 在程序的运行状态中,可以构造任意一个类的对象,了解任意一个对象所属的类,了解任意一个类的成员变量和方法,调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为反射机制
- 优点:运行期类型的判断,动态加载类,提高代码灵活度
- 缺点:反射相当于解释操作,性能比直接的java代码要慢很多
- 功能:
- 在运行时构造一个类的对象
- 判断一个类所具有的成员变量和方法
- 调用一个对象的方法
- 生成动态代理
- 应用场景
- JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序
- Spring通过XML配置反射装载Bean的过程
- Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息
- 使用反射机制,根据这个字符串获得某个类的Class实例,注解等信息
- 动态配置实例的属性
- 工厂模式,使用反射机制,根据全限定类名获得某个类的Class实例
- 原理:Java程序执行分为编译和运行两步,编译之后会生成字节码(.class)文件,JVM类加载的时候,会加载字节码文件,将类型相关的所有信息加载进方法区,反射就是去获取这些信息,然后进行各种操作
54. 反射中,Class.forName和ClassLoader区别?
- 都可用来对类进行加载
- Class.forName(...)方法会将类的.class文件加载到JVM并对类进行解释并执行类中的static块
- ClassLoader只会将.class文件加载到JVM中,在newInstance才执行类中的static块
- Class.forName(name, initialize, loader) 方法也可控制是否加载static块,并且只有调用了newInstance方法采用调用构造函数,创建类的对象。
55. jdk1.8新特性
- 接口默认方法:允许给接口添加一个非抽象的方法实现,使用default关键字修饰
- Lambda表达式和函数式接口:Lambda表达式本质上是一段匿名内部类,允许把函数作为一个方法的参数(函数作为参数传递到方法中),使代码更加简洁,但最好不要超过3行
- Stream API:用函数式编程方式在集合类上进行复杂操作的工具,配合Lambda表达式可以方便的对集合进行处理
- 日期时间API:Java 8 引入了新的日期时间 API 改进了日期时间的管理。
- Optional类:用来解决空指针异常的问题。
56. Lambda表达式
- Lambda 表达式本质上是一段匿名内部类,也可以是一段可以传递的代码。
- 函数式接口(Functional Interface) 被@FunctionalInterface 注解修饰的能缩写成Lambda表示式,如Runnable,Comparator,Callable、Predicate、Function、Supplier、Consumer等等
- Lambda举例:new Thread( () -> System.out.println("Thread is running since Java8!") ).start();
57. Optional使用
- Optional是用于防范NullPointerException。包装对象(可能是 null, 也有可能非 null)的容器
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
58. Stream流用过吗?
- Stream对一个包含一个或多个元素的集合做中间操作或终端操作。终端操作会返回一个结果,而中间操作会返回一个Stream流
![javasteam.png](https://290ff162.telegraph-image-eg9.pages.dev/file/84dcdbd58d40d05030439.png)
59. maven常用命令
- mvn package打包到target
- mvn install打包到本地
- mvn deploy发送到私服