只能在类中操作
|
Main类定义:public类名与文件同名
[public] class Main {
public static void main (String[]/String… args) {
变量类型 实际变量名称= new变量类型 (); 构造函数;
变量类型\[\] 数组名 = new 变量类型\[n\]; //定义大小为n的数组
……
}
}
- 方法签名(signature) :要完整地描述一个方法, 需要指出方法名(非参数名)以及参数类型
- 在方法的签名后面用关键字throw来声明该方法会抛出异常。(X)说法错误?
- 不定 长参数实际上可以理解为数组
- new表示新建并初始化一个对象
构造函数
- 构造方法(constructor)与类名同名。
- 构造方法的返回类型是类实例 (非void)
- 构造方法是不可以继承的。
- 如果类 本身没有写构造方法,系统会自动默认添加一个"不可见的"构造方法,其实就是 无参构造方法
但如果类本身创建了一个构造方法,那么系统将不会再为他创建无参构造方法。
- 在构造方法中,如果没有this及super,则编译器自动加上super()。
- 在构造方法在执行时,令所有父类的构造方法都得到调用。
- 在构造方法在执行时,一定会调用Object的构造方法。
抽象类
一旦某个类中包含了abstract方法,则这个类必须声明为abstract抽象类。
abstract class 父类{ //抽象类
//定义全局变量
final static double PI=3.14;
private static 类型area = 0.0;
//定义抽象方法
public abstract 返回类型func();
抽象类可以有构造方法
不能直接创建抽象类的实例对象
抽象类中可以有抽象方法,也可以没有抽象方法,也可以有非抽象方法。
而非抽象类中不可以有抽象方法。
…….
}
调用抽象类不需要实例化:抽象类名.func();
public class 子类 extends 父类{
public 子类名(){ //构造函数
super(); //在子类构造函数中调用父类的构造函数
……
}
子类中应该定义父类中声明的抽象方法
@override //重写父类同名的函数
……….
}
匿名类(在类中定义这个类)
- 匿名类不能定义构造方法,它使用父类的构造方法。
- 匿名类在定义时就创建了一个实例。
- 匿名类不取名字,直接用其父类或接口的名字。
- 匿名类经常用于做方法的参数。
- 用到界面的事件处理中可以使用匿名类。
- Eg.
class Polygon { //定义一个父类
public void display() {
System.out.println(“在 Polygon 类内部”);
}
}
class AnonymousDemo {
public void createClass() { // 创建的匿名类继承了 Polygon 类
Polygon p1 = new Polygon() { //定义匿名类
public void display() {
System.out.println(“在匿名类内部。”);
}
};
p1.display();
}
}
内部类
- 内部类方法可以访问外部类的属性,包括私有属性(将内部类定义成单独的外部类,则需要提供访问域的public方法)
public class OuterAndInnerClass{ //定义外部类
private int number;
public static void main(String[] args){
OuterAndInnerClass outer = new OuterAndInnerClass();
InnerClass inner = outer.new InnerClass(); //new前面冠以对象变量
inner.getOuterField();
}
class InnerClass{ //声明内部类
private String name;
public void getOuterField(){
System.out.println("inner class: " + number); //访问外部类的私有属性
}
}
}
- 内部类中访问外部类的字段可以用“外部类名.this.字段”的方式。
- 在用new创建内部类实例时,要在 new前面冠以对象变量。
多态
- 多态是指一个程序中相同的名字表示不同的含义的情况。
- override及虚方法调用是多态的一种。
- 虚方法
- java中没有明确说明什么是虚方法,虚方法是C++来的,java虚方法你可以理解为java里所有被overriding的方法都是virtual的
- 虚方法调用是由对象实例的类型来动态决定的。
- 虚方法调用可以理解为方法的动态绑定。
Static
- 静态初始化,在第一次使用这个类时要执行,第一个执行。
- static变量可以表示全局变量。
- static函数:可以直接通过类名来访问, 也可以通过类的实例来访问, 但建议使用类名来访问。
- 一个字段被final static两个修饰符所限定时,它可以表示常量。
- Java8中,在接口中可以有static的方法,也可以有方法体的方法。
- 静态成员方法调用静态成员变量
Final
- final所修饰的方法,是不能被子类所覆盖的方法。
- 在定义final局部变量时,也必须且只能赋值一次。
- 一个类被final修饰,那么它就作为最终类存在,最终类的特征是不能被继承只能被实例化。
- 匿名类及局部类可以访问final局部变量。
Enum类(枚举类默认继承了 java.lang.Enum``)
Enum 枚举类型是一种特殊的数据类型(常用的数据类型比如字符串、整型),这种数据类型的变量值限定在固定的范围, 比如季节只有春夏秋冬,月份是12个。
Eg.
public enum SeasonEnum {
SPRING( “”, “”), SUMMER( “”, “”), AUTUMN( “”, “”), WINTER( “”, “”)
}
private String name;
private String description;
可以set枚举对象的属性,但及不推荐

表示一个特别的类型,这个类型里只有有限个对象,因此如果我们在同一个包下创建一个名为 LeetCodeProblemLevel 的 Java 类是不被允许的;
枚举类反映了现实生活中一个事物只有 有限个 状态;
枚举类可以让编译器帮助我们执行 类型检查,避免传递错误的参数值;
每一个枚举类里面第一行用大写字母表示的就是枚举类里的有限个对象。
所以,一般来说,枚举类的对象的属性值都应该被 final 修饰,并在构造器中初始化。
枚举类自带方法:
ordinal() :返回枚举对象在枚举类中声明的次序,从 0 开始;
特点 1:枚举类的对象的比较使用 == 进行比较,而不必使用 equals()
特点 2:枚举类不允许使用 new 创建对象;
反射不能创建枚举类的对象
枚举类很适合用来实现设计模式中的单例模式

枚举类实现接口

EnumSet 是基于位向量实现的,它将每个枚举值映射到一个二进制位上。由于枚举类型的数量是固定的,因此 EnumSet 可以通过位运算来高效地实现集合操作。同时,由于枚举类型是不可变的,所以 EnumSet 的元素也是不可变的,这使得 EnumSet 可以保证线程安全。
EnumMap 是基于数组实现的。与普通的数组不同的是,EnumMap 中的每一个元素都是由枚举类型的元素作为下标索引的。这种设计可以使得 EnumMap 的查找和更新操作的时间复杂度均为 O(1)O(1),而且空间效率也比一般的 Map 实现更高。
注解

- 注解

- 特殊属性名:value
- 如果注解中只有一个value属性,使用注解时,value名称可以不写!!
- 注解本质是一个接口,Java中所有注解都是继承了Annotation接口的。
- @注解(…):其实就是一个实现类对象,实现了该注解以及Annotation接口。
- 注解的作用
- 对Java中类、方法、成员变量做标记,然后进行特殊处理。
例如:JUnit框架中,标记了注解@Test的方法就可以被当成测试方法执行,而没有标记的就不能当成测试方法执行
-
-
- 增强语义:上面说了,注解有一部分功能是写给人看的,向看代码的人说明这个方法不是当前类就有的,而是父类就有的,在我这里我重新定义了。有人说,注释是代码的一部分。因此注解更是代码的一部分,也起到了自解释的功能;
- 增强功能:在任何需要的地方加上一个 标记,增强原有的功能。我们在学习 Java 框架的时候会遇到大量的注解,使用这些注解大大简化了我们的开发;
- 简化配置:
-
-
元注解
- 注解注解的注解
- @Target约束自定义注解可以标记的范围。
- @Retention用来约束自定义注解的存活范围。
- @Documented 表示在使用 javadoc 生成文档的时候,被修饰的注解会被记录下来。
- @Inherited 表示被修饰的类的注解的子类也继承了该注解。SpringBoot 的启动类上使用的注解 @SpringBootApplication 就是被 @Inherited 修饰的;
- @Repeatable 表示重复注解,相同的注解可以重复修饰目标,这是一个语法糖,是 Java8 的新特性。用于注解的属性是数组的时候,此时配置可能会很长,可读性不好。
-
注解的解析
- 「注解」这个特殊的类使用关键字 @interface 修饰,注解的命名与 Java 类的命名规则相同,前面加上 @ 号。大家可以查看编译好的文件,注解文件编译以后是 .class 文件,因此我们说注解是一个特殊的类。
-
- 获取当前类标记的所有注解
Annotation [] getDeclaredAnnotations()
-
- T getDeclaredAnnotation(Class annotation Class)
- 判断class是否有指定注解:
boolean isAnnotationPresent(Class annotationClass)
-
- 自定义注解
public @interface LCAnnotation {}
-
- 在类上使用注解
@LCAnnotation(name = “中等”, info = “常见、常考”, level = LeetcodeLevelEnum.MEDIUM)
public class Question {}
泛型

- 泛型出现的意义是:这个类(或者接口)里面有一些信息是可以变化的,程序员指定它们是什么类型就可以是什么类型;泛型不是内部类,内部类是确定的类型,而泛型是不确定的类型;
- 一个类里面用到的类有可能是变化的,所以泛型的作用是让 类型需要成为变量,这叫「参数化类型」;
- 有了泛型以后,编译器会为我们检查编译错误,方便了编码,也增强了语义。因此泛型即是给人看的,也是给机器看的;
- 常见的泛型:List 、Set、Optional、Map<Integer, User> 、Pair<Integer, User>。
- 类:public class ArrayList****{}
- 类的成员变量可以是不同的类型
- 接口: public interface A{}
- 方法:public static void test**(T t)**{},送什么返回什么
- 泛型方法不是泛型类里的方法。
- 方法的参数可以接收不同的类型
- 泛型方法可以存在于非泛型类中
- 通配符:就是"?",可以在"使用泛型"的时候代表一切类型;
ETKV是在定义泛型的时候使用。
- 泛型的上下限:
- 泛型上限:?extends XX:?能接收的必须是XX或者其子类。
- 泛型下限:?super XX:?能接收的必须是XX或者其父类。
读取元素时,只能保证是 Object 类型
时间日期类
- 旧版
- API 不直观、不方便
• Date 的构造方法对传入的 year 值加上了 1900 ,在使用的时候就很不方便,例如我想表示 2023 年,就需要传入 2023 - 1900 ,才会和 Date 里面的 + 1900 的作用抵消;
-
- Date 与 Calendar 是可变类 Date 和 Calendar 都是可变类,因此它们在多线程环境下可能会出现线程安全问题。
- 不易于扩展
- 兼容性不好
- Java8
Switch语句
case后面只能跟常量。
switch(表达式) {
case 常量表达式1: 语句1;
case 常量表达式2: 语句2;
default: 语句;
// default不是必须的
}
定义:
Public/Private/Protected (变量类型[] 变量名称);
Public/Private/Protected 返回变量类型 函数名称(形参类型 形参)
访问权限修饰符
public(公共):任何类都可以进行访问(最不严格)。
protected(受保护):同一包内的类以及其子类可以进行访问。
private 实现封装(私有)只能被当前类本身访问(最严格)。
default (默认)(非private) :类中不加任何访问权限限定的成员属于缺省的访问状态。可以被这个类本身以及同一个包中的类包括其子类访问。
字段(field)是类(不是方法)的属性,是用变量来表示的
数组
- 声明数组时不可以直接指定数组的大小,定义时才可以。(区别声明和定义)
- 数组都是引用类型。
- 数组都有一个属性length。
- 增强的for语句可以方便地遍历数组。
接口
- 接口类型是引用类型。
- 一个类可以实现多个接口
- Java中的接口是通过implements关键字来实现的。
//A接口
interface A {
public int getA();
}
//B接口
interface B {
public int getB();
}
//实现了某个接口必须实现其全部的方法
public class ImpAB implements A, B {
public int getA() {
return 0;
}
public int getB() {
return 0;
}
}
继承:
- 继承可以实现代码重用,提高开发效率和可维护性。
- Java中的继承是通过extends关键字来实现的。
- 继承关系在UML图中,是用一个箭头来表示子类与父类的关系的。
- 子类对象可以被视为其父类的一个对象。
- 父类的非私有方法可以被子类自动继承。
- 子类可以Overload重载与父类同名的方法。
重载(overload) 是在一个类里面,方法名字相同,而参数不同。
返回类型可以相同也可以不同。
- 子类也可以Override重写与父类同名的方法,实现对父类方法的覆盖
重写(override)返回值和形参都不能改变。
- 在覆盖父类的方法的同时,使用super可以利用已定义好的父类的方法。
- 在构造方法中,使用super()时,必须放在第一句。
- 如果一个方法的形式参数定义的是父类对象,那么调用这个方法时,可以使用子类对象作为实际参数。
- this和super是指同一个的对象。
一个是指当前对象,一个是指当前对象的父类
异常处理
错误类型:IOException , Exception
当我们调用一个可能会异常的方法,即:
static void func() throws错误类型 // throws表示该方法可能会抛出异常
{
try{ //首先执行try
func(); //可能会抛出一个异常的方法
} catch(FileNotFoundException){ // 多重 catch 语句,有一个以上的catch语句
}catch(IOException){
// 在多重 catch 语句中,子类在上, 父类在下, 因为子类的异常信息比父类更详细, 如果把父类放前面就执行不到后边的了
}catch(Exception ex){ //这个方法出错后执行
ex.printStackTrace(); //错误输出
ex.getCause(); //可以得到异常的内部原因。
}finally { //这个方法无论是否出错都执行
…………
}
}
时:
- 用户自定义异常类继承自Exception类或某个子Exception类。
- Error是Throwable的子类。
函数:
Java编译器自动引入包java.lang.*。_java.lang_包是java语言的核心,它提供了java中的基础类。包括基本Object类、Class类、String类、基本类型的包装类、基本的数学类等等最基本的类。
- 强转函数:(强转类型)(…);
- 输出函数:System.out.println(string);
- 输入函数:引入包:import java.util.Scanner;
- 类中定义scanner:Scanner scan = new Scanner(System.in);
- 获取输入string类型:scan.next();
- 获取输入int类型:scan.nextInt();
- 获取输入bool类型:scan.nextBoolean();
- 获取输入的行string类型:scan.nextLine();
- 关闭scanner:scan.close();
- 数组:引入包:import java.util.Arrays;
- 数组输出(元素是类):
依次调用数组中每一个类的toString ()方法:Arrays.toString(数组名)
-
- 返回数组长度(元素个数): arrayData.length
-
集合:引入包:import java.util.*;
- 声明:private ArrayList<元素类型> 集合名;
- 定义:集合名= new ArrayList<元素类型>();
- 方法:
- 添加元素:集合名.add(new 元素类型(…));
- 遍历:for(元素类型 元素名: 集合名) {…}
同int i
- 键-值对Map:
- 声明定义:
private HashMap<String, String> kv = new HashMap<String, String>();
-
- 方法:
kv.get(key);
kv.put(key, value);
- 获取类:
- 类:实例类名.getClass()
- 父类:实例类名.getClass().getSuperclass()
- 字符串
- 判断两个字符串是否相同:Str1.equals(str2)
- 将字符串str根据str1分成数组:String[]=str.split(str1);
- 将字符串转化为整数 :int=Integer.parseInt(str); 用来解析一个整数
- 将字符串转化为实数 :double=Double.parseDouble(str); 用来解析一个实数
- 将字符串转化为byte数组:String.getBytes()
- 时间:引入包:import java.util.Date;
- 获取当前时间(自January 1, 1970, 00:00:00 GMT以来毫秒):long=Date().getTime()
- HTML文件(储存形式为byte数组):引入包:import java.io.*;
- 定义byte数组:byte[] data = new byte[1024];
- 读取URL链接中的数据:
- InputStream类型 input = url.openStream();
- 读取文件中的数据:
- InputStream input = new FileInputStream(“file”);
- input.read(data):将读取的数据保存在byte数组中,读完返回-1,无数据返回0
- 将读取的数据输出到文件中:
- OutputStream类型 output = new FileOutputStream(“file”))
- 将data中的数据写入file:output.write(data,0,length)
Eg.
try(InputStream input = url.openStream();OutputStream output = new FileOutputStream(“file”))
{
byte\[\] data = new byte\[1024\];
int length;
while((length=input.read(data))!=-1){
output.write(data,0,length);
}
}
- URL:引入包:import java.net.URL;
- 声明URL类型:new URL(“https://www.pku.edu.cn”);
知识点:
-
面向对象语言的特点包括:封装、继承、多态
-
Java的开发工具是JDK,JDK包括JRE及开发工具,JRE包括JVM及API
-
Java是一种面向对象、跨平台的语言
-
Java不直接使用指针
-
垃圾回收:
- Java的垃圾回收会自动检测不再使用的对象,并将其从内存中清除,以便释放内存空间。
- 系统在回收时会自动调用对象的finalize() 方法。
- Java中自动清除创建的对象。不用特定方法
-
编译程序的基本命令是javac
-
运行程序时java后面跟的是文件名或者类名
-
path表示前者是命令(javac及 java)的路径
-
Javap 查看类信息及反汇编
-
javap –c命令可以反汇编 代码、Java类中的指令。
-
可以使用javadoc来生成文档
-
文档注释是用/** */
-
@param表示对方法的说明 对方法中某参数的说明
-
add(xxxx) 可以将按钮等对象加入到Frame中
-
JAR
1. 定义:JAR文件是Java Archive File-java档案文件的简称,是与平台无关的文件格式,基于zip文件格式将许多文件合成一个压缩文件.jar,区别是比zip多了一个包含了一个 META-INF/MANIFEST.MF 文件,这个文件是在生成 JAR 文件的时候自动创建的
2. 可以使用jar来打包程序
3. jar文件中的清单信息文件中Main-Class表示主类
4. jar打包常用的参数是cvfm
5. 运行jar时,使用 java -jar 文件名.jar -
处理事件:
1. btn.addActionListener可以用来处理事件
2. 处理事件的真正函数是actionPerformed()函数 -
数据类型决定数据的存储方式和运算方式
-
java里的都是使用引用而不是指针所以一般是不会复制对象的实体而是和引用相关联。
-
java中的一个char占用2个字节。
-
java 中整型_常量默认为 int 型,每个int型占4个_字节
-
_‘\u0041’_使用的是__Unicode__字符编码,每个字符占两个字__节,共8个
-
3.14是double型。
因为3.14 在计算机中小数的表示基本上无法准确的描述出来,一般是只是一个近似值,所以“3.14f”才能表示成float型,而3.14只能表示成double型。
- Java中没有无符号数。
- Java 语言的布尔类型变量, 其赋值只能是true和false,
所以 非 零 即真并不存在于java中。
- 按惯例,类名首字母大写,变量名首字母小写。
- 字符串连接运算符其实是用append来实现的。
- 书写表达式要多用括号。
- java中除号是整除
- Java中不可以在任一表达式后面加分号构成表达式语句。
- 循环一般有五个要素,分别是循环变量、循环条件、循环体、控制语句和循环步进。
- break及continue后面可以跟一个语句标号。
- 使用setter()及getter()是一种好的编程习惯。
- 当方法调用结束,方法中的局部变量就结束其生命了。
- try-with-resource语句中自动加了close()方法的调用。
- 要使assert起作用,则在运行时,使用选项(-ea,或-enableassertions)。
- 单元测试是保证代码质量的一种手段。
- 程序中的错误通常可以分成三大类:语法错、运行错、逻辑错。
- 一般来说,编译器可以直接报出语法错误,无法报出逻辑错误,
- AtomicInteger 类表示原子变量。
- java.util.Timer类可以实现按周期重复执行一定任务。
- javax.swing.Timer类可以实现按周期重复执行一定任务。
- 流:
1. 流可分为字符流与字节流。
2. 流可分为节点流与处理流。
3. 一个流对象经过其他流的多次包装,称为流的链接。
4. ByteArrayOutputStream可以相当于内存流。 - DataOutputStream可以以二进制的方式写入double。
- 使用java.nio.file.Files的readAllLines()方法可以读入整个文本文件。
- JDK1.7对nio进行了较大的改进。
- 使用流一般都要考虑IOException。
- 在线程中更新图形化用户界面要注意使用invokeLater()方法。
- 正则表达式主要的应用包括:匹配验证、分割、查找、替换等。
- applet是Panel的子类。
- BorderLayout最多可以加五个子组件。
- 不同的组件可以加同一个事件监听对象。
- 将一个组件加上JScrollPane能实现自动滚动功能。
- JMenubar、JMenu、JMenuItem能实现菜单的功能。
- JList可以实现列表框功能。
- 按钮可以使用addActionListener来处理点击后要做的事情。
- JFrame:
JFrame中可以直接用add()方法来添加子组件。
JFrame的默认布局是borderlayout(非null)
Jframe中可以直接用add()方法来添加子组件
默认情况下,点击JFrame的关闭按钮并不会关闭它
-
- GridLayout布局:把容器划分成若干行乘若干列的网格区域,组件就位于这些划分出来的小格中。使用GridLayout 布局的容器最多可添加mxn个组件。GridLayout布局中每个网格都是相同大小并且强制组件与网格的大小相同。
- FlowLayout布局:从上到下、从左到右安排组件,当移动到下一行时是居中的
- JButton是AbstractButton 的子类(非Button)
- 有了Swing,仍然需要awt包
- 使用URL类可以获取网络信息。
- 获取网络信息后经常需要用正则表达式来处理。
- 在Eclipse中可以引用第三方的库。
- SQL中update语句表示修改记录。
- JDBC中Statement表示语句,即:向数据库发送的SQL语句
- 在数据库应用中要将用户输入的参数放入到SQL语句中。
- JTable需要数据模型来决定要显示的数据。
- ORMapping是数据库中应用中常见的技术。
- JDBC中RecordSet不表示结果集。
- SQL中add语句表示添加一列
- Graphics类的方法:
1. drawRect(int x,int y,int width,int height):画线框围起来的矩形。其中参数x和y指定左上角的位置,参数width和height是矩形的宽和高。
2. fillRect(int x,int y,int width,int height):是用预定的颜色填充一个矩形,得到一个着色的矩形块。 - 服务器端 接受客户呼叫需要调用ServerSocket类的accept()方法
- for-each语句不可以用于所有的enumerable对象
- Keylistener是Java中的键盘事件侦听器接口(非鼠标)
- Queue的主要方法包括 (stack 才是push,pop)
add(E e):添加一个元素到队尾
remove():获取队首的元素,并从队列中移除
- Serialize(序列化):将java对象以一连串的字节转换为字节码保存在磁盘文件中的过程,也可以说是保存java对象状态的过程。序列化可以将数据永久保存在磁盘上(通常保存在文件中)。(非内存)
deserialize(反序列化):将保存在磁盘文件中的java字节码重新转换成java对象称为反序列化
- Integeri=5; 实际表示 Interger i = Interger.paresInt(new Interger(5))
- Java中==和equals()的含义是不一样的
- java程序编译的结果(class文件) 中包含的不是实际机器的CPU指令
- & 叫做按位与,& 直接操作整数基本类型,而 && 不行。按位与运算符 “&” 是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位都为 1 时,结果位才为 1。参与运算的两个数均以补码出现。
例如, 0x31 & 0x0f 的结果为 0x01
&& 叫做短路与,&& 有短路效应,即:当第一个布尔运算为 false,第二个布尔运算不执行。而 & 运算符没有。
例如,对于 if (str != null && !str.equals (“”)) 表达式,当 str 为 null 时,后面的表达式不会执行,所以不会出现 NullPointerException
-
正则表达式的基本写法是:字符{数量}位置。
-
File对象的list()方法可以列出子目录。
-
在线程中更新图形化界面,要调用SwingUtilities.invokeLater(Runnable对象)
-
事件Adapter是一些接口,其中含有的方法是空的
-
hashmap不是线程安全的
-
使用PreparedStatement比Statement更安全一些
-
自定义注记使用关键词@interface。
-
为了保证重构后程序还是正确的,可以使用 Junit工具。
-
重构中最常用的是“更名"及“提炼函数"。
-
JDBC中用Class.forName() 来注册驱动程序。
-
JDBC中Connection表示连接。
-
SQL中update语句表示修改记录。
-
播放mp3文件可以使用JMF,还可以使用第三方库。
-
ImagelO类的read及write方法可以表示图像的读写。
-
HttpClient中获取网络信息可以使用 Request类的Get()方法。
-
JFileChooser能实现文件选择器的功能。
-
将一个组件加上JScrollpane能实现自动滚动功能。
-
事件getSource()方法得到的事件源对象。
-
Eclipse中可以进行可视化的窗体设计。
-
JComponent±A件都是Container.
-
RandomAccessFile,可以实现对文件的随机读写操作。
-
使用流一般都要考虑IOException。
-
序列化要求对象实现Serializable接口。
-
设定一个标记变量是常用的方法来决定是否结束线程。
-
sort方法中的比较器可以用Lambda表达式
-
List的主要实现包括LinkedList及ArrayList
-
Map的主要实现包括HashMap及TreeMap.
-
Map记录的是键一值对的集合。
-
List会记录元素的保存顺序。
-
Collection API中包括List、Set及Map.
-
字符串的运算实际表示StringBuffer、StringBuiler的append运算
-
String对象中所包装的内容是不可改变的(immutable)
-
Double对象中所包装的值是不可改变的(immutable)
-
getCause()可以得到异常的内部原因。
-
内部类中访问外部类的字段可以用“外部类名.this.字段"的方式。
-
构造函数初始化列表先于构造函数体中的语句执行。
-
虚方法调用是由对象实例的类型来动态决定的。
-
在接口中定义的方法具有public,abstract的特点(Java8以前)。
-
在接口中定义的常量具有public,static, final 的属性
-
final所修饰的变量,是只读量。
-
同一包中的各个类,默认情况下可互相访问。
-
子类对象实例可以被视为其父类的一个对象。
-
使用super访问父类的域和方法。
-
如果没有extends子句,则该类默认为 java.lang.Object的子类。
-
方法重载是多态(polymorphism)的一种方式。
-
如果没有定义任何构造方法,系统会自动产生一个构造方法。
-
数组元素都会隐式初始化。
-
A:65;a:97
-
找素数:2-该数的开平方数,能否被整除
-
封装:
1. 把数据和对数据的处理封装到同一个类中去
2. 隐藏:private
3. 暴露:public -
构造器:
1. 指定构造器创建对象
2. 初始化对象
3. 默认无参,自定义有参后无参消失 -
This:
1. This是变量
2. 哪个对象调用这个方法,this就拿到哪个对象,实例才有对象
3. 解决变量名称冲突问题 -
Static
1. 静态变量,属于类,只加载一份,可以被类和类的全部对象共享访问
2. 类.静态变量 直接访问
3. 静态方法中可以直接访问静态成员,不可以直接访问实例成员。
4. 实例方法中既可以直接访问静态成员,也可以直接访问实例成我员。
5. 实例方法中可以出现this关键字,静态方法中不可以出现this关键字的。 -
private:只能本类
1. 缺省 :本类、同一个包中的类
2. Protected:本类,同一个包中的类、子孙类中
3. public:任意位置 -
继承后子类访问成员的特点:
1. 单继承
2. 多层继承
3. 祖宗类:Object
4. 就近原则- 在子类方法中访问其他成员(成员变量、成员方法),是依照就近原则的。
- 先子类局部范围找,然后子类成员范围找,然后父类成员范围找,如果父类范围还没有找到则报错。
- 2、如果子父类中,出现了重名的成员,会优先使用子类的,如和果此时一定要在子类中使用父类的怎么办?
- 可以通过super关键字,指定访问父类的成员:
super.父类成员变量/父类成员方法
- 方法重写的其它注意事项
- 子类重写父类方法时,访问权限必须大于或者等于父类该方法的权限(public>protected>缺省)。
- 重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小。
- 私有方法、静态方法不能被重写,如果重写会报错的。
- 子类构造器的特点
- 子类的全部构造器,都会先调用父类的构造器,再执行自己。
- 子类构造器是如何实现调用父类构造器的:
- 默认情况下,子类全部构造器的第一行代码都是super()(写不写都有),它会调用父类的无参数构造器。
- 如果父类没有无参数构造器,则我们必须在子类构造器的第一行手写super(…),指定去调用父类的有参数构造器
-
- this(…)调用兄弟构造器
- this(…)、super(…)都只能放在构造器的第一行,因此,有了this(…)就不能写super(…)了,反之亦然。
- 多态的前提、注意事项
- 有继承/实现关系;存在父类引用子类对象;存在方法重写。
- 多态是对象、行为的多态,Java中的属性(成员变量)不谈多态。
- 解耦合;使用父类类型的变量作为方法的形参时,可以接收一切子类对象。
- 多态下不能直接调用子类的独有方法。
- 多态强转前,Java建议:
使用instanceof关键字,判断当前对象的真实类型,再进行强转
-
final关键字是最终的意思,可以修饰:类、方法、变量。
- 修饰类:该类被称为最终类,特点是不能被继承了。
- 修饰方法:该方法被称为最终方法,特点是不能被重写了。
- 修饰变量:该变量有且仅能被赋值一次。
-
- final修饰基本类型的变量,变量存储的数据不能被改变。
- final修饰引用类型的变量,变量存储的地址不能被改变,但地址用所指向对象的内容是可以被改变的。
-
使用了static final修饰的成员变量就被称为常量.
-
作用:常用于记录系统的配置信息。
-
public class Constant {
-
public static final String SCHOOL NAME
常量名的命名规范:建议使用大写英文单词,多个单词使用下划线连接起来
-
- 使用常量记录系统配置信息的优势、执行原理
- 代码可读性更好,可维护性也更好。
- 程序编译后,常量会被"宏替换":出现常量的地方全部会被替替换成其记住的字面量,这样可以保证使用常量和直接用字面量的性能是一样的。
-
单例怎么写?饿汉式单例的特点是什么?
- 把类的构造器私有;
- 定义一个静态变量存储类的一个对象;提供一个静态方法返回对象。
- 在获取类的对象时,对象已经创建好了。
-
单例有啥应用场景,有啥好处?
- 任务管理器对象、获取运行时对象。
- 在这些业务场景下,使用单例模式,可以避免浪费内存。
-
接口:
- 弥补了类单继承的不足,一个类同时可以实现多个接口,使类的角色更多,功能更强大。
- 让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现(更利于程序的解耦合)。
- JDK8开始,接口中新增了哪些方法?
- 默认方法:使用default修饰,使用实现类的对象调用。
- 静态方法:static修饰**,****必须用当前接口名**调用
- 私有方法:private修饰,jdk9开始才有的,只能在接口内部被调用。
- 他们都会默认被public修饰。
- JDK8开始,接口中为啥要新增这些方法?
- 增强了接口的能力,更便于项目的扩展和维护。
-
- 接口与接口可以多继承:一个接口可以同时继承多个挂口[重点]
- 一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承,也不支持多实现。
- 一个类继承了父类,又同时实现了接口,如果父类中和接口中有同名的默认方法,实现类会优先用父类的。
- 一个类实现了多个接口,如果多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。
-
代码块
- 代码块是类的5大成分之一(成员变量、构造器、方法、代码块、内部类)。
- 静态代码块:
- 格式:static{}
- 特点:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次。
- 作用:完成类的初始化,例如:对静态变量的初始化赋值,。
- 实例代码块:
- 格式:{}
- 特点:每次创建对象时,执行实例代码块,并在构造器前执行。
- 作用:和构造器一样,都是用来完成对象的初始化的,例如:对实例变量进行初始化赋值。
-
成员内部类
- 就是类中的一个普通成员,类似前面我们学过的普通的成员变量、成员方法。
- public class Outer {
创建对象的格式:
//成员内部类
public class Inner {
外部类名.内部类名对象名=new外部类(…).new内部类(…);
}
Outer.Inner in = new Outer().new Inner();
-
- 成员内部类中访问其他成员的特点:
- 成员内部类种可以直接访问外部类的实例成员、静态成员d.
- 成员内部类的实例方法中,可以直接拿到当前外部类对象,格式是:外部类名.this。
- 成员内部类中访问其他成员的特点:
- 静态内部类
- 有static修饰的内部类,属于外部类自己持有。
- public class Outer{
- 外部类名.内部类名对象名=new外部类.内部类(…);
- // 静态内部类
- public static class Inner{
- Outer.Inner in = new Outer.Inner();
- }
- 静态内部类中访问外部类成员的特点
- 可以直接访问外部类的静态成员,不可以直接访问外部类的实例成员
- 局部内部类(了解)
- 局部内部类是定义在在方法中、代码块中、构造器等执行体中,
- 匿名内部类
new 父类或接口(参数值…){
类体(一般是方法重写);
}
-
- 是一种特殊的局部内部类;
- 所谓匿名:指的是程序员不需要为这个类声明名字,默认有个隐藏的名字。
- 特点:匿名内部类本质就是一个子类,并会立即创建出一个子类对象
- 作用**😗*用于更方便的创建一个子类对象或接口实例。
Lambda表达式
- JDK8开始新增的一种语法形式,它表示函数。
- 可以用于替代某些匿名内部类对象,从而让程序更简洁,可读性更好。
(parameters) -> { // parameters 表示参数列表,不用写类型,参数可能有多个
// 函数体,可能有返回值,也可能没有返回值
}
注意:Lambda表达式只能替代函数式接口的匿名内部类!!!
- 什么是函数式接口?
- 有且仅有一个抽象方法的接口。
注意:将来我们见到的大部分函数式接口,上面都可能会有一个@Functionallnterface的注解,
该注解用于约束当前接口必须是函数式接口。
- Lambda表达式的省略规则
- 作用:用于进一步简化Lambda表达式的写法。
- 具体规则
- 参数类型全部可以省略不写。
- 如果只有一个参数,参数类型省略的同时"()“也可以省略,但多个参参数不能省略”()"
- 如果Lambda表达式中只有一行代码,大括号可以不写,同时要省略分号";"如果这行代码是return语句,也必须去掉return。
- 你是如何理解「函数式编程」的?
- • 函数式编程是一种编程范式,使用函数来定义程序行为,而不是操作对象的状态; • 它与面向对象编程(OOP)的主要区别在于,OOP 的重点在于操作对象的状态,而函数式编程的重点在于操作函数。因此,函数式编程更加简洁、可维护,能够更好地把握程序的整体逻辑,并且更容易实现代码的复用和重用。函数式编程也有助于提高代码的可读性和可维护性,从而使开发人员更容易理解和维护代码;
- • 函数式编程重点关注的是函数的调用,函数的定义,参数的传递,通过函数来完成程序的执行,从而实现编程。
- 什么是 Lambda 表达式?
- Lambda 表达式是一种函数式编程技术,其中参数和表达式可以在一行中表达,并且可以在函数体中传递。它可以让我们快速创建函数,并将它们传递给函数或对象,从而避免定义函数,定义函数名称等繁琐且重复的工作。
- 你如何看待 Lambda 表达式?
1. Lambda 表达式是 Java8 推出的新特性,在 Java 的框架中广泛地被使用。MyBatis-Plus 的查询操作中 QueryWrapper 和 LambdaQueryWrapper 就会用到 Lambda 表达式的语法。如果不熟悉 Lambda 表达式,使用框架,或者看框架的源码会有一定障碍。使用lambda表达式可以让程序员更加高效地进行函数式编程。它们可以节省时间,因为程序员不需要定义函数,定义函数名称等。此外,lambda表达式还可以更容易地实现函数编程技术,比如高阶函数,列表推导式,映射,过滤和 Reduce 等。虽然不使用 Lambda 表达式也能实现功能,但代码量大,代码不易阅读和维护。 - Lambda 表达式和匿名内部类有什么区别?
1. Lambda 表达式和匿名内部类都是 Java 中实现接口的方式。它们的区别在于:- • Lambda 表达式使用更简洁的语法,可以直接将代码块作为参数传递给函数式接口,而匿名内部类需要使用更冗长的语法来实现接口;
- • 生成的字节码:Lambda 表达式会生成更少的字节码,因此可以更快地执行,而匿名内部类会生成更多的字节码,因此可能会导致性能问题;
- • 访问外部变量:Lambda 表达式可以访问它所在方法的局部变量,但是这些变量需要声明为final或等效于final的变量。而匿名内部类可以访问所在方法的局部变量,但是这些变量必须声明为 final。
- Lambda 表达式更加简洁和易读,可以更好地支持函数式编程的思想,而匿名内部类则更加灵活,可以实现更复杂的逻辑。
方法引用
- 静态方法引用
- 类名**:😗*静态方法
- 使用场景
如果某个Lambda表达里只是调用一个静态方法,并且"→"前后参数的形式一致,就可以使用静态方法引用。
- 实例方法引用
- 对象名**:😗*实例方法。
- 使用场景
如果某个Lambda表达式里只是通过对象名称调用一个实例方法,并且"→"前后参数的形式一致,就可以使用实例方法引用。
- 特定类的方法引用
- 特定类的名称**:😗*方法
- 使用场景
如果某个Lambda表达里只是调用一个特定类型的实例方法,并且前面参数列表中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的入参的,则此时就可以使用特定类型的方法引用。
- 构造器引用
- 类名**::new**
- 使用场景
如果某个Lambda表达式里只是在创建对象,并且"一"前后参数情清况一致,就可以使用构造器引用
- 方法引用直接引用已经存在的方法或构造函数,因此在调用时不需要创建新的 lambda 对象,避免了创建对象和执行垃圾回收的开销。
Optional
- 创建 Optional 对象 以下是创建 Optional 对象的三种方式。
- • 创建包含非空值的 Optional 对象:
1. Optional optional1 = Optional.of(“LeetCode”);
2. 当我们使用 Optional.of() 方法时,如果传入的值为 null,则会抛出 NullPointerException 异常。 - • 创建空的 Optional 对象 Optional optional2 = Optional.empty();
- • 根据对象是否为空创建 Optional 对象
1. Optional optional3 = Optional.ofNullable(someString);
2. Optional.ofNullable() 方法则可以接受 null 值,并且可以根据传入的对象是否为 null 自动创建包含非空值或空值的 Optional 对象。 - 获取 Optional 对象中的值 • 获取 Optional 对象中的值
- String value = optional.get();
- 需要注意的是,如果 Optional 对象是空的,调用 get() 方法会抛出 NoSuchElementException 异常。
- 检查 Optional 对象中的值是否为空
- boolean isPresent = optional.isPresent();
- 如果 Optional 对象中的值不为空,则 isPresent() 方法返回 true;否则返回 false。注意:这个方法用得很少,因为使用它其实和我们以前使用 if (user != null) 在编程风格上差不多。推荐使用 ifPresent() 进行对象非空时候的操作。 optional.ifPresent(u -> System.out.println(u.getName()));
- 使用 map() 和 flatMap() 对 Optional 对象中的值进行转换 我们可以使用 map() 方法对 Optional 对象中的值进行转换: // 将字符串转换为大写
- Optional result = optional.map(s -> s.toUpperCase());
- 注意:map() 方法将返回一个新的 Optional 对象,其中包含经过转换的值。如果原始 Optional 对象为空,map() 方法会返回一个空的 Optional 对象。此时,可以使用 flatMap() 方法进行类似的转换。与 map() 方法不同的是,flatMap() 方法返回的也是一个 Optional 对象,而不是一个值,不同的是, flatMap() 方法会将两层 Optional 「拉平」。以下是 flatMap() 方法的示例: Optional optional = Optional.of(“LeetCode”);
- // 将字符串转换为大写并包装为 Optional 对象
- Optional result = optional.flatMap(s -> Optional.of(s.toUpperCase()));
- 在上面的示例中,flatMap() 方法使用 Lambda 表达式将字符串转换为大写,再使用 Optional.of() 方法将其包装为一个新的 Optional 对象(从括号里面向括号外面阅读代码)。然后返回一个包含转换值的新 Optional 对象。 使用 filter() 过滤 Optional 对象中的值 我们可以使用 filter() 方法过滤 Optional 对象中的值: Optional optional = Optional.of(“LeetCode”);
- // 过滤字符串,只保留以 L 开头的
- Optional result = optional.filter(s -> s.startsWith(“L”));
- 在上面的示例中,filter() 方法返回一个新的 Optional 对象,其中包含过滤后的值。如果原始 Optional 对象为空或不满足过滤条件,则返回一个空的 Optional 对象。 与其他类的结合使用 Java 的一些类和方法支持 Optional 类型的参数和返回值。例如,Stream 类的 findFirst() 方法会返回一个 Optional 对象: Stream stream = Stream.of(“LeetCode”, “Hello”, “World”);
- // 过滤以 L 开头的字符串,并返回第一个
- Optional result = stream.filter(s -> s.startsWith(“L”)).findFirst();
Stream流
- 创建
- 使用列表创建流:List对象.stream() 就返回了一个流。
- 使用数组创建流:Arrays.stream(arr对象)
- Stream.of()
- IntStream.range(1, 8) //生成 1、2、3、4、5、6、7
IntStream 比 Stream 占用更小的内存;
Stream 中的数据参与计算需要经历「拆箱」「装箱」的过程,因此应该直接使用 IntStream
-
中间操作
- distinct:去重,如果放进流里的是对象数据,需要实现 equals 方法
- limit :只取出流中的前几个元素
- skip :作用和 limit 恰好相反,跳过前面若干个元素,大家可以自行测试
- sorted :进行排序,所以需要告诉流排序规则。或者对象实现 Comparable 接口,或者传入接口 Comparator 的匿名类对象。
- map :对流中的每一个元素执行相同的函数
- filter :过滤,传入一个 Predicate 的实现类或者 Lambda 表达式,返回 true 的对象会被保留下来。
- concat :合并若干个流。下面的代码演示了合并 2 个流。
- flatMap: flat 的意思是「扁平化」「展开」,也就是把 Stream<Stream> 展开成 Stream ,因此传入 flatMap 的 Lambda 表达式的返回值得是一个流。 flatMap 的作用相当于把若干个 Stream 汇聚成一个 Stream。
-
终结操作
- allMatch、anyMatch、noneMatch :传入一个 Predicate 的实现类或者 Lambda 表达式,返回一个布尔类型的数值。从语义上比较容易猜出它们的意思: ◦ allMatch:所有数据的 Predicate 都返回 true,才返回 true; ◦ anyMatch:只要有一个数据的 Predicate 返回 true,就返回 true; ◦ noneMatch :所有数据的 Predicate 都返回 false,才返回 true;
- • findFirst:返回流中数据的第一个元素,可能流中没有数据,因此返回值是 Optinal 类型;
- • findAny:返回流中数据的任意一个元素,可能流中没有数据,因此返回值是 Optinal 类型
- • foreach:消费每一个元素,如果在并行流中,不保持原始流中数据的顺序。
- count:返回流中数据的个数;
- • max:传入一个 Comparator 的实现类或者 Lambda 表达式,定义比较规则,返回在这个比较规则下最大的元素;
- • min:传入一个 Comparator 的实现类或者 Lambda 表达式,定义比较规则,返回在这个比较规则下最小的元素;
- • reduce:reduce 是 max、min 更一般化的版本,得到流中的所有元素依次计算的结果
-
并行流int sum2 = numbers.parallelStream().mapToInt(i -> i).sum();
-
Java 的 Stream 和传统的集合处理方式有什么不同
- • Stream API 是 Java 8 中引入的函数式编程风格的一部分。这意味着我们可以使用 Lambda 表达式和函数引用来对集合进行处理,而不是通过传统的循环和条件语句,不需要手动遍历集合元素;
- • Stream API 操作的结果是不可变的,我们可以在操作之间共享 Stream,有助于减少代码中的错误和不必要的副作用;
- • Stream API 提供了一些方法来支持并行处理数据,这可以使我们更快地处理大量数据。通过简单地使用 parallel() 方法,我们可以将 Stream 转换为具有并行能力 Stream ,然后对其进行操作。
-
Java 的 Stream 的「延迟执行」特性是什么?它有什么优点?
- Java Stream 的延迟执行特性是指:Stream 中的操作并不会立即执行,而是 **等到需要输出结果时才执行。**具体来说:Java Stream 中的操作可以分为两种:中间操作和终止操作。 • 中间操作只是对 Stream 进行转换、过滤等操作; • 终止操作会触发中间操作的执行,并将结果输出。如果创建流以后不执行终止操作,那么中间操作也不会被执行。
- Java Stream 的延迟执行特性有以下优点:
1. • 提高性能:由于中间操作不会立即执行,而是等到需要输出结果时才执行,这意味着 Stream 中的操作可以被优化、合并和延迟执行,从而减少了不必要的计算,提高了性能;
2. • 节省内存:Java Stream 的延迟执行特性可以避免创建大量的临时集合,这可以节省内存,尤其是在处理大量数据时;
3. • 支持并行处理:Java Stream 的延迟执行特性可以支持并行处理,因为多个操作可以被同时执行。这可以提高处理大量数据的效率。 参考资料
-
String
- 只要是以"…"方式写出的字符串对象,会存储到字符串常量池,且相同内容的字符串只存储一份;
- 通过new方式创建字符串对象,每new一次都会产生一个新的对象放在堆内存中。
- 字符串对象的内容比较,千万不要用==,==默认比较地址,字符串对象的内容一样时地址不一定一样
-
异常的基本处理
抛出异常(throws)
在方法上使用throws关键字,可以将方法
直接捕获程序出现的异常。
内部出现的异常抛出去给调用者处理。
-
- 捕获异常(try…catch)
try{
//监视可能出现异常的代码!
}catch(异常类型1变量){
//处理异常
方法throws异常1,异常2,异常3…{
}catch(异常类型2变量){
//处理异常
}…
-
- 自定义异常(运行:better)
-
自定义运行时异常
-
定义一个异常类继承RuntimeException.
-
重写构造器。
-
通过thrownew异常类(xxx)来创建异常对象并抛出
-
特点:编译阶段不报错,运行时才可能出现!提醒不属于激进型。
-
自定义编译时异常
-
定义一个异常类继承Exception.
-
重写构造器。
-
通过thrownew异常类(xxx)创建异常对象并抛出。
-
特点:编译阶段就报错,提醒比较激进
-
- 在开发中异常的常见处理方式是:底层的异常抛出去给最外层**,**最外层集中捕获处理。
- 自定义异常(运行:better)
- Collection集合(单列集合)
- List系列集合:添加的元素是有序、可重复、有索引。
- ArrayList、LinekdList:有序、可重复、有索引。
- Collection集合获取送代器的方法
- Iterator iterator()返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素
- Iterator迭代器中的常用方法
- boolean hasNext()
- List系列集合:添加的元素是有序、可重复、有索引。
询问当前位置是否有元素存在,存在返回true,不存在返回false
-
-
-
- E next()
-
-
获取当前位置的元素,并同时将迭代器对象指向下一个元素处。
-
- 增强for循环:for(元素的数据类型变量名:数组或者集合){}
- 解决并发修改异常问题的方案
- 如果集合支持索引,可以使用for循环遍历,每删除数据后做i–;或者可以倒着遍历
- 可以使用迭代器遍历,并用迭代器提供的删除方法删除数据。
Iterator it = list4.iterator();
while(it.hasNext()){
String name = it.next
if(name.contains(“枸杞”)){
list4.remove(name);
it.remove
}
}
System.out.println(list4);
-
-
- 注意:增强for循环/Lambda遍历均不能解决并发修改异常问题,因此增它们只适合做数据的遍历,不适合同时做增删操作。
- ArravList底层是基于数组存储数据的。
- LinkedList底层是基于(双向)链表存储数据的。
- 设计队列,栈
-
- Set系列集合:添加的元素是无序、不重复、无索引。
-
- HashSet:无序、不重复、无索引;
- JDK8之前,哈希表**=数组+**链表
- JDK8开始,哈希表**=数组+链表+**红黑树
- 哈希表是一种增删改查数据,性能都较好的数据结构
- HashSet集合元素的去重操作:重写两个方法:hashCode()和equals(),自动生成即可
- LinkedHashSet:有序、不重复、无索引。
- ·依然是基于哈希表(数组、链表、红黑树)实现的。
- 但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置
- TreeSet:按照大小默认升序排序、不重复、无索引。
- HashSet:无序、不重复、无索引;
-
- 如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据?
- 用ArrayList集合(有序、可重复、有索引),底层基于数组的。(常用)
- 如果希望记住元素的添加顺序,且增删首尾数据的情况较多?
- 用LinkedList集合(有序、可重复、有索引),底层基于 实现的。
- 如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快?
- 用HashSet集合(无序,不重复,无索引),底层基于哈希表实现的。(常用)
- 如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快?
- 用LinkedHashSet集合(有序,不重复,无索引),底层基于哈希表和双链表。
- 如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快?
- 用TreeSet集合,基于红黑树实现。
- MAP
- 获取所有键的集合 public Set keySet()
根据键获取其对应的值 public V get(Object key)
-
- 键值对:Set< Map.Entry<String, Double> >
- default void forEach(BiConsumer<? super K, ? super V> action)
结合lambda遍历Map集合
- stream
- 方法中可变参数 object… x
- Collectionns
- 给集合批量添加元素
public static boolean addAll(Collection<? super T> C, T… elements)
-
- 打乱List集合中的元素顺序
public static void shuffle(List<?> list)
-
- 对List集合中的元素进行升序排序
public static void sort(List ļist)
-
- 对List集合中元素,按照比较器对象指定的规则进行排序
public static void sort(List list, Comparator<? super T> c)
- 创建File类的对象
- 根据文件路径创建文件对象
public File(String pathname)
-
- 根据父路径和子路径名字创建文件对象
public File(String parent, String child)
-
- 根据父路径对应文件对象和子路径名字创建文件对象
public File(File parent, String child)
- File类创建文件的功能
- 创建个新的空的文件
public boolean createNewFile
-
- 只能创建一级文件夹
public boolean mkdir()
-
- 可以创建多级文件夹
public boolean mkdirs()
- File类删除文件的功能
- public boolean delete 删除文件、空文件夹
- File类提供的遍历文件夹的功能
- public String[] list()
- 获取当前目录下所有的"一级文件名称"到一个字符串数组中去返道。
- 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去选回(重点)
- public File[] listFiles()
使用listFiles方法时的注意事项:
-
- 当主调是文件,或者路径不存在时,返回null
- 当主调是空文件夹时,返回一个长度为0的数组
- 当主调是一个有内容的文件夹时,将里面所有一级文件和文件夹的路径放在File数组中返回
- 当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和口文件夹的路径放在File数组中返回,包含隐藏文件
- 当主调是一个文件夹,但是没有权限访问该文件夹时,返回null
- 字符集

- 对字符的编码
- 使用平台的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中
byte[] getBvtes()
-
- 使用指定的字符集将该String编码为一系列字节,将结果存储到新的字节数组中
byte[] getBytes (String charsetName)
- 对字符的解码
- 通过使用平台的默认字符集解码指定的字节数组来构造新的String
String(byte[] bytes)
-
- 通过指定的字符集解码指定的字节数组来构造新的String
String(byte[] bytes, String charsetName
旧版IO
- 缓冲读取(控制台):引入包:import java.io.*;
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
从system控制台中获取
-
- 获取输入字符的ascll码:int=in.read() ;
- 获取一个字符串:String=in.readLine() ;
- 缓冲写入(文件):引入包:import java.io.*;
- File f =new File(“路径”);
- FileWriter fw =new FileWriter(f);
BufferedWriter bw=new BufferedWriter(fw)
通过自定义的writer写入到指定文件中
-
- 在缓冲区写入内容:bw.write(“内容”);
- 在缓冲区换行:bw.newline();
- 将缓冲区的内容写入到文件中:bw.flush();
- 关闭缓冲写入bw.close();
-
读取文件:引入包:import java.io.*;
- File f =new File(“路径”);
- FileReader fr=new FileReader(f);
- BufferedReader br=new BufferedReader(fr);
- 读取文件中一行内容:String=br.readLine()为空则返回null
- 关闭缓冲读取br.close();
-
IO流
- 字节流byte[]
读取和写入二进制数据,如图像、声音、视频等
-
-
- FileInputStream 和 FileOutputStream:用于读取和写入文件数据的流;
- • ByteArrayInputStream 和 ByteArrayOutputStream:用于读取和写入内存中的字节数据的流;
- • PipedInputStream 和 PipedOutputStream:用于连接两个线程之间的管道的流;
- • FilterInputStream 和 FilterOutputStream:用于对其他输入输出流进行包装的装饰器流。
- 释放资源:自动释放
-

-
- 字符流char[]
- FileReader 和 FileWriter:用于读取和写入文件数据的流;
- CharArrayReader 和 CharArrayWriter:用于读取和写入内存中的字符数据的流;
- InputStreamReader 和 OutputStreamWriter:用于将字节流转换为字符流的桥梁;
- StringReader 和 StringWriter:用于读取和写入字符串数据的流。
- 字符流char[]
-
缓冲字节流
- 作用:可以提高字节输入流读取数据的性能
- 原理:缓冲字节输入流自带了8KB缓冲池;缓冲字节输出流t也自带了8KB缓冲池。
- 把低级的字节输入流包装成一个高级的缓冲字节输入流,从而提高读数据的性能
public BufferedinputStream(InputStream is)
-
- 把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能
public BufferedoutputStream(OutputStream os)
- 缓冲字符流
- 字符缓冲流有几种,作用是什么?
- 字符缓冲流自带8K缓冲区
- 可以提高原始字符流读写数据的性能
- 两种字符缓冲流如何使用?各自新增了什么功能?
- 字符缓冲流有几种,作用是什么?
public BufferedReader(Reader r)
-
-
- 性能提升了,多了readLine()按照行读取的功能
-
public BufferedWriter(Writer w)
-
-
- 性能提升了,多了newLine()换行的功能
-
- 缓冲流的作用主要有以下几点:
- • 提高读写效率:缓冲流通过内部缓冲区来减少对底层输入输出流的访问次数,从而提高 IO 操作的效率;
- • 提供额外的功能:缓冲流可以提供一些额外的功能,如自动刷新、按行读取等。
- 字符输入转换流
- 把原始的字节输入流,按照代码默认编码转成字符输入流(与直接用FileReader的效果一样)
public InputStreamReader(InputStream is)
-
- 把原始的字节输入流,按照指定字符集编码转成字符输入流(重点)
public InputStreamReader(InputStream is , Stringcharset)
- 对象流类
- ObjectOutputStream将 Java 对象序列化为字节流,
- ObjectInputStream将字节流反序列化为 Java 对象。
- 对象流的作用主要有以下几点:
- •简化了对象序列化和反序列化的操作;
- • 用于网络传输和持久化:对象流可以用于网络传输和持久化,方便地将 Java 对象保存到文件或数据库中,或通过网络传输 Java 对象;
- • 提高效率:对象流通过二进制序列化方式进行数据传输,相比于文本方式可以提高传输效率。
- PrintStream/PrintWriter (打印流)
- 作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去。
- PrintStream提供的打印数据的方案
- 打印流直接通向字节输出流/文件/文件路径
public PrintStream(OutputStream/File/String)
-
-
- 可以指定写出去的字符编码
-
public PrintStream(String fileName, Charset charset9
-
-
- 可以指定实现自动刷新
-
public PrintStream(OutputStream out, booleanautoflush)
-
-
- 可以指定实现自动刷新,并可指定字符的编码
-
public PrintStream(OutputStream out, boolean autoFlush, String encoding)
-
-
- 打印任意类型的数据出去
-
public void println(Xxx xx)
-
-
- 可以支持写字节数据出去
-
public void write(int/byte[]/byte[]一部分)
- 数据输出流(输入同 )
- 允许把数据和其类型一并写出去。
- 创建新数据输出流包装基础的字节输出流
public DataOutputStream(OutputStream out)
-
- 将byte类型的数据写入基础的字节输出流
public final void writeByte(int v) throws IOException
-
- 将int类型的数据写入基础的字节输出流
public final void writeInt(int v) throws IOException
-
- 将double类型的数据写入基础的字节输出流
public final void writeDouble(Double v) throws IOEXCeption
-
- 将字符串数据以UTF-8编码成字节写入基础的字节输出流
public final void writeUTF(String str) throws IOEXCEDtion
-
- 支持写字节数据出去
public void write(int/byte[]/byte[]一部分)

NIO
- NIO
- 更加灵活、高效。NIO 提供了一种新的 I/O 操作方式,它基于通道(Channel)和缓冲区(Buffer)的概念,使用非阻塞的方式处理 I/O 操作,能够更好地适应现代的高并发环境。
大数据量的文件操作、网络编程等
-
- Java NIO 的优点
- 高效的 IO 操作 Java NIO 可以使用选择器(Selector)来监视多个通道(Channel),在通道准备就绪时自动通知应用程序,从而避免了阻塞等待 IO 操作完成的情况。这样可以使得 IO 操作更加高效,特别是在同时处理多个连接或数据流时。
- 支持多种 IO 操作类型 Java NIO 支持多种 IO 操作类型,如文件 IO、网络 IO、管道 IO 等。这些操作类型都可以使用相同的 API 进行操作,使得编程更加简单和一致。
- 更好的内存管理 Java NIO 使用缓冲区(Buffer)来缓存读取或写入的数据,缓冲区支持批量读取或写入,这样可以避免频繁的内存分配和释放操作,提高了内存的利用率和程序的性能。
- 可靠的错误处理 Java NIO API 提供了可靠的错误处理机制,可以更好地处理 IO 操作中出现的异常和错误情况。主要有以下几个方面的原因:
1. • 异常处理:NIO API 的各个方法都有明确的异常处理机制,当出现异常时,API 会抛出特定的异常类,而不是简单地返回错误代码。这使得开发人员可以更加精确地定位和处理问题;
2. • 缓冲区管理:在 NIO 中,读写操作都是通过缓冲区来完成的。缓冲区本身就是一个错误处理机制,因为缓冲区在读写操作中可以确保数据的有效性和完整性。当读取到的数据不完整或不符合要求时,缓冲区会自动记录错误,而不是将错误的数据传递给下一层处理;
3. • 选择器(Selector)机制:NIO提供了选择器机制,可以同时监控多个通道的事件,例如读取、写入和连接事件等。选择器可以有效地减少CPU的资源占用和线程的创建,从而提高程序的性能和可靠性;
4. • 异步 I/O 操作:NIO 还支持异步 I/O 操作,异步 I/O 操作可以让程序在进行 I/O 操作时不必一直阻塞等待 I/O 操作的完成,从而提高程序的响应性和可靠性。
- 以下是 Java NIO API 的基本组成部分:
- • 缓冲区(Buffer):它是数据的临时存储区域,用于在通道(Channel)和 IO 操作之间传递数据。缓冲区支持不同的数据类型,如字节(Byte)、字符(Char)、整数(Int)等。缓冲区的主要作用是提供了非阻塞 IO 操作的数据缓存区,使得 IO 操作更加高效。
- • 通道(Channel):它是一种与底层 IO 设备交互的对象。通道支持非阻塞 IO 操作,可以使用不同的通道类型,如文件(FileChannel)、网络(SocketChannel、ServerSocketChannel、DatagramChannel)等。
- • 选择器(Selector):它是一种用于监视多个通道的对象,可以将一个或多个通道注册到选择器中,并在通道准备就绪时自动通知应用程序。选择器使得应用程序可以在一个线程中处理多个非阻塞 IO 操作。
- 下面是使用 Java NIO API 进行非阻塞 IO 操作的基本步骤:
-
- 创建一个通道(Channel):使用通道的工厂方法创建一个通道对象,如 FileChannel.open()、SocketChannel.open() 等。
- 2. 将通道注册到选择器(Selector)中:使用通道的 register() 方法将通道注册到选择器中,并指定感兴趣的 IO 操作类型(如读、写等)。
- 3. 创建一个缓冲区(Buffer):使用缓冲区的工厂方法创建一个缓冲区对象。
-
- 从通道中读取数据或向通道中写入数据:使用通道的 read() 或 write() 方法读取或写入数据,同时将数据存储到缓冲区中。
-
- 处理缓冲区中的数据:使用缓冲区的 get() 方法获取缓冲区中的数据,并进行处理。
-
- 清空缓冲区或将缓冲区中的数据复位:使用缓冲区的 clear() 或 flip() 方法清空缓冲区或将缓冲区中的数据复位。
- 7. 重复执行步骤 4 - 6,直到读取或写入操作完成。
-
- Java NIO 的优点
- NIO2引入了 Path 和 Files 类。
- Path 类用于代表文件路径
- Files 类提供了一组用于文件操作的静态方法,例如:创建、复制、移动、删除、读写等等;
- 引入了 DirectoryStream 接口,用于表示目录流,可以用于枚举目录中的所有文件;
多线程:
public class 类名extends Thread //继承Thread线程类
{
@Override //重写Thread中run方法
public void run() {
………
}
}
Main中多线程调用该类:
for(int i=0; i<length; i++){
实例类名.start()
}
-
线程体的本质是run()方法。
-
多个线程可以同时执行。
-
设定线程优先级用setPriority()方法。
-
设定一个标记变量是常用的方法来决定是否结束线程。
-
后台线程(deamon)是会自动结束的。1
-
垃圾回收线程是优先级很低的线程。
-
ArrayList类不是线程安全的类。
-
多线程(Thread)
- 创建方式:继承Thread类
- 定义一个子类MyThread继承线程类java.lang.Thread,**重写****run()**方法
- 创建MyThread类的对象
- 调用线程对象的start()方法启动线程(启动后还是执行run方法的)
- 优点:编码简单
- 缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。
- 启动线程必须是调用****start方法,不是调用run方法。
- 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
- 只有调用start方法才是启动一个新的线程执行。
- 不要把主线程任务放在启动子线程之前。
- 这样主线程一直是先跑完的,相当于是一个单线程的效果了。
-
多线程(Runnable)
- 创建方式:实现Runnable接口
- 定义一个线程任务类MyRunnable实现Runnable接口**,重写run0**方法
- 创建MyRunnable任务对象
- 把MyRunnable任务对象交给Thread处理。
- Thread类提供的构造器
- public Thread(Runnable target)封装Runnable对象成为线程对象
- 调用线程对象的start()方法启动线程
- 优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
- 缺点:需要多一个Runnable对象,如果有执行结果是不能直接返回的
-
多线程(Callable)
- 创建任务对象:定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据
- 把Callable类型的对象封装成FutureTask(线程任务对象)。
- 把线程任务对象交给Thread对象。
- 调用Thread对象的start方法启动线程。
- 线程执行完毕后、通过FutureTask对象的get方法去获取线程任务执行的结果。
-
线程安全
- 反复执行 Solution 的 main 函数会看到不同的结果,这是因为多线程的代码执行 先后顺序 具有一定的随机性,哪一个线程 先 抢到 CUP 内核的执行权这是我们编码的人不能决定的;**由此产生的数据和我们人为需要的结果不一致的问题,**就叫做「线程安全问题」;
- • 虽然多线程的代码执行 先后顺序 具有一定的随机性,但是我们可以通过多种机制(例如:锁、并发工具包)保证我们的代码执行能够获得我们预期的结果。
- 多个线程,同时访问同一个共享资源,且存在修改该资源。
- 悲观锁
- 在每次访问共享资源时,都会先对其进行加锁,防止其他线程同时访问或修改该资源。保证同一时间只有一个线程访问共享资源。为了提高程序的并发性能,通常会使用乐观锁等其他机制来替代 synchronized
-
-
- 线程同步
synchronized- 让多个线程先后依次访问共享资源,这样就可以避免出现线程安全问题。
- 加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
- 代码块:
- 线程同步
-

-
-
-
- 建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象
- 对于静态方法建议使用字节码(类名**.class**)对象作为锁对象
- 同步方法(更好)
-
-

-
-
-
- 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
- ReentrantLocklock() unlock()
-
-

-
-
-
- 锁 lock.lock() 必须紧跟 try 代码块,且 unlock() 要放到 finally 第一行,这样能够保障进入了锁的线程在出现异常的时候,能够正确释放锁。

- 锁对象建议加上什么修饰?
-
-
建议使用final修饰,防止被别人篡改
-
-
- 线程间通信
-
使用 Condition,可以对多线程间的阻塞和唤醒进行更细粒度的控制。
-
-
-
- await() 当前线程进入等待状态,直到被唤醒
- signal() 唤醒一个在等待状态的线程
- signalAll() 唤醒所有在等待状态的线程。
-
- 乐观锁
- 乐观锁认为并发访问共享数据的冲突是比较少的。因此在进行访问时,不需要加锁,而是在更新数据时,先检查数据是否被其他线程修改过:
- • 如果没有被其他线程修改过,则更新成功
- • 如果被其他线程修改过,重新尝试更新。
- 乐观锁认为并发访问共享数据的冲突是比较少的。因此在进行访问时,不需要加锁,而是在更新数据时,先检查数据是否被其他线程修改过:
-
-
锁的粒度:要尽量减小锁的粒度,避免长时间持有锁,以提高并发性能。
-
避免嵌套锁:嵌套锁容易导致死锁,应尽量避免。
-
悲观锁的缺点?
- • 性能开销:由于每次访问共享资源之前都需要获取锁,因此会增加系统的开销。在高并发场景下,频繁地获取和释放锁可能会成为系统的瓶颈,导致性能下降;
- • 并发度降低:由于悲观锁的特性,即每次访问前都需要获取锁,因此并发度可能会受到限制,从而降低系统的性能。
- • 死锁问题:如果多个线程同时持有锁并且互相等待对方释放锁,则可能会发生死锁问题,导致程序停滞不前;
- • 阻塞问题:当一个线程持有锁时,其他想要访问相同资源的线程会被阻塞,等待锁的释放。如果持锁时间过长,可能会导致等待线程的响应时间变长;
-
Join()方法
- 在 thread.join(); 开始以后,调用 thread 的线程**(主线程)就开始等待了,等待** thread 执行完毕以后,主线程才继续执行后面的逻辑;
- • 还可以在 join(Duration duration) 方法里面传入 java.time.Duration 类的对象,表示秒或纳秒时间间隔。
-
守护线程
- • 为其它线程提供服务的线程可以设置成「守护线程」。「守护线程」可以理解成「守护别人的线程」,它的特点是:当别人都不存在的时候,自己就不再执行;
- • 非守护线程是「用户线程」,我们创建新线程的时候,默认就是「用户线程」,守护线程需要单独设置;
- • 在线程启动之前设置线程为守护线程;
- • 守护线程创建出来的线程还是守护线程;
-
为什么要使用多线程?
- • 生活中很多事情就是「看起来」在同时进行的,提高效率,合理利用系统的资源;
- • 更好地利用多核 CPU,更多的 CPU 就可以帮我们处理更多任务。
-
使用多线程可能带来什么问题?
- • 线程安全问题,由于 CPU 执行线程具有随机性。如果我们不太熟悉多线程的知识,不能利用好编程语言提供的工具类,很可能会得到错误的结果;
- • 可能产生竞态条件:竞态条件指的是多个线程在访问和修改共享的变量或资源时,由于执行顺序的不确定性而导致程序结果的不确定性。具体来说,如果多个线程同时读取和修改一个共享变量,那么就可能出现竞态条件。
- • 可能产生死锁问题:死锁(Deadlock)是指两个或多个线程在等待另一个线程持有的资源时被阻塞的状态,从而导致它们无法继续执行。为避免死锁,在编程的时候应该避免持有多个锁,以相同的顺序获取锁,或者使用超时机制来避免长时间等待等。
-
并发与并行的区别是什么?
- 并发:是指在 同一时间段内 交替执行 多个线程,交替是随机的。在并发编程中,线程之间共享同一个 CPU 的时间片,每个线程都可以在同一时间内执行一部分代码;
- 并发的含义
- 进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,
- CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快给我们的感觉这些线程在同时执行,这就是并发。
- 并行的理解
- 在同一个时刻上,同时有多个线程在被CPU调度执行。
- 并发和并行同时进行的
- 并发:CPU分时轮询的执行线程。
- 并行:同一个时刻同时在执行。
- 并行:是指 同时 执行多个线程。这些线程在不同的 CPU 核心或处理器上同时执行任务,从而实现同时处理多个任务。
-
同步和异步的区别?
- • 同步 :后面的任务必须等待前面的任务完成以后才能执行。
- • 异步 :前面的执行任务还没有返回,后面的任务就可以执行了。
-
创建线程的两个方法「实现 Runnable」 和「继承 Thread」用哪个好?
- 如果想要更灵活和可重用的并发代码,建议实现 Runnable接口。但是,如果只需要编写简单的并发代码,可以考虑继承 Thread 类;
- 通常来说实现 Runnable接口更好一点:实现 Runnable接口这种方式创建并发代码更加灵活。这是因为在 Java 中,如果选择继承 Thread 类,就无法继承任何其它类。Java 没有多继承,但是可以实现多个接口。
- 实现 Runnable 接口可以更好地实现代码的重用性。如果有多个任务需要在不同的线程中运行,我们可以创建多个 Runnable 实例,并将它们传递给多个Thread对象的构造方法。但是如果继承 Thread 类,就需要为每个任务创建一个新的Thread子类。
-
线程池
- 创建线程池
- JDK5.0起提供了代表线程池的接口:ExecutorService。
- 如何创建线程池对象?
- 使用ExecutorService的实现类ThreadPoolExecuto自创建一个线程池对象。

-
-
- 使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。
- 线程池处理Runnable任务
- 使用ExecutorService的方法:
- void execute(Runnable target)
-

-
- 线程池处理Callable任务,并得到任务执行完后返回的结果
-
- 使用ExecutorService的方法:
- Future submit(Callable command)
-
- Executors
- 线程池处理Callable任务,并得到任务执行完后返回的结果

-
-
- Executors工具类底层是基于什么方式实现的线程池对象?
-
线程池Executorservice的实现类:ThreadPoolExecutor
-
-
- Executors不合适做大型互联网场景的线程池方案
-
建议使用ThreadPoolExecutor来指定线程池参数,这样可以明确线程池的运行规则,规避资源耗尽的风险。
JUC (Java Util Concurrent)
- 提供了许多高效且易于使用的并发编程工具,可以帮助开发者更加方便、高效地开发并发程序,提高系统的性能和可靠性。
- 并发容器
- Java 的并发容器解决了多线程环境下的线程安全性问题和数据一致性问题。在多线程环境下,多个线程同时访问和操作共享数据时,如果不进行合适的同步和互斥控制,就容易出现数据竞争、死锁、数据不一致等问题。
- Java 的并发容器充分利用了多核 CPU 的优势,提高了多线程编程的性能和效率,也提供了一些方便的方法和接口,可以使得多线程编程更加灵活和易于使用。
- ConcurrentHashMap
- ConcurrentHashMap 是一个线程安全的哈希表实现,适用于多线程环境下的并发访问。与 Hashtable 不同,ConcurrentHashMap 不是通过全局锁来实现同步的,而是通过分段锁的机制来保证数据的一致性和性能;
- ConcurrentHashMap 可以用于实时统计,例如统计网站的访问量或者用户活跃度等,多个线程可以同时更新 ConcurrentHashMap 中的数据,而不需要加锁。
- 并发容器的局限性
- • 内存消耗大:并发容器通常需要占用更多的内存空间来保证线程安全。比如,ConcurrentHashMap 会占用更多的内存空间来存储桶的锁对象,CopyOnWriteArrayList 则需要在写操作时复制整个数组,消耗更多的内存;
- • 遍历性能低:由于并发容器的内部数据结构通常是多个节点或桶组成的链表或数组,因此在进行遍历操作时会比普通容器慢。特别是在 ConcurrentHashMap 中,由于需要在多个桶中查找数据,因此在遍历时的性能比较低;
- • 更新操作效率低下:并发容器在设计时为了保证线程安全,通常采用了加锁、CAS 等机制,这些机制会导致更新操作的性能下降。因此,对于频繁进行更新操作的场景,如需要高效的插入、删除、更新操作,可能需要选择其他的数据结构。
- ConcurrentHashMap 和 HashTable 的区别是什么?
都是 Java 中线程安全的哈希表实现
-
-
- • 实现方式不同:
1. ◦ ConcurrentHashMap 是基于 分段锁 的并发哈希表实现,它将整个哈希表分成多个小的哈希表段,每个段上有自己的锁,可以支持多个线程同时进行读写操作,从而提高了并发性能;
2. ◦ HashTable 是基于全局锁的并发哈希表实现,它在进行读写操作时需要获取整个哈希表的锁,因此并发性能比 ConcurrentHashMap 差。 - • 扩容方式不同:
1. ◦ ConcurrentHashMap 的扩容是基于分段锁的实现,它可以在多线程环境下动态地调整哈希表的大小,而不需要显式地使用锁来保护共享资源,从而提高了并发性能;
2. ◦ HashTable 的大小是固定的,在哈希表填满时需要进行扩容操作。在并发场景下,HashTable 的扩容操作需要获取整个哈希表的锁,因此会导致锁的竞争,降低并发性能; -
**•** **迭代器支持不同:**- ◦ ConcurrentHashMap 的迭代器支持弱一致性,即迭代器返回的元素可能已经被删除或添加了;
- ◦ HashTable 的迭代器不支持弱一致性,如果在迭代过程中进行修改操作,会抛出 ConcurrentModificationException 异常;
-
**• null** **值支持不同:**- ◦ ConcurrentHashMap 支持 null 值。在 ConcurrentHashMap 中,null 被视为一个特殊的值,可以被添加到哈希表中,在获取操作时需要特殊处理。
- ◦ HashTable 不支持 null 值。
- • 实现方式不同:
- 请简述并发容器的实现原理
-
并发容器是为了实现多线程环境下的安全读写而设计的。
-
-
- • 锁机制:有些并发容器在实现上采用了传统的锁机制,比如 synchronized、ReentrantLock 等。当一个线程需要对容器进行操作时,它需要先获得相应的锁,以保证其它线程不能同时进行操作,从而保证线程安全。锁机制虽然可以保证线程安全,但是对性能会有一定的影响,特别是在高并发的场景下。
- • 分段锁机制:有些并发容器采用了分段锁机制,比如 ConcurrentHashMap。ConcurrentHashMap 中的数据结构由多个桶组成,每个桶又由多个节点组成。每个桶都可以看作是一个独立的数据结构,因此可以为每个桶设置一个独立的锁。这样,当一个线程需要对容器进行操作时,只需要获取对应桶的锁,而不需要获取整个容器的锁,从而提高了并发度,减少了锁的竞争;
- • CAS 机制:有些并发容器采用了无锁机制,比如 ConcurrentLinkedQueue。在无锁机制下,线程之间并不需要进行同步,而是通过 CAS(Compare and Swap)等原子操作来实现对容器的并发访问。在高并发的场景下,无锁机制可以大大提高容器的并发度和性能;
- • Copy-On-Write 机制:有些并发容器采用了 Copy-On-Write 机制,比如 CopyOnWriteArrayList。在 Copy-On-Write 机制下,写操作并不是直接对容器进行修改,而是先将容器复制一份,在副本上进行修改,然后再用新副本替换旧副本。读操作则直接在旧副本上进行,从而保证了读操作的线程安全性。
-
- 阻塞队列
- 什么是阻塞队列?
- 阻塞队列是一种线程安全的队列,在队列已满或为空时,入队和出队操作会阻塞线程,直到队列不为空或不满。阻塞队列通常被用于生产者****-****消费者模型中,以实现线程间的同步和通信。
- ArrayBlockingQueue 与 LinkedBlockingQueue 有何不同?
- • ArrayBlockingQueue 是一个有界阻塞队列,它的内部实现是一个定长的数组。它在创建时需要指定队列的容量,一旦队列已满,进一步的入队操作将被阻塞,直到队列中有元素被取出;
- • LinkedBlockingQueue 是一个无界阻塞队列,它的内部实现是一个链表,如果创建 LinkedBlockingQueue 时没有指定容量,则创建的是一个无界队列,即队列的容量可以无限增长。如果指定了容量,则创建的是一个有界队列,即队列的容量是固定的,无法超过指定的容量。如果尝试在已满的有界队列中添加元素,则会阻塞直到队列中有空间可用
- 当队列为空时,出队操作会被阻塞,当队列已满时,入队操作会被阻塞。
- 阻塞队列如何避免线程安全问题?
- 阻塞队列通过内部实现的同步机制(例如 ReentrantLock、Condition 等)来避免线程安全问题。它们确保在队列已满或为空时,入队和出队操作只能由一个线程进行,从而避免了多线程并发访问队列时可能发生的竞态条件问题。
- 并发工具类1:CountDownLatch、CyclicBarrier、Semapho
- CountDownLatch:一个线程等待多个线程
- CyclicBarrier:多个线程互相等待 并且可以重置
- Semaphore:同一个时刻最多只能允许若干个线程同时执行
- 并发工具类2:线程池
- 线程池的工作原理
- • 一开始有任务来,把任务交给「核心线程 corePoolSize」工作;
- • 如果「核心线程」都在工作,就新来的任务在「阻塞队列 BlockingQueue」里等待;
- • 如果「阻塞队列」都满了,就开启「非核心线程」工作。
- • 如果「非核心线程」都在工作,再来新的任务就装不下了,就要采取相应的拒绝策略。
- 什么是线程池?为什么要使用线程池?
- 线程池是一种管理和重用线程的机制,它可以减少线程的创建和销毁的开销,提高系统的性能和稳定性。线程池的使用可以有效地避免线程频繁地创建和销毁,从而减少系统开销,提高应用程序的响应速度和并发能力。
- 线程池的工作原理是什么?
- 线程池的工作原理
线程池的工作原理主要分为以下几步:
-
-
- • 初始化:线程池初始化时,会创建一定数量的核心线程以及一个任务队列。这些核心线程会一直存在,除非被线程池关闭或者被替换掉;
- • 任务提交:当有新任务需要执行时,将任务放入任务队列中;
- • 线程执行任务:线程池中的线程会从任务队列中取出任务并执行;
- • 线程池维护:线程池会维护线程的数量,当线程数量超过预设的上限时,新的任务会被放入任务队列中等待执行。
- 线程池的核心参数有哪些?如何设置线程池的大小?
- 线程池的核心参数包括以下几个:
- • 核心线程数:线程池中始终保持的线程数;
- • 最大线程数:线程池中最多允许的线程数(可以这样理解:最大线程数 - 核心线程数 = 非核心线程数,非核心线程可以看成「临时工」 ,任务队列满了的时候,才会启动非核心线程);
- • 任务队列:存放等待执行的任务的队列; • 线程空闲时间:线程在空闲状态下的最大存活时间;
- • 线程池拒绝策略:当任务队列已满且线程数量达到最大值时,线程池如何处理新的任务。
-
- 原子类
- 1、简述原子类的使用场景
原子类适合在多线程环境下进行原子操作的场景。当多个线程需要对共享变量进行读取、写入、递增、递减等操作时,原子类可以提供一种线程安全的、高效的解决方案。
-
-
- • 计数器和累加器:在多线程环境下,如果需要对计数器或者累加器进行递增或递减操作,可以使用 AtomicInteger 或 AtomicLong 等原子类来实现,避免了数据竞争的问题;
- • 缓存行填充:在多线程环境下,缓存行的大小通常是 64 字节,如果共享变量之间的距离小于缓存行的大小,会导致伪共享(false sharing)问题。可以使用 AtomicLongArray 等原子类来解决这个问题;
- • 布尔标志:在多线程环境下,如果需要对布尔标志进行读取和写入操作,可以使用 AtomicBoolean 来实现,避免了数据竞争的问题;
-
总之,使用原子类可以避免多线程环境下的数据竞争和死锁等问题,并且不需要使用显式的同步机制,从而提高程序的并发性能。原子类适用于对共享变量进行简单的读取、写入、递增、递减等操作的场景,但是对于复杂的操作,需要使用更高级别的同步机制来保证程序的正确性。
-
- 原子类有什么缺点?
1. • 原子类的主要缺点是性能较低。虽然原子类可以在多线程环境下保证数据的原子性,但是这种保证是有代价的。原子类的实现通常依赖于 CAS(Compare and Swap)操作,而 CAS 操作需要保证内存的可见性、有序性和原子性,因此需要使用同步机制来实现。这种同步机制可能会导致上下文切换和缓存不一致等问题,从而影响程序的性能;
2. • 当多个原子类之间存在依赖关系时,仍然需要使用同步机制来保证程序的正确性;
3. • 原子类只能保证单个操作的原子性,而不能保证多个操作之间的原子性。如果需要进行多个操作的原子操作,需要使用更高级别的同步机制,例如使用锁或者使用并发容器。
- 原子类有什么缺点?
- AQS
- 核心思想
使用一个先进先出的双向队列(即 FIFO 队列)来管理获取锁但是未成功的线程。当一个线程请求锁时:
-
-
- • 如果锁没有被占用,那么该线程可以获取锁并继续执行;
- • 如果锁已经被其他线程占用,那么该线程就会被阻塞,并且被加入到等待队列中。在锁被释放时,AQS 会从等待队列中唤醒一个线程继续执行。
- 核心流程
- tryAcquire()—> acquire()
- tryRelease()àrelease()
- 独占和共享两种同步方式
- 独占方式是指同一时刻只能有一个线程持有锁,ReentrantLock 就是一种支持独占方式的锁;
- 共享方式则允许多个线程同时持有锁, Semaphore 是一种支持共享方式的锁。
- 支持条件变量
-
Condition 是 AQS 中提供的一种条件变量实现,它提供了 await 和 signal 方法来支持线程的等待和唤醒操作,通常和 Lock 对象一起使用。
-
- 实现自定义同步器
可以通过继承 AQS 类来实现自定义同步器,具体实现需要重写 AQS 的一些方法,如 tryAcquire、tryRelease 等
-
- 实现类:ReentrantLock、 Semaphore 、CountDownLatch
JVM
- 内存模型
- Java 内存模型通过一系列机制来保证多线程程序中的正确性和可靠性。
- 可见性 可见性指的是当一个线程修改了共享数据后,其他线程能够立即看到这个修改。Java 内存模型通过使用 volatile 关键字和 synchronized 关键字等机制来实现可见性。
- 有序性 有序性指的是程序执行的顺序不会被重排序,从而保证了线程执行指令的顺序与程序中编写的顺序一致。Java 内存模型通过使用 volatile 关键字和 synchronized 关键字等机制来实现有序性。
- 原子性 指的是若干个操作是不可被打断的整体性,不会出现部分执行成功的情况。Java 内存模型通过使用 synchronized 关键字和 Atomic 类等机制来实现原子性。
- Java 内存模型通过一系列机制来保证多线程程序中的正确性和可靠性。
- 内存空间划分为以下几个区域:
- • 堆(Heap):存放对象实例及数组等动态分配的数据,由Java垃圾回收器管理。
- • 栈(Stack):存放线程执行时所需的数据,包括局部变量、方法参数、返回地址等。
- • 方法区(Method Area):存放类信息、常量池、静态变量、即时编译器编译后的代码等。
- • 本地方法栈(Native Method Stack):存放 Java 虚拟机使用到的本地方法信息。
- String 常量池存在于哪里?
- String 常量池位于方法区(Method Area)中。
- 它用于存储编译器在编译时生成的字符,并且它们的值在整个程序生命周期内都不会发生变化。这样做的好处是可以提高字符串的重用率,避免浪费内存空间。
- Java 中堆和栈有什么区别?
- • 内存分配方式:栈(Stack)采用先进后出的方式进行内存分配和释放,堆(Heap)采用动态的方式进行内存分配和释放;
- • 存储内容:栈存储基本类型变量和对象引用,而堆存储对象实例;
- • 空间大小:栈的空间通常比堆小,它是在程序运行时直接分配的,而堆的空间是在程序启动时就预先分配的;
- • 空间管理:栈的空间由JVM自动管理,无需手动释放;堆是有垃圾回收算法管理的,程序员也无需手工释放;
- • 作用范围:栈的作用范围通常比堆小,栈中的变量只在定义它的方法中有效,方法结束后变量就会被自动释放;而堆中的对象实例可以在多个方法中使用;
- • 内存碎片:堆中的内存容易产生内存碎片,这会导致垃圾回收效率降低;而栈中的内存不会产生碎片,因为它的内存分配和释放方式是基于栈帧的。
- 垃圾分代回收机制
- 通常将堆内存分为新生代和老年代两个部分,可以根据对象的生命周期采用不同的回收算法和策略,提高垃圾回收的效率和性能。
- 新生代通常是指刚被创建的对象,因此它的对象生命周期比较短暂。为了高效地回收新生代中的对象,一般采用复制算法,将新生代分为一个较小的 Eden 区和两个较小的 Survivor 区
- 老年代则是指存活时间较长的对象,通常采用标记****-****清除或标记****-****整理算法进行回收。
- 通常将堆内存分为新生代和老年代两个部分,可以根据对象的生命周期采用不同的回收算法和策略,提高垃圾回收的效率和性能。
- JVM的永久代中会发生垃圾回收么?
- 在 JDK 8 及之前的版本中,JVM 中有一个永久代(PermGen),用于存储类的元数据信息、常量池等内容。在永久代中确实会发生垃圾回收,但是垃圾回收机制与堆(Heap)中的垃圾回收机制不同。永久代的垃圾回收主要是回收无用的类信息和常量,而不是回收对象实例。
- 在 JDK 8 之后,永久代被移除,被一个称为元空间(Metaspace)的区域所取代。元空间不再是 JVM 堆的一部分,而是直接使用本地内存。因此,在元空间中也会发生垃圾回收,但是垃圾回收机制与传统的垃圾回收机制有所不同,它主要是回收已经被加载的类信息。元空间的垃圾回收机制使用的是基于标记****-****清除算法的垃圾回收机制。
- Minor GC 与 Full GC 分别发生在什么时候?
- Minor GC 和 Full GC 都是垃圾回收(Garbage Collection)的过程,用于清理Java虚拟机(JVM)堆中不再使用的对象。
Minor GC 发生在年轻代(Young Generation)中,用于回收年轻代中不再被引用的对象。年轻代被划分为 Eden 空间、Survivor 0 空间和Survivor 1 空间。当 Eden 空间满时,触发 Minor GC。在Minor GC中,Eden空间中不再被引用的对象会被清理,并将存活的对象移动到Survivor 0 或 Survivor 1 空间,当一个 Survivor 空间被填满时,存活对象将被移动到另一个 Survivor 空间,如果 Survivor 空间不足以存储存活对象,则存活对象将被移动到年老代(Old Generation)。
-
- Full GC发生在年老代中,用于回收年老代和永久代(Permanent Generation)中不再被引用的对象。年老代是 JVM 堆中存储较长时间的对象的区域。Full GC会清理整个堆,包括年轻代和年老代,并且会停止应用程序的运行。Full GC 的触发条件比较复杂,一般包括以下几个方面:当年老代空间不足时,当永久代空间不足时,当系统内存不足时,当调用 System.gc() 方法时等。
-
进程
- 正在运行的程序(软件)就是一个独立的进程。
- 线程是属于进程的,一个进程中可以同时运行很多个线程。
- 进程中的多个线程其实是并发和并行执行的。
-
进程有独立地址空间,隔离性优于线程
-
线程资源消耗更轻量
-
不同进程逻辑地址空间独立,不共享
-
同一进程的线程共享堆内存,但栈是每个线程独立的,不共享。因此 “包括堆和栈” 的描述错误。
-
进程可通过共享内存等机制共享数据,正
-
线程间通讯因共享内存,直接访问全局变量即可,或者使用进程间通讯(IPC)。
-
网络通信


- IP地址
- IP(Internet Protocol):全称"互联网协议地址",是分配给上网设备的唯一标识
- 目前,被广泛采用的IP地址形式有两种:IPv4(32位)、IPv6(128位
- InetAddress ip1 = InetAddress.getLocalHost();)。
- 端口
- 端口号的作用
唯一标识正在计算机设备上运行的进程(程序)
-
-
- 一个设备中,不可以出现2个应用程序的端口号一样,如果一样会出现端口冲突错误。
- 协议
- 计算机网络中,连接和通信数据的规则被称为网络通信协议。
- UDP协议有什么特点?
- 用户数据报协议(UserDatagram Protocol)
- UDP是面向无连接,不可靠传输的通信协议。
- 速度快,有大小限制一次最多发送64K,数据不安全**,**易丢失数据。
- TCP协议有哪些特点?
- TCP是一种面向连接的可靠通信协议
- 传输前,采用"三次握手"方式建立连接,点对点的通信。
- 在连接中可进行大数据量的传输。
- 传输后,采用"四次挥手"方式断开连接,确保消息全部收发完毕。
- 通信效率相对较低,可靠性相对较高。
-
- UDP网络通信
- 客户端实现步骤
- 创建DatagramSocket对象(客户端对象) 扔韭菜的人
- 创建DatagramPacket对象封装需要发送的数据(数据包对象)
- 客户端实现步骤
韭菜盘子
-
-
- 使用DatagramSocket对象的send方法,传入DatagramPaacket对象开始抛出韭菜
- 释放资源
-
使用while死循环不断的接收用户的数据输入**,如果用户输入的exit****则退出程序**
-
- 服务端实现步骤
- 创建DatagramSocket对象并指定端口(服务端对象)
- 服务端实现步骤
接韭菜的人
-
-
- 创建DatagramPacket对象接收数据包对象)
-
韭菜盘子
-
-
- 使用DatagramSocket对象的receive方法,传入DatagramPacket对象
-
开始接收韭菜
-
-
- 释放资源
- 使用while死循环不断的进行第3步
-
- TCP网络通信
-
- 客户端
- 创建客户端的Socket对象,请求与服务端的连接。
- 使用socket对象调用get0utputStream()方法得到字节输出流。
- 使用字节输出流完成数据的发送。
- 释放资源:关闭socket管道。
- 服务端
- 创建ServerSocket对象,注册服务端端口。
- 调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象。
- 通过Socket对象调用getInputStream()方法得到字节输入流、完成数据的接收。
- 释放资源:关闭socket管道
- TCP通信,客户端的代表类是谁?
- 客户端
-
Socket类
public Socket(String host , int port)
-
-
- TCP通信,如何使用Socket管道发送、接收数据?
- OutputStream getOutputStream():获得字节输出流对象(发)
- InputStream getlnputStream():获得字节输入流对象(收)
- TCP通信服务端用的类是谁?
- ServerSocket类,注册端口。
- 调用accept()方法阻塞等待接收客户端连接。得到Socket对象。
- TCP通信,如何使用Socket管道发送、接收数据?
-
- 线程池优化
- BS架构的基本原理是什么?
- 客户端使用浏览器发起请求(不需要开发客户端)
- 服务端必须按照HTTP协议响应数据。
- 解决精度运算

- 单元测试
- 具体步骤
- 将Junit框架的jar包导入到项目中(注意:IDEA集成了Junit框架,不需要我们自己手工导入了)
- 为需要测试的业务类,定义对应的测试类,并为每个业务方法,编写对应的测试方法(必须:公共、无参、无返回值)
- 测试方法上必须声明@Test注解,然后在测试方法中,编写代玛调用被测试的业务方法进行测试
- 开始测试:选中测试方法,右键选择"Junit运行",如果测试通过则是绿色;如果测试失败,则是红色
- 具体步骤
反射
- Java 反射是一种强大的机制,它允许我们在运行时获取类的信息并对对象进行操作。

-
- 反射有啥作用?
-
- 可以在运行时得到一个类的全部成分然后操作。
- 可以破坏封装性。(很突出)
- 也可以破坏泛型的约束性。(很突出)
- 更重要的用途是适合:做Java高级框架
- 基本上主流框架都会基于反射设计一些通用技术功能。
-
- 除了使用关键字 new 创建对象之外,还可以用什么方法创建对象?
- • 通过反射机制创建对象:使用 Java 反射机制可以在运行时动态地创建对象,通过 Class 类的 newInstance() 方法可以创建一个对象的实例
例如: Class myClass = MyClass.class;
Object myObject = myClass.newInstance();
-
- • 使用对象克隆:Java 中的所有类都继承了 Object 类,Object 类提供了一个 clone() 方法,可以使用该方法创建一个对象的克隆对象
例如: MyClass obj1 = new MyClass();
MyClass obj2 = (MyClass) obj1.clone();
-
- • 使用反序列化创建对象:将一个对象转换为字节流进行传输或存储,可以使用反序列化机制将其重新转换为对象
例如: ObjectInputStream in = new ObjectInputStream(new FileInputStream(“object.ser”));
MyClass myObject = (MyClass) in.readObject();
需要注意的是,以上方法可能会涉及到异常处理和安全性问题,需要谨慎使用。
- 反射创建对象效率高还是通过 new 创建对象的效率高?
- 使用 new 关键字创建对象,编译器可以在编译时确定类的构造方法和字段,因此不需要在运行时进行额外的操作,创建对象的效率较高
- 使用反射机制创建对象的优势在于其灵活性和动态性
- 应用
- 框架开发
- 单元测试
- 动态代理
- 序列化与反序列化
- 调试工具
- 举例 Java 框架中用到的反射机制
- • Spring 框架中的依赖注入(DI)和控制反转(IOC):Spring 框架在初始化 Bean 的时候,会通过反射机制来获取 Bean 的信息,例如Bean 的类名、属性名和方法名等,然后动态地创建 Bean 对象并注入相关的依赖;
- • Hibernate 框架中的 ORM 映射:Hibernate 框架通过反射机制来获取实体类的属性和对应的数据库字段,从而实现 Java 对象和关系型数据库之间的映射;
- • JUnit 框架中的单元测试:JUnit 框架使用反射机制来获取测试类的信息,并执行其中被标记为测试方法的方法;
- • Java 反射 API 本身的实现:Java 反射 API 本身就是通过反射机制来实现的,它提供了许多用于获取类的信息、调用对象方法、访问对象属性等功能的方法;
- • Java 的动态代理机制:Java 动态代理机制通过反射机制来生成代理类,实现动态地创建代理对象的功能。
- 动态代理

- 动态代理的实现过程 在 Java 中,可以通过实现 InvocationHandler 接口和 Proxy 类来实现动态代理。具体实现过程如下:
-
- • 定义一个 InvocationHandler 接口的实现类,实现 invoke() 方法,该方法将被代理对象的所有方法调用;
-

InvocationHandler dynamicProxy = new DynamicProxy(realSubject);
-
-
-
- • 使用 Proxy 类的静态方法 newProxyInstance() 创建一个代理对象,该代理对象会调用 InvocationHandler 接口实现类的 invoke() 方法来处理所有方法调用;
-
-
Subject proxySubject = (Subject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(),
dynamicProxy);
-
-
-
- • 将代理对象返回给调用方,调用方可以像调用原始对象一样来使用代理对象。
-
-
proxySubject.request();
-
-
- 动态代理的主要优点在于它可以在运行时动态地生成代理对象,从而可以灵活地控制和扩展对象的行为,同时也可以实现对原始对象的保护,避免直接修改原始对象。动态代理在许多框架和库中都得到了广泛的应用,例如 Spring AOP、Hibernate 等。
- 动态代理、静态代理的区别,什么场景使用静态代理、什么场景使用动态代理?
- Java 中的代理模式是一种常见的设计模式,它可以帮助我们在不修改原有代码的情况下实现额外的功能或控制。代理模式主要有两种实现方式:静态代理和动态代理。
- 静态代理是在编译期间就已经确定了代理类的代码,代理类和目标对象之间的关系在程序运行前就已经确定。在静态代理中,代理类需要手动编写,代理类和目标对象实现同样的接口或者继承同一个父类,代理类中调用目标对象的方法,并在方法前后添加额外的逻辑或者控制。
- 静态代理的优点是可以在编译期间检查出代理类和目标对象是否实现了同样的接口或者继承了同一个父类,代理类的实现相对简单,容易理解和掌握。
- 缺点是每一个目标对象都需要对应一个代理类,如果目标对象的方法发生变化,代理类的代码也需要进行相应的修改,工作量相对较大。
- 动态代理是在运行期间动态生成代理类的代码,代理类和目标对象之间的关系在程序运行时才能确定。在动态代理中,代理类可以不需要手动编写,通过 Java 的反射机制在运行时动态生成代理类的字节码,代理类中的方法会在运行时动态地调用目标对象的方法,并在方法前后添加额外的逻辑或者控制。
- 动态代理的优点是可以在运行期间动态地生成代理类,不需要手动编写代理类,可以适应多种不同的目标对象,代码的维护和扩展性较好。
- 缺点是动态代理的实现相对较为复杂,需要了解Java反射机制和动态代理的实现原理。
- 通常情况下,如果目标对象的数量不多,且每个目标对象需要不同的代理逻辑,可以使用静态代理;如果目标对象的数量较多,且每个目标对象需要相同的代理逻辑,可以使用动态代理。另外,动态代理还可以用于 AOP 编程,帮助我们在不修改原有代码的情况下实现切面编程。
-
Redis
-
Redis是一个基于内存的键值存储数据库
-
什么是 Redis 的 RDB 和 AOF?
- • RDB 是 Redis 的一种持久化方式,将内存中的数据按照一定的格式写入到磁盘中;
- • AOF 是另一种持久化方式,将 Redis 执行的每一条写命令记录下来,并以日志的形式写入到磁盘中。
-
RDB 和 AOF 的优缺点分别是什么?
- • RDB 的优点是:能够在一定程度上提高 Redis 的性能,因为将数据写入磁盘的过程比较简单快速;
- • RDB 的缺点是:数据可能会丢失,因为 RDB 是定期执行的,如果 Redis 在 RDB 执行之前出现故障,那么数据可能会丢失;
- • AOF 的优点是:能够保证数据不会丢失,因为每一条写命令都会被记录下来;
- • AOF 的缺点是:AOF 文件可能会比较大,同时对于写操作的性能影响比较大。
-
RDB 和 AOF 的恢复机制是什么?
- • RDB 的恢复机制是在 Redis 启动时自动加载最近一次生成的 RDB 文件,并将其加载到内存中;
- • AOF 的恢复机制是在 Redis 启动时将 AOF 文件中记录的写命令依次执行一遍,以恢复数据。
-
RDB 和 AOF 的触发机制是什么?
- • RDB 的触发机制是可以设置定期执行,也可以在满足一定条件时执行;
-
- • AOF 的触发机制是可以设置每执行多少次写操作就同步一次,也可以设置每隔多长时间同步一次。
-
如何选择使用 RDB 还是 AOF? 选择使用哪种持久化方式主要取决于对数据的要求。
- • 如果对数据的完整性要求比较高,那么应该选择 AOF;
-
- • 如果对数据的完整性要求不高,但是对于性能要求比较高,那么可以选择 RDB。
-
- 还可以同时使用两种持久化方式,以提高数据的可靠性和灵活性。
-
Redis 在使用 RDB 和 AOF 时是否可以同时启用?
- • 可以同时启用 RDB 和 AOF;
- • Redis 会先加载 AOF 文件,然后再加载 RDB 文件。也可以只启用其中一种持久化方式。
-
Redis 会先加载 AOF 文件,然后再加载 RDB 文件。数据会冲突吗?
- • Redis 在启动时,会优先根据用户配置选择是否载入 AOF 文件和 RDB 文件。如果两者都选择载入,那么 Redis 会先载入 AOF 文件,然后再载入 RDB 文件;
- • 由于 Redis 会先载入 AOF 文件,所以如果 AOF 文件中包含的数据和 RDB 文件中包含的数据冲突,那么 Redis 会 以 AOF 文件中的数据为准,因为 AOF 文件中记录的是 Redis 执行的所有写操作,而 RDB 文件则是 Redis 在某个时间点的快照。因此,如果 AOF 文件中的数据和 RDB 文件中的数据不一致,那么 Redis 会优先使用 AOF 文件中的数据;
- • 需要注意的是,如果 AOF 文件和 RDB 文件中都包含同一个 key 的数据,那么 Redis 会使用 AOF 文件中最后一次写入的数据。这是因为 AOF 文件中的数据是 Redis 执行的所有写操作的记录,而 RDB 文件只是某个时间点的快照。因此,在相同 key 存在于 AOF 文件和 RDB 文件时,Redis 会以 AOF 文件中最后一次写入的数据为准。
-
数据类型
- 字符串(String)
- 列表(List)
- 集合(Set)
- 散列表(Hash)
- 有序集合(Sorted Set)
-
主从复制
- Redis 主从复制是一种数据复制技术,用于将一个 Redis 服务器的数据复制到多个 Redis 服务器上,以提高系统的可用性和性能。在 Redis 主从复制中,一个 Redis 服务器作为主服务器,其他 Redis 服务器作为从服务器,主服务器将数据复制到从服务器上。
- 主从复制的原理
- • 当从服务器启动时,它会向主服务器发送一条同步命令,请求复制主服务器上的数据;
- • 主服务器收到同步命令后,会开启一个新的后台线程,将数据发送给从服务器;
- • 从服务器接收到数据后,将其存储在本地,并周期性地向主服务器发送同步命令,以获取更新的数据
- • 当主服务器接收到写操作时,会将写操作发送给所有连接的从服务器,并在本地执行写操作,从而保证数据的一致性。
- Redis 主从复制的优点
1. • 提高了系统的可用性,当主服务器发生故障时,可以快速切换到从服务器上继续提供服务;
2. • 提高了系统的性能,可以通过增加从服务器来分摊主服务器的读取负载;
3. • 提高了数据的可靠性,数据会被复制到多个服务器上,即使其中某些服务器发生故障,数据仍然可以被保留。
4. 需要注意的是,Redis 主从复制是异步的,从服务器的数据可能与主服务器上的数据有一定的延迟,因此在使用主从复制时,需要根据实际情况进行配置和调优。
-
哨兵
- 什么是 Redis 哨兵机制?
- Redis 哨兵机制是 Redis 的高可用性解决方案之一,它可以监控 Redis 主从复制模式下的主节点,如果主节点出现故障,哨兵会自动选举一个从节点作为新的主节点,并通知其他从节点切换到新的主节点上。
- Redis 哨兵机制如何保证高可用性?
- Redis 哨兵机制通过监控 Redis 主从复制模式下的主节点,及时发现主节点故障,并通过自动选举新的主节点来保证服务的持续可用性。
- Redis 哨兵机制中的 quorum 是什么? Redis 哨兵机制中的 quorum 是指在进行主节点切换时,需要多少个哨兵节点同意切换操作才能进行切换。quorum 的大小可以通过配置文件进行设置。
- Redis 哨兵机制中的哨兵节点的作用是什么? Redis 哨兵机制中的哨兵节点负责监控 Redis 主从复制模式下的主节点,及时发现主节点故障,并通过自动选举新的主节点来保证服务的持续可用性。
- Redis 哨兵机制中的故障转移流程是怎样的? Redis 哨兵机制中的故障转移流程如下:
- • 哨兵节点检测到主节点宕机;
- • 哨兵节点从所有从节点中选出一个最优的从节点作为新的主节点;
- • 哨兵节点向其他哨兵节点发送消息,请求对新的主节点进行投票;
- • 如果超过一半的哨兵节点同意切换操作,则进行切换,将新的主节点选举为主;
- • 哨兵节点向所有从节点发送消息,通知它们将新的主节点设置为自己的主节点,并开始复制数据;
- • 故障转移完成。
- Redis 哨兵机制有哪些缺点? Redis 哨兵机制的缺点包括:
- • 哨兵节点需要额外的资源和管理成本;
- • 故障转移需要一定的时间,可能会影响服务的可用性;
- • 如果多个哨兵节点同时宕机,可能会导致整个系统的不可用。
- Redis 哨兵机制与 Redis 集群有什么区别?
- Redis 哨兵机制通过监控 Redis 主从复制模式下的主节点,自动选举新的主节点,并通知其他从节点切换到新的主节点上,来保证服务的持续可用性。
- Redis 集群则是一种分布式数据库解决方案,可以将数据分散到多个节点上进行存储和处理,通过集群中的各个节点共同处理请求来提高系统的性能和可用性。此外,Redis 集群还支持数据的分片,使得集群可以处理更大规模的数据。
- 什么是 Redis 哨兵机制?
RocketMQ
- RockerMQ 具有以下几个优点:
- • 高吞吐量:RocketMQ 具有极高的消息处理能力和吞吐量,单个 Broker 可支持百万级别的消息并发处理。
- • 高可用性:RockerMQ 支持 Master-Slave 机制,可以保证在 Master 节点发生故障时,数据不会丢失;
- • 高性能:RockerMQ 的吞吐量非常高,可以处理大量的消息;
- • 可扩展性:RockerMQ 支持集群模式,在集群中可以动态添加或删除 Broker,以适应不同规模的业务需求;
- • 易于使用:RockerMQ 提供了简单易用的 API 和命令行工具,方便用户进行开发和管理。
- RocketMQ 的高级特性包括事务消息、顺序消息、延迟消息和批量消息等。
单例模式
- 单例模式在多线程环境中为什么需要特殊注意?请给出一种线程不安全的单例实现,并说明它的问题所在。
- •当多个线程同时调用获取单例对象的方法时,有可能会创建多个实例,从而破坏单例的唯一性;
- • 上面给出的懒汉式单例模式就是一个线程不安全的单例实现; •
- 为了解决这个问题,我们可以使用 synchronized 关键字来保证 getInstance() 方法在同一时刻只能被一个线程访问,从而避免多个线程同时创建实例的情况; • 但是使用 synchronized 关键字会影响性能,所以也可以使用双重检查锁定机制来解决线程安全问题。
- 如何通过枚举类型来实现单例模式?与其他实现方式相比,它有哪些优势?
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("Do something...");
}
}
相比于其他实现方式,使用枚举类型实现单例模式有以下优势:
-
-
- • 线程安全:枚举类型在 Java 中是线程安全的,因此不需要担心多线程下的并发问题;
- • 避免反射攻击:枚举类型没有公共的构造函数,无法通过反射来创建新的实例,因此能够防止反射攻击;
- • 避免序列化问题:枚举类型默认情况下是不支持序列化的,因此能够避免序列化问题。如果要让枚举类型支持序列化,需要手动添加一个 serialVersionUID 字段,并在枚举类型中实现 readResolve() 方法。这样才能保证枚举类型在序列化和反序列化的过程中都能正确地恢复;
- • 简单易用:使用枚举类型实现单例非常简单,而且代码量非常少,易于理解和维护。
-
工厂模式
- 工厂方法模式是一种创建型设计模式,它定义了一个用于创建对象的接口,但让子类决定实例化哪个类。也就是说,工厂方法模式将对象的实例化交给子类去完成,从而使得代码更加灵活和可扩展。
- 在工厂方法模式中,通常会有一个抽象工厂类和多个具体工厂类。抽象工厂定义了一个工厂方法,用于创建产品对象,并且该方法通常是抽象的。具体工厂类则实现了抽象工厂中的工厂方法,用于实例化具体的产品对象。
想象你走进一家蛋糕店,这家蛋糕店就像是一个 “工厂”。你来到店里,不需要自己去制作蛋糕,只需要告诉店员你想要什么口味的蛋糕,比如巧克力蛋糕、草莓蛋糕或者抹茶蛋糕。店员会根据你的需求,从厨房拿出相应的蛋糕给你。在这个过程中,你不用关心蛋糕是怎么做出来的,只需要表达你的需求,蛋糕店(工厂)就会为你提供合适的蛋糕(对象)。
建造者模式
- 建造者设计模式是一种创建型的设计模式,它将复杂对象的构建和表示分离开来,通过一步一步地构建一个复杂对象,最终得到一个完整的对象实例。它可以帮助我们更加灵活、可扩展和易于维护地创建复杂对象。下面是建造者设计模式的几个要点:
- • 定义一个产品类:产品类是需要被构建出来的复杂对象,它包含多个组成部分;
- • 抽象出一个 Builder 接口:Builder 接口用于定义产品构建过程中的各个步骤,以及如何将这些步骤组合起来构建产品;
- • 创建具体的 Builder 实现类:具体的 Builder 实现类用于实现 Builder 接口,并完成产品的具体构建过程。不同的 Builder 实现类可以构建出不同的产品;
- • 客户端代码通过指定 Builder 来构建产品:客户端代码可以根据需要选择不同的 Builder 实现类来构建产品;
- • 使用链式调用(Fluent Interface)方式:在 Builder 接口中定义各个方法时使用返回 this 的方式,可以实现链式调用,使得客户端代码更加简洁易读;
- • 提供默认值:为了方便客户端代码的使用,可以在 Builder 接口中提供一些默认值,使得客户端代码可以只设置必要的参数。 总之,建造者设计模式可以帮助我们更加灵活、可扩展和易于维护地创建复杂对象,尤其适用于那些需要多步骤构建的对象。它能够有效地降低系统的耦合性,提高代码的重用性和可读性,是一种非常实用和常用的设计模式。
想象你去定制一辆汽车:
选择配置:你可以选择不同的发动机、轮胎、内饰等部件。
组装过程:工厂会按照你的要求一步步组装汽车,比如先装发动机,再装轮胎,最后安装内饰。
最终交付:工厂最终交给你一辆完整的定制汽车。
建造者模式就像这个过程,用户只需要指定 “想要什么配置”,而无需关心 “如何一步步组装”。
装饰器模式
- 装饰器设计模式是一种结构型设计模式,它允许在不改变原有对象的基础上,动态地添加新的行为或修改已有行为;
- 通过将对象传递给装饰器函数或类,可以创建一个包装对象,该对象可以在执行原始对象的操作之前或之后执行其他操作;
- 这种模式使得代码更加灵活和可扩展,而不需要在原始对象中硬编码所有可能的变体。
Java开发的就业市场正在经历结构性调整,竞争日益激烈
传统纯业务开发岗位(如仅完成增删改查业务的后端工程师)的需求,特别是入门级岗位,正显著萎缩。随着企业技术需求升级,市场对Java人才的要求已从通用技能转向了更深入的领域经验(如云原生、微服务)或前沿的AI集成能力。这也导致岗位竞争加剧,在一、二线城市,求职者不仅面临技术内卷,还需应对学历与项目经验的高门槛。
大模型为核心的AI领域正展现出前所未有的就业热度与人才红利
2025年,AI相关新发岗位数量同比激增543%,单月增幅最高超过11倍,大模型算法工程师位居热门岗位前列。行业顶尖人才的供需严重失衡,议价能力极强,跳槽薪资涨幅可达30%-50%。值得注意的是,市场并非单纯青睐算法研究员,而是急需能将大模型能力落地于复杂业务系统的工程人才。这使得具备企业级架构思维和复杂系统整合经验的Java工程师,在向“Java+大模型”复合人才转型时拥有独特优势,成为企业竞相争夺的对象,其薪资天花板也远高于传统Java岗位。

说真的,这两年看着身边一个个搞Java、C++、前端、数据、架构的开始卷大模型,挺唏嘘的。大家最开始都是写接口、搞Spring Boot、连数据库、配Redis,稳稳当当过日子。
结果GPT、DeepSeek火了之后,整条线上的人都开始有点慌了,大家都在想:“我是不是要学大模型,不然这饭碗还能保多久?”
先给出最直接的答案:一定要把现有的技术和大模型结合起来,而不是抛弃你们现有技术!掌握AI能力的Java工程师比纯Java岗要吃香的多。
即使现在裁员、降薪、团队解散的比比皆是……但后续的趋势一定是AI应用落地!大模型方向才是实现职业升级、提升薪资待遇的绝佳机遇!
如何学习AGI大模型?
作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
2025最新版CSDN大礼包:《AGI大模型学习资源包》免费分享**
一、2025最新大模型学习路线
一个明确的学习路线可以帮助新人了解从哪里开始,按照什么顺序学习,以及需要掌握哪些知识点。大模型领域涉及的知识点非常广泛,没有明确的学习路线可能会导致新人感到迷茫,不知道应该专注于哪些内容。
我们把学习路线分成L1到L4四个阶段,一步步带你从入门到进阶,从理论到实战。

L1级别:AI大模型时代的华丽登场
L1阶段:我们会去了解大模型的基础知识,以及大模型在各个行业的应用和分析;学习理解大模型的核心原理,关键技术,以及大模型应用场景;通过理论原理结合多个项目实战,从提示工程基础到提示工程进阶,掌握Prompt提示工程。

L2级别:AI大模型RAG应用开发工程
L2阶段是我们的AI大模型RAG应用开发工程,我们会去学习RAG检索增强生成:包括Naive RAG、Advanced-RAG以及RAG性能评估,还有GraphRAG在内的多个RAG热门项目的分析。

L3级别:大模型Agent应用架构进阶实践
L3阶段:大模型Agent应用架构进阶实现,我们会去学习LangChain、 LIamaIndex框架,也会学习到AutoGPT、 MetaGPT等多Agent系统,打造我们自己的Agent智能体;同时还可以学习到包括Coze、Dify在内的可视化工具的使用。

L4级别:大模型微调与私有化部署
L4阶段:大模型的微调和私有化部署,我们会更加深入的探讨Transformer架构,学习大模型的微调技术,利用DeepSpeed、Lamam Factory等工具快速进行模型微调;并通过Ollama、vLLM等推理部署框架,实现模型的快速部署。

整个大模型学习路线L1主要是对大模型的理论基础、生态以及提示词他的一个学习掌握;而L3 L4更多的是通过项目实战来掌握大模型的应用开发,针对以上大模型的学习路线我们也整理了对应的学习视频教程,和配套的学习资料。
二、大模型经典PDF书籍
书籍和学习文档资料是学习大模型过程中必不可少的,我们精选了一系列深入探讨大模型技术的书籍和学习文档,它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础。(书籍含电子版PDF)

三、大模型视频教程
对于很多自学或者没有基础的同学来说,书籍这些纯文字类的学习教材会觉得比较晦涩难以理解,因此,我们提供了丰富的大模型视频教程,以动态、形象的方式展示技术概念,帮助你更快、更轻松地掌握核心知识。

四、大模型项目实战
学以致用 ,当你的理论知识积累到一定程度,就需要通过项目实战,在实际操作中检验和巩固你所学到的知识,同时为你找工作和职业发展打下坚实的基础。

五、大模型面试题
面试不仅是技术的较量,更需要充分的准备。
在你已经掌握了大模型技术之后,就需要开始准备面试,我们将提供精心整理的大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取




7290

被折叠的 条评论
为什么被折叠?



