Java基础
-
==和equals的区别
==比较的是值是否相等。
==作用于基本数据类,他比较的是内容
==作用于引用数据类型,比较的是地址值
equals比较的是对象是否是同一个对象,比较的是对象的内容
equals()方法存在于Object类中,在没有重写之前和==是一样,也是比较的是引用数据类型的地址值,重写后的equals()方法一般都是比较对象的内容。
-
如何重新equals方法
重写equals()方法的签名,
检查参数是否为null以及是否是同一类型的对象,
将参数对象转换为当前类的类型,
比较对象的内容,可以使用适当的字段进行比较,
可选:重写equals()方法的同时也应该重写hashCode()方法,以保持equals()和hashCode()的一致性。
重新实现equals()方法时,需要根据类的具体情况来决定比较的逻辑和使用哪些字段进行比较。一般来说,比较对象的内容应该是基于对象的属性,而不只是
比较引用是否相等。
equals()方法的重写需要满足以下几个原则:
-
自反性:x.equals(x)应该始终返回true。
-
对称性:如果x.equals(y)返回true,那么y.equals(x)也应该返回true。
-
传递性:如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)应该返回true。
-
一致性:多次调用x.equals(y)应该始终返回相同的结果,前提是对象上的信息没有被修改。
-
非空性:x.equals(null)应该始终返回false。
这些原则有助于确保equals()方法的正确性和一致性。
-
-
重载跟重写的区别
重载是同一个类中,方法名相同,参数的位置不同,个数不同,数据类型不同。和方法的权限修饰符以及返回值类型无关。
重写(Overriding)指的是在子类中重新定义父类的方法,具有相同的方法名称、参数列表和返回类型。重写的目的是为了改变或扩展父类方法的行为。
子类重写父类中的方法规则:
-
不同的类中,类之间要有继承关系
-
方法名字和形参列表必须相同
-
返回值:
基本数据类型和void:保持一致
引用数据类型:子类重写的方法返回值可以是父类的子类
-
子类重写的方法的权限修饰符不能小于父类的权限修饰符
注意:不能重写private修饰的方法,因为private修饰的方法和属性不能被继承
-
子类重写的方法的异常声明应该与父类方法的异常声明保持一致或更宽松,不能更窄。
-
-
删除数组的思路
-
找到要删除的元素在数组中的索引位置。
-
根据不同的需求,有两种方法可以删除数组中的元素:
如果数组的大小是固定的(静态数组),不能直接删除元素,但可以将删除位置后面的元素向前移动一个位置,覆盖要删除的元素。
如果数组是可变长的(动态数组/集合),可以使用现有的数据结构或相关的库函数来删除元素。
-
-
删除静态数组中的元素:
int[] arr = {1, 2, 3, 4, 5};
int indexToDelete = 2; // 要删除元素的索引位置
// 将删除位置后面的元素向前移动一个位置
for (int i = indexToDelete; i < arr.length - 1; i++) {
arr[i] = arr[i + 1];
}
// 缩小数组的大小,舍弃最后一个重复的元素
int[] newArr = Arrays.copyOf(arr, arr.length - 1);
-
删除可变长数组/集合中的元素(使用ArrayList作为示例):
ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
int indexToDelete = 2; // 要删除元素的索引位置
list.remove(indexToDelete); // 使用ArrayList的remove方法删除元素
-
for循环的执行顺序
-
初始化表达式(Initialization):在执行循环之前,初始化表达式会被执行一次。这通常用于声明和初始化循环变量。
-
循环条件(Condition):在每次循环迭代之前,循环条件会被检查。如果条件为真,循环会继续执行,否则退出循环。
-
执行循环体(Body):如果循环条件为真,将执行循环体中的代码块。循环体内通常包含一些要被重复执行的语句。
-
更新表达式(Update):在每次循环迭代的最后,更新表达式会被执行。它通常用于更新循环变量的值。
-
回到步骤2:在执行完更新表达式后,程序会回到步骤2,检查循环条件。如果循环条件仍然为真,继续执行下一次迭代,否则退出循环。
这个循环过程会不断重复直到循环条件为假。如果循环条件一开始就为假,循环体内的代码将不会执行。
注意:循环体内的代码可以包含
break
和continue
等控制语句,它们会影响循环的执行流程 -
-
break、return、continue的区别
break 跳出当前循环
return 结束代码
continue 跳出本次循环,进入下次循环
-
switch的使用及注意方法
在Java中,switch-case语句提供了一种多重条件分支的方式,可以根据不同的取值对变量进行判断,执行对应的分支代码。
-
基本语法:
switch (variable) {
case value1:
// 执行与 value1 匹配的代码
break;
case value2:
// 执行与 value2 匹配的代码
break;
...
default: // 可选
// 执行其他情况的代码
break;
}
// variable 是要进行判断的变量或表达式。
// case value1 表示与 value1 匹配时执行对应的代码块。
// break 关键字用于中断 switch-case 结构,防止一旦找到匹配的 case 后继续执行下面的 case。
// default 是可选的,表示当没有任何的 case 匹配时,执行 default 块中的代码。-
注意事项:
-
switch-case 语句适用于需要根据不同的值执行不同的代码块的情况,比较适合处理离散的取值。
-
变量类型必须是整型(包括 byte、short、int 和 char),枚举类型,或者是从 Java 7 开始支持的字符串类型(String)。
-
每个 case 后面的值必须是一个常量表达式(字面量、枚举常量或 final 常量)。
-
每个 case 分支的代码块结束后必须包含
break
语句,否则会继续执行下一个分支的代码,直到遇到break
或整个 switch-case 结束。 -
可以在 switch-case 结构中使用
default
,它相当于其他分支都不匹配时的备选分支。 -
在 Java 7 及以后的版本中,switch-case 支持使用字符串类型(String)进行匹配。
-
如果default语句在所有case最后,此时可以不加break 如果default语句之后还有case语句,如果不加break,则default语句执行过之后会继续下面的case语句,此时必须要在default之后加break语句,不过这种default用法是不推荐的,default顾名思义是缺省情况,只有任何条件都不匹配的情况下才会执行,所以应该将default语句放在所有case结束之后。
-
-
-
Java中的数据类型及各自的范围
基本数据类型:
整形
数据类型 关键字 占用内存 取值范围
字节型 byte 1个字节 -128~127
短整型 short 2个字节 -2^15~2^15-1
整形 int 4个字节 -2^31~2^31-1
长整型 long 8个字节 -2^63~2^63-1
浮点型
单精度 float 4个字节 精度小数位7位
双精度 double 8个字节 精度小数位15位
布尔 boolean 1个字节 true/false
字符 char 2个字节
默认的整型为:int
默认的浮点型:double
引用数据类型:类 -
Java中运算符
-
算术运算符
(+ - * / % ++ --) 取模:即取余数,例:int b=5;b%=10 ,5/10,得余数为5,所以输出为5.
自增或递增是++ ++b(前置)和b++(后置)的区别: ++b先自增后赋值,所以赋值是自增后的值。 b++先赋值后自增,所以赋值是自增前的值,然后b本身自增。 (自增值都是1) 自减或递减是-- --b(前置)和b--(后置)的区别: --b先自减后赋值,所以赋值是自减后的值。 b--先赋值后自减,所以赋值是自减前的值,然后b本身自减。 (自减值都是1)
-
关系运算符
(> >= < <= != ==)
-
赋值运算符
(= += -= *= /=)
-
逻辑运算符
&&短路与
&&和&的区别:(一假全假)
&&当前面有条件为false的时候,后面的条件不会再进行判断
&当前面有条件为false的时候,后面的条件会继续进行判断
||短路或
|| 和 | 的区别:(一真全真)
|| 当前面有条件为true的时候,后面的条件不会再进行判断
| 当前面有条件为true的时候,后面的条件会继续进行判断
!()取反
!() 取反的例子:
int a = 10; int b = 20;
a>b --输出的是假--false;
!(a>b)--输出的就是真--true 或 a>b--输出的是假--!false = true
-
三元运算符
数据类型 变量名 = 布尔类型表达式? 结果1:结果2; 布尔类型表达式结果是true,三元运算符整体结果为结果1,赋值给变量。 布尔类型表达式结果是false,三元运算符整体结果为结果2,赋值给变量。
-
-
String和stringbuffer和stringbuilder区别和常用方法
-
String(字符串不可变):
-
不可变性:String是不可变类,一旦创建,它的值就无法改变。对于每次对字符串的操作,都会创建一个新的String对象,而原始对象则保持不变。
-
线程安全:String类是线程安全的,可以被多个线程同时访问而不会出现并发问题。
-
常用方法:String类提供了许多用于操作字符串的方法,如拼接字符串、截取子串、查找字符或子串、替换字符或子串等。
-
-
StringBuffer(线程安全):
-
可变性:StringBuffer是可变类,它提供了许多方法来修改和操作字符串。当需要频繁进行字符串拼接、插入、删除等操作时,使用StringBuffer效率相对较高。
-
线程安全:StringBuffer是线程安全的,它的方法大多数都使用了synchronized关键字,可以被多个线程同时访问并保持数据一致性。
-
常用方法:StringBuffer类包含了String类的大多数方法,并额外提供了一些用于修改字符串的方法,如append()、insert()、delete()、reverse()等。
-
-
StringBuilder(非线程安全):
-
可变性:StringBuilder同样是可变类,功能与StringBuffer类似,但它的实现方式更加高效。当不需要考虑多线程安全问题时,推荐使用StringBuilder。
-
非线程安全:StringBuilder不是线程安全的,它的方法没有使用synchronized关键字,所以在多线程下使用StringBuilder可能会出现并发问题。
-
常用方法:StringBuilder类提供了与String和StringBuffer相同的方法,用于修改和操作字符串。它的用法与StringBuffer几乎相同。
-
常用方法示例(假设使用类的实例名为"str"、“buffer"和"builder”):
-
String常用方法:
-
length()
: 返回字符串的长度。 -
charAt(int index)
: 返回指定索引位置的字符。 -
substring(int beginIndex, int endIndex)
: 返回指定范围的子串。 -
concat(String str)
: 将指定字符串连接到原字符串的末尾。 -
contains(CharSequence sequence)
: 检查字符串中是否包含指定的字符序列。 -
replace(CharSequence target, CharSequence replacement)
: 将字符串中的指定字符序列替换为新的字符序列。
-
-
StringBuffer和StringBuilder常用方法:
-
append(String str)
: 将指定字符串追加到可变序列的末尾。 -
insert(int offset, String str)
: 在指定位置插入字符串。 -
delete(int start, int end)
: 删除指定范围内的字符。 -
reverse()
: 反转字符串。
-
-
-
Java中的内存图
Java的优点:跨平台,高复用,面对对象的语言,安全性高,性能高,简单易用,健壮性,分布式,解释执行,多线程。
jdk:jre+类库:jdk是Java的开发运行环境,当你需要开发并运行Java程序时,安装jdk即可。
jre:jvm(虚拟机)+核心类库:Java的运行环境,当你只需要运行一个Java程序时,安装jre即可。
jvm(虚拟机)里面包含了:
-
栈(Stack):栈是线程私有的,用于存储方法的调用和局部变量。每个线程在执行方法时,都会在栈上创建一个栈帧(Stack Frame),栈帧包含了方法的参数、局部变量和操作数栈(Operand Stack)。栈具有后进先出(LIFO)的特性,方法调用时会将栈帧压入栈,方法返回时弹出栈顶的栈帧。
-
堆(Heap):堆是线程共享的内存区域,用于存储对象实例和数组等动态分配的数据。在Java中,所有的对象都存储在堆内存中。对象在堆上分配的内存由垃圾回收器自动进行回收,不再被引用时会被释放。
-
本地方法栈(Native Method Stack):本地方法栈也是线程私有的,用于存储Java虚拟机调用本地方法(Native Method)时的参数和局部变量。本地方法是使用其他编程语言(如C、C++)编写的方法,它们与Java虚拟机之间通过本地方法接口(JNI)进行通信。
-
程序计数器(Program Counter Register):程序计数器是线程私有的,用于存储当前线程执行的字节码指令的地址。在多线程环境下,每个线程都有自己的程序计数器,用于记录线程执行的位置,使得线程切换后能正确地恢复执行。
-
方法区(Method Area):方法区也是线程共享的内存区域,用于存储类的结构信息、静态变量、常量池、方法代码等。方法区是在JVM启动时被创建,并且被所有线程共享。在较早的JVM规范中,方法区被定义为持久代(Permanent Generation),但在JDK 8及以后的版本中,持久代被移除,取而代之的是元空间(Metaspace)。
总结一下:
-
栈用于方法调用和局部变量的管理。
-
堆用于动态分配的对象实例和数组的存储。
-
本地方法栈是为本地方法提供参数和局部变量的存储。
-
程序计数器用于记录线程执行的位置。
-
方法区存储类的结构信息、静态变量、常量池、方法代码等
-
-
Java文件执行流程
-
编写源代码:首先,你需要编写Java源代码文件,它以
.java
为扩展名。在源代码文件中,你可以定义类、方法和变量等。 -
编译源代码:使用Java编译器(例如
javac
命令),将源代码文件编译成字节码文件。编译后的字节码文件以.class
为扩展名,它包含了Java虚拟机可以执行的指令。 -
类加载器加载类:Java虚拟机的类加载器负责将字节码文件加载到内存中,并将其转换为一个或多个类的定义。类加载器按照特定的规则和策略查找、加载和连接类。
-
字节码验证:在类加载的过程中,Java虚拟机会对字节码进行验证,以确保它符合Java虚拟机规范和安全要求。验证包括类型检查、访问权限验证、代码语义验证等。
-
内存分配:在字节码验证通过后,Java虚拟机会为类的实例和静态变量分配内存空间。这些对象存储在Java虚拟机的堆内存中,静态变量存储在方法区(元数据区)中。
-
初始化:在内存分配后,Java虚拟机会执行类的初始化操作。这包括执行静态代码块,初始化静态变量和静态方法等。类的初始化是线程安全的。
-
执行主方法:如果源代码文件中定义了
public static void main(String[] args)
方法,Java虚拟机将从该方法开始执行程序。该方法是Java程序的入口点。 -
执行程序:Java虚拟机按照字节码中的指令序列执行程序。指令可以包括变量操作、方法调用、条件判断、循环等。Java虚拟机使用栈来执行方法调用和参数传递,使用堆来存储对象。
-
程序结束:当程序执行完主方法中的语句或遇到
return
语句时,程序结束。Java虚拟机会回收对象占用的内存,并将结果返回给操作系统。
这些步骤描述了Java文件从源代码到最终执行的过程。Java的跨平台性体现在将源代码编译为字节码,而字节码是与平台无关的,可以在任何支持Java虚拟机的平台上执行。
-
-
类的加载器及加载过程
-
启动类加载器(Bootstrap Class Loader):也称为根加载器,是Java虚拟机的一部分,负责加载Java核心类库,如rt.jar等。它是虚拟机自身的一部分,用C++实现,并不继承自
java.lang.ClassLoader
。 -
扩展类加载器(Extension Class Loader):也称为扩展加载器,主要负责加载Java的扩展类库,位于JRE的
lib/ext
目录下。它是由Java编写的,继承自java.lang.ClassLoader
。 -
应用程序类加载器(Application Class Loader):也称为系统类加载器,负责加载应用程序类路径(Classpath)上指定的类库和用户自定义的类。它是由Java编写的,继承自
java.lang.ClassLoader
。在大多数情况下,我们自定义的类是由应用程序类加载器加载的。
此外,还可以通过继承
java.lang.ClassLoader
类来创建自定义的类加载器,实现特定的类加载行为。这些自定义的类加载器可以根据自身的需求加载特定位置的类文件,或者修改类加载的方式和规则。自定义类加载器通常用于实现类加载的一些特殊需求,如加载加密的类文件、从网络加载类等。需要注意的是,类加载器之间形成了层次结构,被称为委托机制(Delegation Model)。当类加载器需要加载某个类时,它首先会委托给父类加载器进行加载,只有当父类加载器无法加载时,才会由当前类加载器尝试加载。这种层次结构保证了类的唯一性和安全性。
-
-
面对对象
面对对象是一件事情,我们只需要指定一个对象去完成,而反馈给我们结果就可以。我们面对的是对象,所以称为面对对象
-
接口跟抽象类的区别
-
定义和特点:
-
接口:接口是一种完全抽象的类,它只定义了方法和常量的签名,没有实现任何具体的方法体。接口中的方法默认是抽象和公共的,而常量默认是公共、静态和最终的。接口用于定义一组行为的规范,可以被类实现。
-
抽象类:抽象类是一种部分抽象的类,它可以包含抽象方法和具体方法,抽象类不能被实例化。抽象方法只有声明而没有具体实现,需要在派生类中被重写。抽象类可以包含成员变量和构造方法。
-
-
继承关系:
-
接口:一个类可以实现多个接口,使用关键字
implements
来实现接口。通过实现接口,类可以拥有接口中定义的所有方法,实现类必须提供方法的具体实现。 -
抽象类:一个类只能继承一个抽象类,使用关键字
extends
来继承抽象类。通过继承抽象类,子类可以获得抽象类中定义的属性和方法,但子类可以选择性地重写抽象方法。
-
-
构造函数:
-
接口:接口中不能包含构造函数,因为接口不能被实例化。
-
抽象类:抽象类可以包含构造函数,用于初始化抽象类的成员变量。
-
-
成员变量:
-
接口:接口中可以定义常量(静态和最终的变量),但不能定义实例变量。
-
抽象类:抽象类可以包含实例变量、常量和静态变量。
-
-
默认实现:
-
接口:在Java 8及之后的版本,接口可以包含默认方法(默认实现),使用关键字
default
来修饰,默认方法可以提供方法的具体实现。默认方法可以被实现类继承或重写,也可以被其他接口调用。 -
抽象类:抽象类可以包含抽象方法和具体方法,具体方法可以提供方法的实现。
-
总结来说,接口更加抽象,只定义了方法和常量的规范,而抽象类具有更多的灵活性,既可以包含抽象方法,也可以包含实例变量和具体方法。接口适用于定义行为规范和实现多继承,而抽象类适用于定义共享的属性和行为,并提供一部分实现。选择使用接口还是抽象类取决于具体的需求和设计目标。
-
-
mybatis的工作原理
-
解析全局配置文件以及所对应相关的映射文件,加载解析的相关信息会存储在configuration对象中
-
configuration对象会跟sqlsessionfactory对象绑定。
-
当用户的请求过来时,我们就需要获取session对象,从工厂中获取,然后通过会话对象的相关的API做相应的处理
-
关闭会话。
-
-
mybatis中的缓存
缓存的作用:减低数据库的访问频率,从而提高数据源的处理能力,或者提高服务器的响应速度。
mybatis中的缓存分为一级缓存,和二级缓存
一级缓存是sqlsession会话级的缓存,用户查询的时候,会把从数据库查询的数据放入到一级缓存中,下次同一个会话再查询的时候,不会再走数据库, 直接从一级缓存中获取。减轻对数据库的压力。默认开启的。在分布式情况或多个会话的情况下,一级缓存可能会出现脏读。
二级缓存,二级缓存是全局的其中的数据可以实现共享。任何一个会话拿到了数据都会放入到二级缓存中的。不同的会话去查询同一个数据时,直接可以 从二级缓存中取到。如果二级缓存中没有,那么各自 在自己的一级缓存中查找,如果还没有,那么就从数据库查询,查询完成后,就会放入到一级缓 存和二级缓存中。
-
Java中的关键字有哪些
-
this 作用:1、在方法内部使用,表示这个方法所属的对象的引用。 2、在构造器中使用,表示该构造器正在初始化对象。 this 表示当前对象,可以调用类的属性、方法和构造器。 在方法内部需要调用此方法的对象时,就用this,具体用法:使用this区分局部变量和全局变量 例:this.age = age; 在方法中使用: this 调用属性和方法 在构造器中使用: this调用构造器必须放在第一行,一个构造器只能调用一个
-
super super理解为:父类的 可以用来调用父类的:属性、方法、构造器 1、我们可以在子类的方法或者构造器中,通过使用”super.属性“或“super.方法“的方式,调用父类中声明的属性或方法,但是,通长情况下,我们习惯省略”super.” 2、通过子类对象,调用重写的方法或者属性,会优先在子类中寻找此属性和方法,当在子类中找不到时,回去父类中去找 3、super调用父类的构造器: 必须在构造器中,才能使用“super(形参列表)“的方式,调用父类的构造器,必须写在第一行,和我们学习this时,使用“this(形参列表)” 调用本类中的构造器一样,也就是说:this(形参列表)和super(形参列表)只能出现一个。 在构造器的首行,如果不写 this(形参列表)或者super(形参列表),那么默认为:super() 注意:当我们通过子类的构造器创建子类对象时,我们一定会直接或者间接的调用其父类的构造器,进而调用父类的父类构造器,直到 java . lang . Object 中的无参构造为止。虽然调用了父类构造器,但是并没有创建父类对象
-
static static:静态的 可以用来修饰,属性、方法、代码块、内部类。不可以用来修饰构造方法。 属性 使用static修饰属性:静态变量(类变量)。 属性分为:静态属性和非静态属性。 非静态属性(实例变量):当我们创建一个个的类实例后,每一个实例都有一套独立的非静态属性。 静态属性(类变量):与非静态属性相反,所有对象,共享同一个静态属性。 静态变量随着类的加载而加载, 也就是说静态变量的创建,要早于对象的创建。 也就是静态变量可以直接使用 “类名.属性” 的方式调用,也可以使用“对象.属性”的方式调用。 也就是静态变量只会被加载一次,在内存中只会存在一份,存在方法区的静态域中。 方法 使用static修饰方法:静态方法(类方法)。 方法分为:静态方法和非静态方法。 非静态方法(实例方法):当我们创建一个个的类实例后,每一个实例都有一套独立的非静态方法。 静态方法(类方法):与非静态方法相反,所有对象(实例),共享同一个静态方法。 静态方法随着类的加载而加载, 也就是说静态方法的创建,要早于对象的创建。 静态方法可以使用 “类名.类方法()” 的方式调用,也可以使用对象.类方法()。 也就是静态方法只会被加载一次,在内存中只会存在一份。 静态方法只能调用静态的方法和属性 非静态方法中可以调用静态方法和属性。 静态方法中不能使用this、super static的用法总结 何时使用静态属性? 属性不会随着对象的不同而不同的属性、被所有对象共享的属性可以被声明为静态的。 何时使用静态方法? 当方法操作静态属性时、工具类中的方法,都习惯上写成静态方法。
-
final final—表示最终的 修饰类:表示该类不能被继承的 修饰方法:表示该方法不能被重写。 修饰属性(全局变量):变成:常量(全部大写),不能被修改(重新赋值),必须赋值。 方法中不能对常量进行赋值,因为常量在对象创建完成之前,必须完成初始化,方法是在对象创建完成之后才能调用所以方法中不能对其进行赋值。
-
-
Java中的权限修饰符有哪些
-
类和对象的区别
类似对象的抽象化,对象是类的具体化。
-
数组和集合的区别
-
大小固定 vs. 大小可变:
-
数组:数组在创建时需要指定大小,并且在运行时大小是固定的,无法改变数组的长度。
-
集合:集合的大小是可变的,可以根据需要动态添加或删除元素。
-
-
数据类型限制:
-
数组:数组可以存储任何类型的元素,包括基本类型和对象类型。
-
集合:Java的集合框架中提供了多种集合接口和实现类,每种集合类有特定的元素类型限制。例如,ArrayList可以存储任何类型的对象,而HashSet只能存储唯一的对象。
-
-
直接访问 vs. 迭代访问:
-
数组:可以通过索引直接访问数组中的元素,可以使用下标来读取或修改数组元素。
-
集合:集合通常需要通过迭代器或增强for循环来遍历集合中的元素,不能直接根据索引来获取元素。
-
-
长度确定 vs. 长度未知:
-
数组:数组在创建时需要指定长度,长度是确定的,可以通过
length
属性获取数组的长度。 -
集合:集合的长度是动态变化的,在运行时可以根据需要添加或删除元素,长度是未知的,可以使用集合的
size()
方法获取当前元素个数。
-
-
引用类型 vs. 泛型支持:
-
数组:数组可以存储基本数据类型和引用类型的元素,但在存储引用类型时并没有类型安全性检查。
-
集合:集合框架提供了泛型支持,可以指定集合中元素的类型,并在编译时进行类型安全检查,避免类型错误。
-
总结来说,数组是一种简单而底层的数据结构,适用于需要直接访问元素并且元素数量固定的情况。集合是Java提供的更高级的数据结构,提供了更多的功能和灵活性,适用于需要动态添加、删除和操作元素的情况,并且支持泛型,提供类型安全性保证。选择使用数组还是集合取决于具体的需求和设计目标。
-
-
Java中的自动开装箱
在Java中,自动拆装箱(Autoboxing and Unboxing)是指Java的编译器自动地在基本数据类型(如int、char、boolean等)和其对应的包装类型(如Integer、Character、Boolean等)之间进行转换。
自动装箱:当需要将一个基本类型的值赋给对应的包装类型时,编译器会自动地将基本类型的值转换为对应的包装类型对象。例如,将一个int类型的值赋给Integer类型的对象时,编译器会自动进行装箱操作。
int primitiveValue = 42;
Integer boxedValue = primitiveValue; // 自动装箱自动拆箱:当需要将一个包装类型的对象赋给对应的基本类型时,编译器会自动地提取包装类型对象中的基本类型值。例如,将一个Integer类型的对象赋给int类型的变量时,编译器会自动进行拆箱操作。
Integer boxedValue = 42;
int primitiveValue = boxedValue; // 自动拆箱自动拆装箱使得基本类型和包装类型之间的转换更加方便,可以在需要使用包装类型的地方直接使用基本类型,反之亦然,编译器会自动进行转换操作。这样可以节省编码的复杂性和提高代码的可读性。
然而,需要注意自动拆装箱操作可能会带来性能上的开销,因为在拆箱时需要将包装类型对象转换为基本类型值,在装箱时需要将基本类型值转换为包装类型对象。在某些性能敏感的场景中,应谨慎使用自动拆装箱,以避免不必要的性能损耗。
-
Java中集合的体系及各自的方法
-
Collection单列集合
-
list(List中特有的迭代器:新增 hasPrevious():判断有没有上一个元素,previous():指针指向上一个元素,并将该值返回。)
-
ArrayList存储有序可重复的元素,线程不安全的,效率高,底层是数组,查询效率比较快,增删相率比较低
JDK1.7中的ArrayList 底层创建了一个长度为10的Object[]数组,当添加的元素个数不超过10时,底层的数组是不会扩容的,超过之后会将数组扩容成原数组长度的1.5倍,同时将元素组中的元素拷贝到新数组中。 JDK1.8中的ArrayList底层创建了一个长度为0的Object[]数组,当第一次添加元素时,在创建一个长度为10的数组(类似于饿汉式:延迟加载、节省内存),之后的扩容和JDK1.7中的方式相同
总结:开发中建议使用带参构造器创建ArrayList(int capacity),避免频繁扩容。
常用方法:add();addAll();get();indexOf();lastindexOf();remove();set();sublist();
-
LinkedList底层使用双向链表存储,插入和删除比较快,查询比较慢,线程不安全.
常用方法:addfirst();addLast();removeFirst();removeLast();getFirst();getLast();
-
Vector线程安全的,效率低,底层是数组。(1.0比较老的数组了).
-
-
set存储无序不可重复的元素
-
hashset
HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除的性能 HashSet的特点:底层是一个数组(加链表)+红黑树。 1、不能保存元素的存储顺序 2、HashSet不是线程安全的 3、集合元素可以是null
-
LinkedHashSet
LinkedHashSet根据元素的hashCode()返回值来决定元素的存储位置,但是他同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。LinkedHashSet插入性能略低于HashSet,但是在迭代访问其中的全部元素时,有很好的性能。LinkedHashSet不允许集合元素重复。作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,用来记录前一个数据和后一个数据,所以对于使用频繁遍历时,它的效率会高于HashSet。
-
-
SortedSet
-
TreeSet底层是使用TreeMap实现的,而TreeMap的底层是二叉树(红黑树)。输出结果有序,查询速度快,比list还要快,二叉树不允许重复的数据添加进去。TreeSet可以确保集合元素处于排序状态。红黑树的排列,小左大右。
新增方法:comparator(); first();last();lower(Object o);higher(Object o);subset(formElement , toElement); headset(toElement); tailSet(formElement);TreeSet两种排序方法:自然排序、定制排序。默认情况下,TreeSet采用自然排序
-
-
-
-
Map双列集合保存键值对的集合
-
HashMap是Map的主要实现类,效率高、线程不安全,可以存储null的key和value
1.7jdk的hashmap底层是由数组+链表实现的。1.7中会首先创建一个长度为16的数组,当向hash桶插入数据时,根据hashcode的算法来确定key在hash桶的位置,如果当前位置没有元素,那么直接插入进去,如果有元素,那么用equals方法来判断一下两个key是否相等,相等的话,那么就会覆盖前一个,如果不相等,那么原位置的元素会向下挪动给新元素腾出来(实际中,只是把哈希桶的记录地址改为新元素的地址),两个元素形成链表。当元素的个数超过临界值<!--是拿数组的长度✖负载因子(0.75)得到的临界值-->时,数组就进行扩容,扩容为原来的2倍大小。(头插法。但是在多线程的情况下会出现扩容死循环)。
1.8jdk的hashmap底层是由数组+链表+红黑树实现的,java8中当向hash桶插入数据时,底层是一个长度为0的数组,当第一个元素插入进去时,会创建一个长度为16的数组(延迟加载),根据hashcode的算法来确定key在hash桶的位置,如果发生哈希碰撞时,先比较两个key的hash值是否相等,如果不相等,就形成链表(尾插法),如果相等,再调用equals方法比较两个key是否相等,如果相等,就覆盖,如果不相等,就形成链表。当元素的个数超过临界值<!--是拿数组的长度✖负载因子(0.75)得到的临界值-->时,数组就进行扩容,扩容为原来的2倍大小。当链条的长度大于等于8并且数组的长度大于等于64时,链表就会变成红黑树。当红黑树的数据小于6时,就退化成链表。<!--每个node节点存储着用来地位数据索引的hash值,k键,v值,以及指向链表下一个节点的node<k,v>next节点组成。node是hashmap的内部类,实现了map.Entry接口,本质是一个键值对。-->(多线程并发的情况下在链表转换红黑树的时候,容易产生死循环以及数据丢失的情况)
-
LinkedHashMap是HashMap的子类,保证再遍历map集合时,可以按照添加的顺序进行遍历。原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素对于频繁的遍历操作,此类的执行效率会高与HashMap。
-
ConcurrentHashMap不允许为空的。
1.7版本。它的底层和1.7的hashmap有点区别,是有一个大数组(segment),跟一个小数组(hashentry),再加链表的形式实现的。使用分段锁来保证线程安全。在segment上加锁。
1.8版本它的底层是node+CAS+synchronized实现的,只不过,在它node的节点或者是红黑树的首节点都添加了锁,这样就保证了它在并发的情况下线程安全
-
-
Hashtable作为很老旧的实现类,其线程是安全的,所以效率低,并且不能存储null的key和value
-
SortedMap
TreeMap是SortedMap的实现类,保证按照添加的key-value对进行排序,实现排序遍历,此时考虑key的自然排序和定制排序。 它的底层使用的是红黑树
-
-
-
异常的体系及解决方式
异常的体系:
-
Throwable:解释:是所有的异常的顶层父类,抽取了所有异常的共性的属性和行为
-
Error:错误:属于异常中的一大分支,指的是程序员不能够进行捕获处理的异常。比如:内存溢出、栈溢出
-
Exception:异常;属于异常中的另一大分支,指的是我们可以使用代码捕获异常对象。
-
RuntimeException:运行异常,指的是那些编译没有问题,运行会发生异常的现象比如:空指针异常,除数为0异常。
异常的处理:
-
声明异常,也就是我们常说的抛异常,我们自己不处理,抛给JVM去处理。使用关键字throws在方法的声明的后面声明异常的类型。
-
捕获异常处理:
当方法体中可能出现异常,方法体自己去捕获这个异常对象,并对异常对象内容进行相关处理 格式: 1.try…catch 2.try…catch…finally 3.try…finally
-
-
io流的体系及高效流
IO流就是用来处理设备间数据传输问题的。常见的应用: 文件复制、文件上传、 文件下载等。
按照数据的流向:
输入流:读数据
输出流:写数据
按照数据类型:粒度
字节流: 字节输入流和字节输出流
字符流: 字符输入流和字符输出流
字节流和字符流的使用场景:
如果操作的是纯文本文件,优先使用字符流
如果操作的是图片、视频、音频等二进制文件,优先使用字节流
如果不确定文件类型,优先使用字节流.字节流是万能的流
字节缓冲流
字符缓冲流
-
Java中的线程池及线程池的生命周期
1.线程池的参数
-
核心线程数(Core Pool Size): 核心线程数是线程池中最小的线程数量。即使在空闲时,这些核心线程也会一直存在。核心线程被用于执行任务,直到任务队列被填满。
-
最大线程数(Maximum Pool Size): 最大线程数是线程池中能够容纳的最大线程数量。当任务队列已满且核心线程都在执行任务时,线程池会创建新的线程,直到达到最大线程数。
-
任务队列(Task Queue): 任务队列用于存储等待执行的任务。当线程池的线程都在执行任务时,新提交的任务会被放入任务队列中进行排队等待执行。
-
线程空闲时间(Keep-Alive Time): 线程空闲时间是指当线程执行完任务后,在终止之前保持空闲状态的时间。超过这个时间,如果线程池中的线程数量超过核心线程数,空闲的线程就会被终止,以减少资源占用。
-
拒绝策略(Rejected Execution Policy): 拒绝策略用于定义当线程池和任务队列都已满时,如何处理新提交的任务。常见的拒绝策略包括:
-
中止(Abort):直接抛出
RejectedExecutionException
异常。 -
丢弃(Discard):直接丢弃新提交的任务,不抛出异常。
-
丢弃最旧的任务(Discard Oldest):丢弃队列中最老的任务,然后尝试再次提交新任务。
-
调用者运行(Caller-Runs):由提交任务的线程自己执行该任务。
这些是线程池的一些主要参数,它们可以用于控制线程池的行为和性能特性。在使用线程池时,我们可以根据实际需求来调整这些参数以达到最佳的线程管理和任务执行效果。
-
2.线程池常见的类型
-
固定线程池(FixedThreadPool): 固定线程池是最简单的线程池类型,它包含固定数量的线程。一旦线程池被创建,线程池中的线程数量将保持不变,不会根据任务的数量进行调整。如果所有线程都被占用,新的任务将会在任务队列中等待。这种线程池适用于预先知道任务量较大且稳定的场景。
-
可缓存线程池(CachedThreadPool): 可缓存线程池会根据任务的数量自动调整线程的数量。如果线程池中有空闲线程,任务将会交给这些线程执行。如果没有空闲线程,则创建新的线程来处理任务。当线程长时间处于空闲状态时,它们会被终止并从线程池中移除。这种线程池适用于任务量不确定、任务执行时间较短的场景。
-
单线程线程池(SingleThreadExecutor): 单线程线程池只包含一个工作线程,它按照任务的提交顺序依次执行任务。如果该线程发生异常而终止,会创建一个新的线程来取代它。这种线程池适用于需要顺序执行任务,且保证任务按提交顺序执行的场景。
-
定时线程池(ScheduledThreadPool): 定时线程池可用于按照延迟或固定周期执行任务。它可以周期性地执行任务,并且可以设定任务的延迟时间。这种线程池适用于需要定时执行任务或周期性执行任务的场景。
这些是常见的线程池类型,每种类型都适用于不同的应用场景。我们可以根据具体情况选择最适合的线程池类型来管理和执行任务
3.线程池的生命周期
-
创建(Creation): 在创建线程池时,需要指定线程池的核心线程数、最大线程数、任务队列类型、拒绝策略等参数。线程池会根据这些参数初始化并创建相应数量的线程,并准备接收任务。
-
运行(Running): 线程池进入运行状态后,可以接收任务并调度执行。线程池会根据任务的数量和线程池的类型来决定是直接执行任务、将任务放入任务队列中等待执行,还是创建新的线程来执行任务。
-
停止(Shutdown): 当不再需要线程池时,可以调用线程池的
shutdown()
方法来停止线程池。停止线程池后,线程池不再接收新的任务,但会等待已提交的任务执行完毕。已提交但尚未执行的任务会被取消。 -
终止(Termination): 在所有已提交的任务都执行完毕后,线程池进入终止状态。终止状态意味着线程池已完全停止,并且所有线程都已销毁,线程池进入到它的生命周期的最终状态。
4.线程的生命周期
-
新建(New): 当线程对象被创建时,线程处于新建状态。此时操作系统还未为线程分配资源,线程尚未开始执行。
-
就绪(Runnable): 当线程被启动(
start()
方法被调用)之后,线程进入就绪状态。此时线程被添加到可运行线程池中,等待被调度执行。然而,并不能保证线程立即开始执行,而是由操作系统的线程调度器来决定何时分配处理器资源给线程。 -
运行(Running): 当线程得到处理器资源后,即进入运行状态。线程的
run()
方法被调用,线程开始执行其中的代码逻辑。 -
阻塞(Blocked): 在运行状态下,线程可能因为某些原因而暂时无法继续执行,进入阻塞状态。这些原因可能包括等待I/O操作、获取锁失败、等待某个条件的发生等。当条件满足时,线程将从阻塞状态恢复到就绪状态,等待重新调度执行。
-
等待(Waiting): 线程进入等待状态是因为调用了
wait()
方法,或者线程等待某个条件的发生。在等待状态下,线程不会被分配处理器资源,直到其他线程显式唤醒或满足等待条件。 -
定时等待(Timed Waiting): 定时等待状态类似于等待状态,但是它有一个等待时间限制。例如,调用了
sleep()
方法会使线程进入定时等待状态,线程将暂时休眠,等待指定的时间后再次进入就绪状态。 -
终止(Terminated): 线程最终会执行完其
run()
方法中的代码逻辑,或者在执行过程中抛出了未捕获的异常导致线程异常终止。当线程进入终止状态后,它将无法再次恢复执行。
需要注意的是,线程的状态转换是由操作系统和线程调度器管理的,程序中可以通过合适的方法进行线程状态的控制和同步。不同的编程语言和操作系统对线程状态和线程管理方式可能会略有差异,但是以上是线程的基本生命周期
-
-
MySQL的优化
-
设计合理的数据模型:良好的数据库设计是优化的基础。使用适当的数据类型和字段长度,规范化数据库结构,避免冗余和重复数据,可以提高查询效率和节省存储空间。
-
创建索引:根据查询的需求和访问模式,创建适当的索引可以加快查询速度。索引应该覆盖常用的查询条件和排序字段,但过多的索引会增加写操作的开销,需要权衡使用。
-
优化查询语句:合理编写和优化查询语句,使用
EXPLAIN
分析查询计划,避免全表扫描和不必要的数据检索。可以通过选择合适的查询操作符、使用合适的连接方式、避免使用SELECT *
等方式来提高查询效率。 -
优化表结构和数据类型:选择合适的数据类型可以节省存储空间和提高查询速度。避免使用过长的字段和冗余数据,使用合适的数据类型,比如使用整型来存储数字,使用日期时间类型来存储日期等。
-
配置合适的缓存:合理配置MySQL的查询缓存、查询缓冲和连接池等参数,可以减少重复查询的开销和提高并发处理能力。
-
使用合适的存储引擎:根据应用的需求和特点选择合适的存储引擎,如InnoDB、MyISAM等。不同的存储引擎在性能、事务支持和数据一致性等方面有所差异。
-
分区和分表:对于大型数据集,可以使用分区和分表来提高查询效率和管理数据。分区可以将数据水平划分到多个表空间,分表可以将表按照一定规则切分为多个子表。
-
定期优化和维护:定期进行数据库的优化和维护工作,如表碎片整理、统计信息收集、日志清理等,可以保持数据库的性能和稳定性。
-
-
索引分为哪几种
1.普通索引是最基本的索引,它没有任何限制。
2.唯一索引与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。
3.主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引
4.组合索引指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合
5.全文索引主要用来查找文本中的关键字,而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的where语句的参数匹配。fulltext索引配合match against操作使用,而不是一般的where语句加like。它可以在create table,alter table ,create index使用,不过目前只有char、varchar,text 列上可以创建全文索引。值得一提的是,在数据量较大时候,现将数据放入一个没有全局索引的表中,然后再用CREATE index创建fulltext索引,要比先为一张表建立fulltext然后再将数据写入的速度快很多。
-
索引在什么情况下会失效
-
where语句中包含or时,可能会导致索引失效.
-
where语句中索引列使用了负向查询,可能会导致索引失效.(负向查询包括:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等。)
-
索引字段可以为null,使用is null或is not null时,可能会导致索引失效 (注意:在mysql8.0之后是使用 not in 和 in 是走索引的)
-
在索引列上使用内置函数,一定会导致索引失效
-
隐式类型转换导致的索引失效(字段类型与sql语句类型不匹配时)
-
对索引列进行运算,一定会导致索引失效
-
like通配符可能会导致索引失效
-
联合索引中,where中索引列违背最左匹配原则,一定会导致索引失效
-
MySQL优化器的最终选择,不走索引
-
-
MySQL中的内连接和外连接
-
内连接:
-
隐式内连接
-
显示内连接
-
-
外连接:
-
-
spring的三级缓存
循环依赖是指循环引用,是指两个bean之间相互持有对方的引用。分为三种情况。1 A依赖B,B依赖A,2.A依赖B,B又依赖C,C又依赖A,3.A依赖自己A。
一级缓存存放着完整的成熟bean
二级缓存存放未完全赋值的bean,但别的bean又需要的bean
三级缓存 打破循环的作用,放bean的代理对象
-
公平锁和非公平锁的区别
公平锁和非公平锁是并发编程中用于对共享资源进行同步的两种锁机制。它们之间的主要区别如下:
-
获取锁的顺序:公平锁按照线程请求锁的顺序来获取锁,即先到先得的原则。而非公平锁则允许线程突然获取锁,不考虑其他线程的排队顺序。
-
线程的等待和获取:在公平锁中,当一个线程请求锁时,如果锁已经被占用,该线程会被放入等待队列中,按照先到先等待的顺序等待获取锁。而非公平锁在一个线程请求锁时,会先尝试获取锁,如果成功则获取锁,如果失败才会加入等待队列。
-
性能影响:由于公平锁要维护一个等待队列,并且需要判断等待线程的顺序,因此会增加系统的开销和调度成本。而非公平锁不需要维护等待队列和判断顺序,因此通常具有更好的性能。
-
公平性:公平锁能够保证所有等待获取锁的线程按照顺序获得锁,避免线程饥饿现象的发生。而非公平锁由于不考虑线程的排队顺序,可能会导致某些线程一直无法获取锁,产生线程饥饿问题。
选择使用公平锁还是非公平锁取决于应用场景和需求。如果系统要求按照线程的请求顺序来获取锁,以避免线程饥饿问题,那么可以选择公平锁。但公平锁的性能通常较差。如果对线程的顺序没有强制要求,并且希望获得更好的性能,那么可以选择非公平锁。
需要注意的是,在Java中,ReentrantLock和ReentrantReadWriteLock是可重入锁的具体实现,它们都可以作为公平锁或非公平锁使用,具体取决于构造函数的参数。默认情况下,它们都是非公平锁。
-
-
重量级锁
在Java中,重量级锁(也称为悲观锁)是一种同步机制,用于保护共享资源的并发访问。它的主要特点是使用了操作系统级别的线程阻塞和唤醒机制,适用于高并发环境下的互斥访问。
重量级锁的主要特征包括:
-
互斥性:重量级锁提供了互斥访问机制,同一时间只允许一个线程持有锁,并且其他线程需要等待锁的释放才能获取锁。
-
阻塞等待:当一个线程请求获取锁时,如果锁已经被其他线程持有,该线程会进入阻塞状态,等待锁的释放。这种等待通常通过操作系统提供的线程阻塞机制(如操作系统的管程或操作系统监视器锁)实现。
-
线程上下文切换:当一个线程从阻塞状态转为就绪状态时,操作系统需要进行线程上下文的切换,这涉及用户态和内核态之间的切换,开销相对较高。
-
高并发性能:重量级锁适用于高并发环境,当并发访问的线程数量较大时,它可以提供较好的资源访问控制和保护。
Java中的synchronized关键字就是一种重量级锁。当使用synchronized关键字修饰一个方法或代码块时,它会自动加上重量级锁的特性,确保方法或代码块的互斥访问和线程安全。
尽管重量级锁提供了强大的互斥性和线程安全性,但它也存在一些缺点。由于涉及阻塞和线程上下文切换,重量级锁在高并发场景下可能导致性能下降。此外,长时间持有重量级锁的线程会阻塞其他线程的访问,可能导致线程饥饿问题。因此,在设计并发程序时,需要根据实际需求和性能要求,综合考虑是否使用重量级锁。
-
-
vue的生命周期
-
创建阶段(Creation):
-
beforeCreate
:在实例被创建之初,数据观测和初始化事件还未完成。 -
created
:实例已经完成了数据观测、属性和方法的运算,但尚未挂载到DOM上。
-
-
挂载阶段(Mounting):
-
beforeMount
:在Vue实例挂载之前,即将开始编译模板并将其插入到DOM中。 -
mounted
:Vue实例已经挂载到DOM中,此时可以进行DOM操作和请求数据。
-
-
更新阶段(Updating):
-
beforeUpdate
:在数据更新之前,DOM尚未重新渲染。 -
updated
:数据已经更新,DOM已经重新渲染。
-
-
销毁阶段(Destruction):
-
beforeDestroy
:在Vue实例被销毁之前,可以进行善后工作,比如清除定时器、解绑事件等。 -
destroyed
:Vue实例已经被销毁,所有的事件监听器和子实例也会被销毁。
-
此外,还有一个额外的生命周期钩子函数:
-
activated
:在使用<keep-alive>
时,被缓存的组件激活时调用。 -
deactivated
:在使用<keep-alive>
时,被缓存的组件停用时调用。
以上是Vue.js组件的生命周期钩子函数,开发人员可以在这些钩子函数中执行相应的操作,实现相应阶段的逻辑和功能
-
-