关于整体学习路线的补充知识点
路线图
JAVA
基础
概述
垃圾回收
java值传递机制
例子:
https://www.iteye.com/problems/77923
https://www.zhihu.com/search?type=content&q=java值传递
JDK\JRE\JVM
基本语法
命名规范
关键字:所有关键字都是小写的
变量
自动类型提升(只涉及7种基本数据类型)
强制类型转换 (只涉及7种基本数据类型)
:自动类型提升运算的逆运算。
1.需要使用强转符:()
2.注意点:强制类型转换,可能导致精度损失。
String与8种基本数据类型间的运算
- String属于引用数据类型,翻译为:字符串
- 声明String类型变量时,使用一对""
- String可以和8种基本数据类型变量做运算,且运算只能是连接运算:+
- 运算的结果仍然是String类型
避免:
String s = 123;//编译错误
String s1 = "123";
int i = (int)s1;//编译错误
char类型对应的是ASCII码
练习
运算符
Scanner类的使用
如何从键盘获取不同类型的变量:需要使用Scanner类
具体实现步骤:
1.导包:import java.util.Scanner;
2.Scanner的实例化:Scanner scan = new Scanner(System.in);
3.调用Scanner类的相关方法(next() / nextXxx()),来获取指定类型的变量注意:
需要根据相应的方法,来输入指定类型的值。如果输入的数据类型与要求的类型不匹配时,会报异常:InputMisMatchException
导致程序终止。
//1.导包:import java.util.Scanner;
import java.util.Scanner;
class ScannerTest{
public static void main(String[] args){
//2.Scanner的实例化 System.in表示从键盘输入
Scanner scan = new Scanner(System.in);
//3.调用Scanner类的相关方法
System.out.println("请输入你的姓名:");
String name = scan.next();
System.out.println(name);
System.out.println("请输入你的芳龄:");
int age = scan.nextInt();
System.out.println(age);
System.out.println("请输入你的体重:");
double weight = scan.nextDouble();
System.out.println(weight);
System.out.println("你是否相中我了呢?(true/false)");
boolean isLove = scan.nextBoolean();
System.out.println(isLove);
//对于char型的获取,Scanner没有提供相关的方法。只能获取一个字符串
System.out.println("请输入你的性别:(男/女)");
String gender = scan.next();//"男"
char genderChar = gender.charAt(0);//获取索引为0位置上的字符
System.out.println(genderChar);
}
}
循环语法的使用
break、continue关键字的使用:
带标签的break、continue关键字的使用:
数组
一维数组概述
int[] ids;//声明
//1.1 静态初始化:数组的初始化和数组元素的赋值操作同时进行
ids = new int[]{1001,1002,1003,1004};
//1.2动态初始化:数组的初始化和数组元素的赋值操作分开进行
String[] names = new String[5];说明:
数组一旦初始化,其长度就是确定的。arr.length
数组长度一旦确定,就不可修改。
2.一维数组默认初始化值
数组元素是整型:0
数组元素是浮点型:0.0
数组元素是char型:0或'\u0000' (对应ASCII码是0的字符) ,而非'0'
数组元素是boolean型:false
数组元素是引用数据类型:null3.一维数组内存解析
局部变量放在栈中,栈中保存数组的首地址值;堆中放new的结构
二维数组概述
1.如何理解二维数组?
数组属于引用数据类型
数组的元素也可以是引用数据类型
一个一维数组A的元素如果还是一个一维数组类型的,则,此数组A称为二维数组。
2.二维数组的声明与初始化
//静态初始化
int[][] arr1 = new int[][]{{1,2,3},{4,5},{6,7,8}};
//动态初始化1
String[][] arr2 = new String[3][2];
//动态初始化2
String[][] arr3 = new String[3][];
//System.out.println(arr3[1][0]);//空指针异常
arr3[1] = new string[4];
System.out.println(arr3[1][0]); //null
3.二维数组默认初始化值
规定:二维数组分为外层数组的元素,内层数组的元素
int[][] arr = new int[4][3];
外层元素:arr[0],arr[1]等
内层元素:arr[0][0],arr[1][2]等
针对于初始化方式一:比如:int[][] arr = new int[4][3];
外层元素的初始化值为:地址值
内层元素的初始化值为:与一维数组初始化情况相同
针对于初始化方式二:比如:int[][] arr = new int[4][];
外层元素的初始化值为:null———> 一维数组还未初始化,数组属于引用数据类型,默认值:null
内层元素的初始化值为:不能调用,否则报空指针异常。
4.二维数组内存解析
关于数组首地址值
数组栈中的首地址值,实际是JVM算出的hash值,屏蔽了底层真实的地址!!!
数组算法
- Arrays工具类的使用
排序算法
https://www.cnblogs.com/BigMonster-S/p/14988178.html
面向对象_上
类和对象
概述
1.面向对象学习的三条主线:
- 1.Java类及类的成员:属性、方法、构造器;代码块、内部类
- 2.面向对象的大特征:封装性、继承性、多态性、(抽象性)
- 3.其它关键字:this、super、static、final、abstract、interface、package、import等
- “大处着眼,小处着手”
2.面向对象与面向过程(理解)
- 1.面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做。
- 2.面向对象:强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
- 举例对比:人把大象装进冰箱。
3.完成一个项目(或功能)的思路:
4.面向对象中两个重要的概念:
- 类:对一类事物的描述,是抽象的、概念上的定义
- 对象:是实际存在的该类事物的每个个体,因而也称为实例(instance)
- 面向对象程序设计的重点是类的设计
- 设计类,就是设计类的成员。
二者的关系:
对象,是由类new出来的,派生出来的。
匿名对象:
- 我们创建的对象,没显式的赋给一个变量名。即为匿名对象
- 特点:匿名对象只能调用一次。
内存解析
属性是放在堆空间中
类中的属性、局部变量、方法声明
-
类的设计中,两个重要结构之一:属性
-
对比:属性 vs 局部变量
1.相同点:
1.1 定义变量的格式:数据类型 变量名 = 变量值
1.2 先声明,后使用
1.3 变量都其对应的作用域
2.不同点:
2.1 在类中声明的位置的不同
属性:直接定义在类的一对{}内
局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量2.2 关于权限修饰符的不同 属性:可以在声明属性时,指明其权限,使用权限修饰符。 常用的权限修饰符:private、public、缺省、protected --->封装性 目前,大家声明属性时,都使用缺省就可以了。 **==<font color = 'red'>局部变量:不可以使用权限修饰符。</font>==** 2.3 默认初始化值的情况: 属性:类的属性,根据其类型,都默认初始化值。 整型(byte、short、int、long:0) 浮点型(float、double:0.0) 字符型(char:0 (或'\u0000')) 布尔型(boolean:false) 引用数据类型(类、数组、接口:null) 局部变量:没默认初始化值。 意味着,我们在调用局部变量之前,一定要显式赋值。 特别地:形参在调用时,我们赋值即可。 2.4 在内存中加载的位置: 属性:加载到堆空间中 (非static) 局部变量:加载到栈空间
-
类中方法的声明与使用
-
方法:描述类应该具的功能。
- 比如:Math类:sqrt()\random() ...
-
Scanner类:nextXxx() ...
-
Arrays类:sort() \ binarySearch() \ toString() \ equals() \ .
- 万事万物皆对象
再谈方法
-
方法的重载
-
可变个数形参
- 递归方法
递归方法导致栈溢出
每次调用递归方法都会新建方法形参(局部变量),而局部变量声明在栈中,会导致栈溢出。
方法参数的值传递机制
1.Java的值传递机制
规则:
如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
2.针对于方法的参数概念
形参:方法定义时,声明的小括号内的参数
实参:方法调用时,实际传递给形参的数据
重要
-
数组交换1.0
-
结果
-
引用数据类型——交换值-内存解析
- 总结:值传递
对于基本数据类型,赋值的是变量所保存的 数据值,新建的是无关的复制变量,怎么改都是对新建的修改;
对于引用数据类型,赋值的是变量所保存的数据的 地址值,新建的变量也指向之前的实体,怎么改相当于对之前的也进行修改
- 总结:值传递
封装与隐藏
- 一、问题的引入
当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如:setLegs()同时,我们需要避免用户再使用"对象.属性"的方式对属性进行赋值。则需要将属性声明为私有的(private).
-->此时,针对于属性就体现了封装性。
-
二、封装性思想具体的代码体现:
-
Java规定的四种权限修饰符
-
权限从小到大顺序为:private < 缺省 < protected < public
-
具体的修饰范围:
-
权限修饰符可用来修饰的结构说明:
4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
修饰类的话,只能使用:缺省、public
-
构造器(构造方法):Constructor
- 构造器的作用:
- 1.创建对象
- 2.初始化对象的信息
- 使用说明:
- 1.如果没显式的定义类的构造器的话,则系统默认提供一个空参的构造器
- 1.1系统默认提供的空参构造器权限与该构造器所在类的权限相同
- 2.定义构造器的格式:权限修饰符 类名(形参列表){}
- 3.一个类中定义的多个构造器,彼此构成重载
- 4.一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
- 5.一个类中,至少会有一个构造器。
属性赋值的先后顺序
① 默认初始化(类中)
② 显式初始化 (类中)
③ 构造器中初始化
- 以上三步是类加载时就会加载的,只执行一次
④ 通过"对象.方法" 或 "对象.属性"的方式,赋值
以上操作的先后顺序:① - ② - ③ - ④
JavaBean的概念
所谓JavaBean,是指符合如下标准的Java类:
>类是公共的
>一个无参的公共的构造器
>属性,且对应的get、set方法
this\package\import关键字
this关键字
1.可以调用的结构:属性、方法;构造器
2.this调用属性、方法:
this理解为:当前对象 或 当前正在创建的对象
2.1 在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。但是,
- 通常情况下,我们都择省略"this."。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式
- 的使用"this.变量"的方式,表明此变量是属性,而非形参。
- 2.2 在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都择省略"this."。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
3.this调用构造器:
① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
② 构造器中不能通过"this(形参列表)"方式调用自己
③ 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)"
④ 规定:"this(形参列表)"必须声明在当前构造器的首行
⑤ 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器
package关键字
- package的使用
1.1 使用说明:
- 1.为了更好的实现项目中类的管理,提供包的概念
- 2.使用package声明类或接口所属的包,声明在源文件的首行
- 3.包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、“见名知意”
- 4.每"."一次,就代表一层文件目录。
1.2 举例:
举例一:
某航运软件系统包括:一组域对象、GUI和reports子系统
举例二:MVC设计模式
MVC是常用的设计模式之一,将整个程序分为三层次:视图模型层,控制器与视图模型层,这种将程序输入输出、数据处理,以及数据的展示分离开来设计模式使程序结构变的灵活而且清晰,同时也描述了各个对象间通信方式,降低了程序的耦合性。
1.3 JDK中的主要包介绍:
import关键字
import:导入
-
- 在源文件中显式的使用import结构导入指定包下的类、接口
-
- 声明在包的声明和类的声明之间
-
- 如果需要导入多个结构,则并列写出即可
-
- 可以使用"xxx.*"的方式,表示可以导入xxx包下的所结构
-
- 如果使用的类或接口是java.lang包下定义的,则可以省略import结构
-
- 如果使用的类或接口是本包下定义的,则可以省略import结构
-
- 如果在源文件中,使用了不同包下的同名的类,则必须至少一个类需要以全类名的方式显示。
-
- 使用"xxx.*"方式表明可以调用xxx包下的所结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入
-
- import static:导入指定类或接口中的静态结构:属性或方法。
面向对象_中
面向对象的特征二:继承性
-
体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。
- 特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只因为封装性的影响,使得子类不能直接调用父类的结构而已。
- 特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只因为封装性的影响,使得子类不能直接调用父类的结构而已。
-
子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
- 子类和父类的关系,不同于子集和集合的关系。
- extends:延展、扩展
方法的重写
-
什么是方法的重写(override 或 overwrite)?
- 子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作.
- 重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
-
重写的规则:
- 方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{
-
//方法体
-
}
-
约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
-
① 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
-
② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
-
>特殊情况:子类不能重写父类中声明为private权限的方法
-
③ 返回值类型:
-
>父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
-
>父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
-
>父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
-
④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)
- 子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
- 实际开发中,子类重写的方法除了方法体,其他一般都一样
-
区分方法的重写和重载?
关键字:super
1.super 关键字可以理解为:父类的
2.可以用来调用的结构:属性、方法、构造器
3.super调用属性、方法:
3.1 我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
3.2 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性。
3.3 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法。
4.super调用构造器:
4.1 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
4.2 "super(形参列表)"的使用,必须声明在子类构造器的首行!
4.3 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现
4.4 在构造器的首行,没显式的声明"this(形参列表)"或"super(形参列表)",则默认调用的是父类中空参的构造器:super()
4.5 在类的多个构造器中,至少一个类的构造器中使用了"super(形参列表)",调用父类中的构造器
子类对象实例化全过程
1.从结果上看:继承性
子类继承父类以后,就获取了父类中声明的属性或方法。
创建子类的对象,在堆空间中,就会加载所父类中声明的属性。
2.从过程上看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,...直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所的父类的结构,所以才可以看到内存中父类中的结构,子类对象才可以考虑进行调用。
3.强调说明:
虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
面向对象的特征三:多态性
-
1.何为多态性:
对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用) -
2.多态性的使用:虚拟方法调用
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
总结:编译,看左边;运行,看右边。 -
3.多态性的使用前提:
① 类的继承关系 ② 方法的重写 -
4.多态性使用的注意点:
对象的多态性,只适用于方法,不适用于属性(属性:编译和运行都看左边)
- 多态是运行时行为.不是编译时行为
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
如何才能调用子类特的属性和方法?
使用向下转型:使用强制类型转换符:()
- 重载和重写
instanceof关键字的使用
① a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
② 如果 a instanceof A返回true,则 a instanceof B也返回true.其中,类B是类A的父类。
Object类的使用
equals()方法
https://blog.csdn.net/qq_43705131/article/details/108237846?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163232068916780357294388%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163232068916780357294388&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_click~default-1-108237846.first_rank_v2_pc_rank_v29&utm_term=%E9%87%8D%E5%86%99hashcode%E5%92%8Cequals%E6%96%B9%E6%B3%95&spm=1018.2226.3001.4187
-
- 是一个方法,而非运算符
-
- 只能适用于引用数据类型
-
- Object类中equals()的定义:
- public boolean equals(Object obj) {
return (this == obj);
} - 说明:Object类中定义的equals()和的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体==
-
- 像String、Date、File、包装类等都重写了Object类中的equals()方法。
- 重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。
-
- 通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们
- 就需要对Object类中的equals()进行重写.
- 重写的原则:比较两个对象的实体内容是否相同.
自定义类重写equals()方法:
//重写的原则:比较两个对象的实体内容(即:name和age)是否相同
//手动实现equals()的重写
@Override
public boolean equals(Object obj) {
// System.out.println("Customer equals()....");
if (this == obj) {
return true;
}
if(obj instanceof Customer){
Customer cust = (Customer)obj;
//比较两个对象的每个属性是否都相同
// if(this.age == cust.age && this.name.equals(cust.name)){
// return true;
// }else{
// return false;
// }
//或
return this.age == cust.age && this.name.equals(cust.name);
}
return false;
}
自动生成equals()方法
//自动生成的equals()
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Customer other = (Customer) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
-
回顾==运算符
- == :运算符
-
- 可以使用在基本数据类型变量和引用数据类型变量中
-
- 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
- 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
- 补充: == 符号使用时,必须保证符号左右两边的变量类型(基本数据类型变量和引用数据类型变量)一致。
-
总结equals()方法 与 ==
toString()方法
toString()的使用:
-
- 当我们输出一个对象的引用(地址值)时,实际上就是调用当前对象的toString()
-
- Object类中toString()的定义:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
-
- 像String、Date、File、包装类等都重写了Object类中的toString()方法。
- 使得在调用对象的toString()时,返回"实体内容"信息
-
- 自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容"
如何重写toString()
//自动实现
@Override
public String toString() {
return "Customer [name=" + name + ", age=" + age + "]";
}
包装类的使用
-
为了使基本数据类型的变量具有类的特征,引入包装类。
-
基本数据类型与对应的包装类:
-
需要掌握的类型间的转换:(基本数据类型、包装类、String)
- 基本数据类型与包装类的转换
- 基本数据类型与包装类的转换
-
String类型 --->基本数据类型、包装类:调用包装类的parseXxx(String s)
-
基本数据类型、包装类--->String类型:调用String重载的valueOf(Xxx xxx)
简易版:
面向对象_下
关键字static
static修饰属性:静态变量(或类变量)
-
变量分类
-
出现
-
描述
static修饰方法:静态方法、类方法
① 随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
② 静态方法 非静态方法
-
类 yes no
-
对象 yes yes
③ 静态方法中,只能调用静态的方法或属性
非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
main()的使用说明
类的结构:代码块
关键字:final/abstract
Java高级
Java常用类
String类
-
概述
-
String的不可变性
- 说明
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
- 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。(新建)
- 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。(新建)
- 说明
代码举例
String s1 = "abc";//字面量的定义方式
String s2 = "abc";
s1 = "hello";
System.out.println(s1 == s2);//比较s1和s2的地址值
System.out.println(s1);//hello
System.out.println(s2);//abc
System.out.println("*****************");
String s3 = "abc";
s3 += "def";
System.out.println(s3);//abcdef 新造s3
System.out.println(s2);
System.out.println("*****************");
String s4 = "abc";
String s5 = s4.replace('a', 'm');
System.out.println(s4);//abc
System.out.println(s5);//mbc
- String实例化的不同方式
方式一:通过字面量定义的方式
方式二:通过new + 构造器的方式
代码举例
//通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
String s1 = "javaEE";
String s2 = "javaEE";
//通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
String s3 = new String("javaEE");
String s4 = new String("javaEE");
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false
- 面试题
String s = new String("abc");方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:"abc"
- 字符串拼接方式赋值的对比
- 说明
1.字面量与字面量的拼接结果在字符串常量池。且常量池中不会存在相同内容的常量。
2.只要其中一个是变量,结果就在堆中。
3.如果拼接的结果调用intern()方法,返回值就在常量池中
- 说明
代码举例
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop"; //字面量 + 字面量
String s5 = s1 + "hadoop"; //变量 + 字面量
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
String s8 = s6.intern(); //返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
System.out.println(s3 == s8);//true
****************************
String s1 = "javaEEhadoop";
String s2 = "javaEE";
String s3 = s2 + "hadoop";
System.out.println(s1 == s3);//false
final String s4 = "javaEE";//s4:常量
String s5 = s4 + "hadoop";
System.out.println(s1 == s5);//true
JVM中涉及字符串的内部结构
StringBuffer、StringBuilder
-
StringBuffer、StringBuilder只能通过new获得,无法通过像String还可以通过字面量
- 1.String、StringBuffer、StringBuilder三者的对比
- String:不可变的字符序列;底层使用char[]存储
- StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
- StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储
- 1.String、StringBuffer、StringBuilder三者的对比
-
StringBuffer与StringBuilder的内存解析
- 以StringBuffer为例:
String str = new String();//char[] value = new char[0];
String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
System.out.println(sb1.length());//
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';
StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];
//问题1. System.out.println(sb2.length());//3
//问题2. 扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
//默认情况下,扩容为原来容量的2倍 + 2,同时将原数组中的元素复制到新的数组中。
//指导意义:开发中建议大家使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)
Java比较器(比较引用数据类型)
-
1.Java比较器的使用背景:
Java中的对象,正常情况下,只能进行比较:== 或 != 。不能使用 > 或 < 的但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。
如何实现?使用两个接口中的任何一个:Comparable 或 Comparator -
2.自然排序:使用Comparable接口
- 2.1 说明
- 1.像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。
- 2.像String、包装类重写compareTo()方法以后,进行了从小到大的排列
-
- 重写compareTo(obj)的规则:
如果当前对象this大于形参对象obj,则返回正整数,
如果当前对象this小于形参对象obj,则返回负整数,
如果当前对象this等于形参对象obj,则返回零。
- 重写compareTo(obj)的规则:
-
- 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。在compareTo(obj)方法中指明如何排序
- 2.1 说明
-
3.定制排序:使用Comparator接口
- 3.1 说明
1.背景:
当元素的类型没实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序
2.重写compare(Object o1,Object o2)方法,比较o1和o2的大小:
如果方法返回正整数,则表示o1大于o2;
如果返回0,表示相等;
返回负整数,表示o1小于o2。
- 3.1 说明
-
4.两种排序方式对比
- Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小。
- Comparator接口属于临时性的比较。
Java集合
Collection接口
|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储序的、可重复的数据。 -->“动态”数组
|----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
|----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;线程不安全的;底层使用双向链表存储
|----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
-
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
-
|----HashSet、LinkedHashSet、TreeSet
- Collection集合与数组间的转换
//集合 --->数组:toArray()
Object[] arr = coll.toArray();
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
//拓展:数组 --->集合:调用Arrays类的静态方法asList(T ... t)
List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(list);
List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());//1
List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2
Iterator接口与foreach循环
Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了lterator接口的对象。
-
1.遍历Collection的两种方式:
① 使用迭代器Iterator ② foreach循环(或增强for循环) -
说明:
- Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
- GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
遍历的代码实现:
Iterator iterator = coll.iterator();
//hasNext():判断是否还下一个元素
while(iterator.hasNext()){
//next():①指针下移 ②将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
图示说明:
List接口
Set接口
-
存储的数据特点:无序的、不可重复的元素
具体的:
以HashSet为例说明: -
无序性:不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的。
-
不可重复性:保证添加的元素照equals()判断时,不能返回true.即:相同的元素只能添加一个。
-
元素添加过程:(以HashSet为例)
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置,判断数组此位置上是否已经元素:
如果此位置上没其他元素,则元素a添加成功。 --->情况1
如果此位置上其他元素b(或以链表形式存在的多个元素,则比较元素a与元素b的hash值:
如果hash值不相同,则元素a添加成功。--->情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。--->情况2
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下
HashSet底层:数组+链表的结构。(前提:jdk7)
-
|----Collection接口:单列集合,用来存储一个一个的对象
-
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
-
|----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
-
|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
-
在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。 对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
-
|----TreeSet:可以照添加对象的指定属性,进行排序。
-
- 存储对象所在类的要求:
- 存储对象所在类的要求:
-
Map接口
1.常用实现类结构
[面试题]
-
- HashMap的底层实现原理?
-
- HashMap 和 Hashtable的异同?
-
- CurrentHashMap 与 Hashtable的异同?(暂时不讲)
2.存储结构的理解:
Map中的key:无序的、不可重复的,使用Set存储所的key ---> key所在的类要重写equals()和hashCode() (以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所的value --->value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所的entry
图示:
- 内存结构说明:(难点)
- 4.1 HashMap在jdk7中实现原理:
-
在实例化以后,底层创建了长度是16的一维数组Entry[] table。
-
...可能已经执行过多次put...
-
map.put(key1,value1):
-
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
-
如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
-
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
-
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
-
如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
-
如果equals()返回false:此时key1-value1添加成功。----情况3
-
如果equals()返回true:使用value1替换value2。
-
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
-
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。
-
- 4.1 HashMap在jdk7中实现原理:
泛型
IO流
- Java中的 IO/NIO/AIO/BIO
Java 中 IO 流分为⼏种?
- 按照流的流向分,可以分为输⼊流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
- 按照流的⻆⾊划分为节点流和处理流。
Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,⽽且彼此之间存在⾮常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派⽣出来的。
- InputStream/Reader: 所有的输⼊流的基类,前者是字节输⼊流,后者是字符输⼊流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
按操作⽅式分类结构图:
按操作对象分类结构图:
既然有了字节流,为什么还要有字符流?
问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
简而言之,字节是个计算机看的,字符才是给人看的
Java中的常见类使用
-
Java 的 I/O 大概可以分成以下几类:
- 磁盘操作: File
- 字节操作: InputStream 和 OutputStream
- 字符操作: Reader 和 Writer
- 对象操作: Serializable
- (不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。)
- (序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。)
- (transient 关键字可以使一些属性不会被序列化。)
- ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。
- 网络操作: Socket
-
Java 中的网络支持:
著作权归https://pdai.tech所有。
链接:https://www.pdai.tech/md/java/io/java-io-basic-usage.html- InetAddress: 用于表示网络上的硬件资源,即 IP 地址;
- URL: 统一资源定位符;
- Sockets: 使用 TCP 协议实现网络通信;
- Datagram: 使用 UDP 协议实现网络通信。
Sockets:
- ServerSocket: 服务器端类
- Socket: 客户端类
- 服务器和客户端通过 InputStream 和 OutputStream 进行输入输出
Datagram:
- DatagramSocket: 通信类
- DatagramPacket: 数据包类
BIO,NIO,AIO 有什么区别?
-
BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写⼊必须阻塞在⼀个线程内等待其完成。在活动连接数不是特别⾼(⼩于单机 1000)的情况下,这种模型是⽐较不错的,可以让每⼀个连接专注于⾃⼰的 I/O 并且编程模型简单,也不⽤过多考虑系统的过载、限流等问题。线程池本身就是⼀个天然的漏⽃,可以缓冲⼀些系统处理不了的连接或请求。但是,当⾯对⼗万甚⾄百万级连接的时候,传统的 BIO 模型是⽆能为⼒的。因此,我们需要⼀种更⾼效的 I/O 处理模型来应对更⾼的并发量
-
NIO (Non-blocking/New I/O): NIO 是⼀种同步⾮阻塞的 I/O 模型,在 Java 1.4 中引⼊了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它⽀持⾯向缓冲的,基于通道的 I/O 操作⽅法。 NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都⽀持阻塞和⾮阻塞两种模式。阻塞模式使⽤就像传统中的⽀持⼀样,⽐较简单,但是性能和可靠性都不好;⾮阻塞模式正好与之相反。对于低负载、低并发的应⽤程序,可以使⽤同步阻塞 I/O 来提升开发速率和更好的维护性;对于⾼负载、⾼并发的(⽹络)应⽤,应使⽤ NIO 的⾮阻塞模式来开发
-
AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引⼊了 NIO 的改进版 NIO 2,它是异步⾮阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应⽤操作之后会直接返回,不会堵塞在那⾥,当后台处理完成,操作系统会通知相应的线程进⾏后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在⽹络操作中,提供了⾮阻塞的⽅法,但是 NIO 的 IO ⾏为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程⾃⾏进⾏ IO 操作,IO 操作本身是同步的。查阅⽹上相关资料,我发现就⽬前来说 AIO 的应⽤还不是很⼴泛,Netty 之前也尝试使⽤过 AIO,不过⼜放弃了。
网络编程
-
IP
-
端口号
-
端口号与IP地址的组合得出一个网络套接字:Socket
TCP网络编程
代码示例1:客户端发送信息给服务端,服务端将数据显示在控制台上
//客户端
@Test
public void client() {
Socket socket = null;
OutputStream os = null;
try {
//1.创建Socket对象,指明服务器端的ip和端口号
InetAddress inet = InetAddress.getByName("192.168.14.100");
socket = new Socket(inet,8899);
//2.获取一个输出流,用于输出数据
os = socket.getOutputStream();
//3.写出数据的操作
os.write("你好,我是客户端mm".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源的关闭
if(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//服务端
@Test
public void server() {
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
//1.创建服务器端的ServerSocket,指明自己的端口号
ss = new ServerSocket(8899);
//2.调用accept()表示接收来自于客户端的socket
socket = ss.accept();
//3.获取输入流
is = socket.getInputStream();
//不建议这样写,可能会乱码
// byte[] buffer = new byte[1024];
// int len;
// while((len = is.read(buffer)) != -1){
// String str = new String(buffer,0,len);
// System.out.print(str);
// }
//4.读取输入流中的数据
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len;
while((len = is.read(buffer)) != -1){
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据");
} catch (IOException e) {
e.printStackTrace();
} finally {
if(baos != null){
//5.关闭资源
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(ss != null){
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
UDP网络编程
//发送端
@Test
public void sender() throws IOException {
DatagramSocket socket = new DatagramSocket();
String str = "我是UDP方式发送的导弹";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);
socket.send(packet);
socket.close();
}
//接收端
@Test
public void receiver() throws IOException {
DatagramSocket socket = new DatagramSocket(9090);
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
socket.close();
}
URL编程
Java反射机制 (重要)
概述
-
关于反射的理解
-
反射机制能提供的功能
通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性
Class类的理解与获取Class的实例
-
对于java.lang.Class类的理解;
1.类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为ava.lang.Class类的一个实例。
2.换句话说,Class的实例就对应着一个运行时类。
3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。 -
总结:创建类的对象的方式?
方式一:new + 构造器
方式二:要创建Xxx类的对象,可以考虑:Xxx、Xxxs、XxxFactory、XxxBuilder类中查看是否有
静态方法的存在。可以调用其静态方法,创建Xxx对象。
方式三:通过反射 -
Class实例可以是哪些结构的说明
了解ClassLoader
1.类的加载过程
2.类的加载器的作用
3.类的加载器的分类
4.Java类编译、运行的执行的流程
动态代理
1.代理模式的原理:
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
- 动态代理与AOP
Java8新特性
lambda表达式(Java8 新特性)
package com.atguigu.java1;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.function.Consumer;
/**
* Lambda表达式的使用
*
* 1.举例: (o1,o2) -> Integer.compare(o1,o2);
* 2.格式:
* -> :lambda操作符 或 箭头操作符
* ->左边:lambda形参列表 (其实就是接口中的抽象方法的形参列表)
* ->右边:lambda体 (其实就是重写的抽象方法的方法体)
*
* 3. Lambda表达式的使用:(分为6种情况介绍)
*
* 总结:
* ->左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数,其一对()也可以省略
* ->右边:lambda体应该使用一对{}包裹;如果lambda体只有一条执行语句(可能是return语句),省略这一对{}和return关键字
*
* 4.Lambda表达式的本质:作为函数式接口的实例(创建函数式接口的对象)
*
* 5. 如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface 注解,
* 这样做可以检查它是否是一个函数式接口。
*
* 6. 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
*
* @author shkstart
* @create 2019 上午 11:40
*/
public class LambdaTest1 {
//语法格式一:无参,无返回值
@Test
public void test1(){
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱北京天安门");
}
};
r1.run();
System.out.println("***********************");
Runnable r2 = () -> {
System.out.println("我爱北京故宫");
};
r2.run();
}
//语法格式二:Lambda 需要一个参数,但是没有返回值。
@Test
public void test2(){
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("谎言和誓言的区别是什么?");
System.out.println("*******************");
Consumer<String> con1 = (String s) -> {
System.out.println(s);
};
con1.accept("一个是听得人当真了,一个是说的人当真了");
}
//语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
@Test
public void test3(){
Consumer<String> con1 = (String s) -> {
System.out.println(s);
};
con1.accept("一个是听得人当真了,一个是说的人当真了");
System.out.println("*******************");
Consumer<String> con2 = (s) -> {
System.out.println(s);
};
con2.accept("一个是听得人当真了,一个是说的人当真了");
}
@Test
public void test4(){
ArrayList<String> list = new ArrayList<>();//类型推断
int[] arr = {1,2,3};//类型推断
}
//语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略
@Test
public void test5(){
Consumer<String> con1 = (s) -> {
System.out.println(s);
};
con1.accept("一个是听得人当真了,一个是说的人当真了");
System.out.println("*******************");
Consumer<String> con2 = s -> {
System.out.println(s);
};
con2.accept("一个是听得人当真了,一个是说的人当真了");
}
//语法格式五:Lambda 需要两个或以上的参数,多条执行语句, 并且可以有返回值
@Test
public void test6(){
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
System.out.println(com1.compare(12,21));
System.out.println("*****************************");
Comparator<Integer> com2 = (o1,o2) -> {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};
System.out.println(com2.compare(12,6));
}
//语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
@Test
public void test7(){
Comparator<Integer> com1 = (o1,o2) -> {
return o1.compareTo(o2);
};
System.out.println(com1.compare(12,6));
System.out.println("*****************************");
Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);
System.out.println(com2.compare(12,21));
}
@Test
public void test8(){
Consumer<String> con1 = s -> {
System.out.println(s);
};
con1.accept("一个是听得人当真了,一个是说的人当真了");
System.out.println("*****************************");
Consumer<String> con2 = s -> System.out.println(s);
con2.accept("一个是听得人当真了,一个是说的人当真了");
}
}
容器
并发
JVM
https://www.bilibili.com/video/BV1PJ411n7xZ?from=search&seid=8500405077940424822
类加载子系统
类的加载
——————————————
类加载器
引导类加载器、扩展类加载器、系统类加载器
系统类加载器
扩展类加载器
引导类加载器
用户自定义类加载器
注释:
根据所在类的路径不同,使用的类的加载器不同
双亲委派机制
原理
举例
优势
沙箱安全机制 (保护java核心源代码)
其他
运行时数据区概述及线程
概述
线程
程序计数器
虚拟机栈
介绍
栈中可能出现的异常
栈运行原理
栈帧的内部结构
静态变量与局部变量的对比
面试题举例
本地方法接口
本地方法栈
堆
1.概述
2.设置堆内存大小与OOM
整个堆内存分为"新生代+老年代",其中"新生代"又分为"S0+S1",S0和S1只有一份能用,另一份空闲用于复制
3.年轻代和老年代
-Xms600m -Xmx600m :设置堆空间初始化空间和最大空间
-XX:NewRatio : 设置新生代与老年代的比例。默认值是1:2.
-XX:SurvivorRatio :设置新生代中Eden区与Survivor区的比例。默认值是8:1: 1
-XX:-UseAdaptiveSizePolicy :关闭使用自适应的内存分配策略 (暂时用不到)
-XX:+UseAdaptiveSizePolicy :使用自适应的内存分配策略(新生区:6:1:1;新生区:老年区:1:2)
-Xmn:设置新生代的空间的大小。 (一般不设置)
4.图解对象分配过程
概述
总结
伊甸园区满了会带着幸存者区被动触发GC,幸存者区满了不会主动触发GC,会自动晋升老年区
过程图示
OOM举例
public class HeapInstanceTest {
byte[] buffer = new byte[new Random().nextInt(1024 * 200)];
public static void main(String[] args) {
ArrayList<HeapInstanceTest> list = new ArrayList<HeapInstanceTest>();
while (true) {
list.add(new HeapInstanceTest());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
常见调优工具
5.Minor GC/Major GC/Full GC
概述
年轻代GC策略触发机制
老年代GC策略触发机制
Full GC策略触发机制
6.堆空间分代思想
7.内存分配策略
8.为对象分配内存:TLAB
图解对象分配过程:TLAB
9.小结堆空间的参数设置
/**
* 测试堆空间常用的jvm参数:
* -XX:+PrintFlagsInitial : 查看所有的参数的默认初始值
* -XX:+PrintFlagsFinal :查看所有的参数的最终值(可能会存在修改,不再是初始值)
* 具体查看某个参数的指令: jps:查看当前运行中的进程
* jinfo -flag SurvivorRatio 进程id
*
* -Xms:初始堆空间内存 (默认为物理内存的1/64)
* -Xmx:最大堆空间内存(默认为物理内存的1/4)
* -Xmn:设置新生代的大小。(初始值及最大值)
* -XX:NewRatio:配置新生代与老年代在堆结构的占比
* -XX:SurvivorRatio:设置新生代中Eden和S0/S1空间的比例
* -XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄
* -XX:+PrintGCDetails:输出详细的GC处理日志
* 打印gc简要信息:① -XX:+PrintGC ② -verbose:gc
* -XX:HandlePromotionFailure:是否设置空间分配担保
*
* @author shkstart shkstart@126.com
* @create 2020 17:18
*/
10.堆是分配对象存储的唯一选择吗?否
逃逸分析概述
/**
* 逃逸分析
*
* 如何快速的判断是否发生了逃逸分析,大家就看new的对象实体是否有可能在方法外被调用。
* @author shkstart
* @create 2020 下午 4:00
*/
public class EscapeAnalysis {
public EscapeAnalysis obj;
/*
方法返回EscapeAnalysis对象,发生逃逸
*/
public EscapeAnalysis getInstance(){
return obj == null? new EscapeAnalysis() : obj;
}
/*
为成员属性赋值,发生逃逸
*/
public void setObj(){
this.obj = new EscapeAnalysis();
}
//思考:如果当前的obj引用声明为static的?仍然会发生逃逸。
/*
对象的作用域仅在当前方法中有效,没有发生逃逸
*/
public void useEscapeAnalysis(){
EscapeAnalysis e = new EscapeAnalysis();
}
/*
引用成员变量的值,发生逃逸
*/
public void useEscapeAnalysis1(){
EscapeAnalysis e = getInstance();
//getInstance().xxx()同样会发生逃逸
}
}
结论:
1.如何快速的判断是否发生了逃逸分析,大家就看new的对象实体是否有可能在方法外被调用。
2.开发中能使用局部变量的,就不要在方法外定义。
11.小结
方法区
1.栈、堆、方法区的交互关系
2.方法区的理解
方法区概述
3.设置方法区大小及OOM
JDK7以前
JDK8及以后
OOM举例:创建的类太多,超出元空间最大内存
package com.atguigu.java;
import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
/**
* jdk6/7中:
* -XX:PermSize=10m -XX:MaxPermSize=10m
*
* jdk8中:
* -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
*
* @author shkstart shkstart@126.com
* @create 2020 22:24
*/
public class OOMTest extends ClassLoader {
public static void main(String[] args) {
int j = 0;
try {
OOMTest test = new OOMTest();
for (int i = 0; i < 10000; i++) {
//创建ClassWriter对象,用于生成类的二进制字节码
ClassWriter classWriter = new ClassWriter(0);
//指明版本号,修饰符,类名,包名,父类,接口
classWriter.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
//返回byte[]
byte[] code = classWriter.toByteArray();
//类的加载
test.defineClass("Class" + i, code, 0, code.length);//Class对象
j++;
}
} finally {
System.out.println(j);
}
}
}
如何解决OOM
4.方法区的内部结构
类型信息
域信息
方法信息
non-final的类变量(static)
全局常量:static final
运行时常量池与常量池
常量池中有什么?
小结
运行时常量池
5.方法区使用举例
6.方法区的演进
永久代为什么被原空间替代
StringTable为什么要调整
静态变量在哪
静态引用对应的对象实体始终都存在堆空间 (JDK6/7/8及以后)
静态引用对应的变量名JDK6在永久代,JDK7及以后在堆空间
7.方法区的垃圾回收
对象的实例化、内存布局与访问定位
https://www.processon.com/view/link/5eea141cf346fb1ae56a44e7
1.对象的实例化
创建对象的方式
创建对象的步骤
2.对象的内存布局
小结
3.对象的访问定位
JVM是如何通过栈帧中的对象引用访问到其内部的对象实例的
建立对象就是为了使用对象,我们的Java程序通过栈上的reference数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前的主流访问方式有两种:①使用句柄和②直接指针
HotSpot采用的是直接指针
优缺点对比
直接内存
概述
执行引擎
1.概述
2.Java代码编译和执行过程
3.机器码、指令、汇编语言
4.解释器
5.JIT编译器
总结
String Table
1.String的基本特性
String存储结构变更
String的基本特性
2.String的内存分配
3.String的基本操作
4.S字符串拼接操作
拼接"+" 与 "append"
每次拼接都会创建StringBulider和String
append更快
/*
体会执行效率:通过StringBuilder的append()的方式添加字符串的效率要远高于使用String的字符串拼接方式!
详情:① StringBuilder的append()的方式:自始至终中只创建过一个StringBuilder的对象
使用String的字符串拼接方式:创建过多个StringBuilder和String的对象
② 使用String的字符串拼接方式:内存中由于创建了较多的StringBuilder和String的对象,内存占用更大;如果进行GC,需要花费额外的时间。
改进的空间:在实际开发中,如果基本确定要前前后后添加的字符串长度不高于某个限定值highLevel的情况下,建议使用构造器实例化:
StringBuilder s = new StringBuilder(highLevel);//new char[highLevel]
*/
5.intern()的使用
intern()的使用:jdk6 vs jdk7/8
new String("ab")会创建几个对象?
/**
* 题目:
* new String("ab")会创建几个对象?看字节码,就知道是两个。
* 一个对象是:new关键字在堆空间创建的
* 另一个对象是:字符串常量池中的对象"ab"。字节码指令:ldc
*
*
* 思考:
* new String("a") + new String("b")呢?
* 对象1:new StringBuilder()
* 对象2: new String("a")
* 对象3: 常量池中的"a"
* 对象4: new String("b")
* 对象5: 常量池中的"b"
*
* 深入剖析: StringBuilder的toString():
* 对象6 :new String("ab")
* 强调一下,toString()的调用,在字符串常量池中,没有生成"ab"
*
* @author shkstart shkstart@126.com
* @create 2020 20:38
*/
关于intern()问题
package com.atguigu.java2;
import org.junit.Test;
/**
* 如何保证变量s指向的是字符串常量池中的数据呢?
* 有两种方式:
* 方式一: String s = "shkstart";//字面量定义的方式
* 方式二: 调用intern()
* String s = new String("shkstart").intern();
* String s = new StringBuilder("shkstart").toString().intern();
*
* @author shkstart shkstart@126.com
* @create 2020 18:49
*/
public class StringIntern {
public static void main(String[] args) {
String s = new String("1");
s.intern();//调用此方法之前,字符串常量池中已经存在了"1"
String s2 = "1";
System.out.println(s == s2);//jdk6:false jdk7/8:false
String s3 = new String("1") + new String("1");//s3变量记录的地址为:new String("11")
//执行完上一行代码以后,字符串常量池中,是否存在"11"呢?答案:不存在!!
s3.intern();//在字符串常量池中生成"11"。如何理解:jdk6:创建了一个新的对象"11",也就有新的地址。
// jdk7:此时常量中并没有创建"11",而是创建一个指向堆空间中new String("11")的地址
String s4 = "11";//s4变量记录的地址:使用的是上一行代码代码执行时,在常量池中生成的"11"的地址
System.out.println(s3 == s4);//jdk6:false jdk7/8:true
}
}
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
// 强调一下,toString()的调用,在字符串常量池中,没有生成"ab"
public class StringIntern1 {
public static void main(String[] args) {
//StringIntern.java中练习的拓展:
String s3 = new String("1") + new String("1");//new String("11")
//执行完上一行代码以后,字符串常量池中,是否存在"11"呢?答案:不存在!!
String s4 = "11";//在字符串常量池中生成对象"11"
String s5 = s3.intern();
System.out.println(s3 == s4);//false
System.out.println(s5 == s4);//true
}
}
使用intern()测试执行效率
package com.atguigu.java2;
import java.util.Random;
/**
* 使用intern()测试执行效率:空间使用上
*
* 结论:对于程序中大量存在存在的字符串,尤其其中存在很多重复字符串时,使用intern()可以明显节省内存空间。
* 因为直接new string,会造多个重复对象;而intern(),指向的是常量池中的对象,之前重复造的对象会被回收
*
* @author shkstart shkstart@126.com
* @create 2020 21:17
*/
public class StringIntern2 {
static final int MAX_COUNT = 1000 * 10000;
static final String[] arr = new String[MAX_COUNT];
public static void main(String[] args) {
Integer[] data = new Integer[]{1,2,3,4,5,6,7,8,9,10};
long start = System.currentTimeMillis();
for (int i = 0; i < MAX_COUNT; i++) {
arr[i] = new String(String.valueOf(data[i % data.length]));
// arr[i] = new String(String.valueOf(data[i % data.length])).intern();
}
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.gc();
}
}
6.String Table的垃圾回收
package com.atguigu.java3;
/**
* String的垃圾回收:
* -Xms15m -Xmx15m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails
*
* @author shkstart shkstart@126.com
* @create 2020 21:27
*/
public class StringGCTest {
public static void main(String[] args) {
for (int j = 0; j < 100000; j++) {
String.valueOf(j).intern();
}
}
}
7.G1的String去重操作
堆上去重,而非字符串常量池
垃圾回收概述
1.什么是垃圾
2.为什么需要GC
3.早期垃圾回收
4.Java垃圾回收机制
垃圾回收相关算法
1.标记阶段:引用计数算法
uploading-image-442517.png
2.标记阶段:可达性分析算法
3.对象的finalization机制
4.MAT与JProfiler的GC Roots溯源
5.清除阶段:标记—清除算法
6.清除阶段:复制算法
7.清除阶段:标记—压缩算法
8.小结
9.分代收集算法
X.增量收集算法、分区算法
垃圾回收相关概念
垃圾回收器
计算机基础
网络
通讯例程说明
利用 TCP/IP 协议族进行网络通信时,会通过分层顺序与对方进行通信。发送端从应用层往下走,接收端则往应用层往上走。
我们用 HTTP 举例来说明,首先作为发送端的客户端在应用层(HTTP 协议)发出一个想看某个 Web 页面的 HTTP 请求。
接着,为了传输方便,在传输层(TCP 协议)把从应用层处收到的数据(HTTP 请求报文)进行分割,并在各个报文上打上标记序号及端口号后转发给网络层。
在网络层(IP 协议),增加作为通信目的地的 MAC 地址后转发给链路层。这样一来,发往网络的通信请求就准备齐全了。
接收端的服务器在链路层接收到数据,按序往上层发送,一直到应用层。当传输到应用层,才能算真正接收到由客户端发送过来的 HTTP请求。
发送端在层与层之间传输数据时,每经过一层时必定会被打上一个该层所属的首部信息。反之,接收端在层与层传输数据时,每经过一层时会把对应的首部消去。
这种把数据信息包装起来的做法称为封装(encapsulate)。
运输层
UDP概述
TCP概述
TCP的连接
TCP 协议如何保证可靠传输
打开一个网页的过程
https://blog.csdn.net/ailunlee/article/details/90600174/
关于TCP三次握手和四次挥手
TCP 三次握⼿漫画图解
- 客户端–发送带有 SYN 标志的数据包–⼀次握⼿–服务端
- 服务端–发送带有 SYN/ACK 标志的数据包–⼆次握⼿–客户端
- 客户端–发送带有带有 ACK 标志的数据包–三次握⼿–服务端
为什么要三次握⼿
为什么要传回 SYN
接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。
SYN 是 TCP/IP 建⽴连接时使⽤的握⼿信号。在客户机和服务器之间建⽴正常的 TCP ⽹络连接时,客户机⾸先发出⼀个 SYN 消息,服务器使⽤ SYN-ACK 应答表示接收到了这个消息,最后客户机再以ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的⼀种传输控制字符。它表示确认发来的数据已经接受⽆误。 ])消息响应。这样在客户机和服务器之间才能建⽴起可靠的TCP连接,数据才可以在客户机和服务器之间传递。
传了 SYN,为啥还要传 ACK
双⽅通信⽆误必须是两者互相发送信息都⽆误。传了 SYN,证明发送⽅到接收⽅的通道没有问题,但是接收⽅到发送⽅的通道还需要 ACK 信号来进⾏验证。
TCP断开连接
-
断开⼀个 TCP 连接则需要“四次挥⼿”:
- 客户端-发送⼀个 FIN,⽤来关闭客户端到服务器的数据传送
- 服务器-收到这个 FIN,它发回⼀ 个 ACK,确认序号为收到的序号加1 。和 SYN ⼀样,⼀个 FIN 将占⽤⼀个序号
- 服务器-关闭与客户端的连接,发送⼀个FIN给客户端
- 客户端-发回 ACK 报⽂确认,并将确认序号设置为收到序号加1
-
为什么要四次挥⼿
任何⼀⽅都可以在数据传送结束后发出连接释放的通知,待对⽅确认后进⼊半关闭状态。当另⼀⽅也没有数据再发送的时候,则发出连接释放通知,对⽅确认后就完全关闭了TCP连接。
举个例⼦:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着⾃⼰的节奏结束通话,于是 B 可能⼜巴拉巴拉说了⼀通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。
上⾯讲的⽐较概括,推荐⼀篇讲的⽐较细致的⽂章:https://blog.csdn.net/qzcsu/article/details/72861891
HTTP协议 与 HTTPS协议
HTTP 和 HTTPS 的区别
- 端⼝:HTTP的URL由“http://”起始且默认使⽤端⼝80,⽽HTTPS的URL由“https://”起始且默认使⽤端⼝443。
- 安全性和资源消耗:HTTP协议运⾏在TCP之上,所有传输的内容都是明⽂,客户端和服务器端都⽆法验证对⽅的身份。HTTPS是运⾏在SSL/TLS之上的HTTP协议,SSL/TLS 运⾏在TCP之上。所有传输的内容都经过加密,加密采⽤对称加密,但对称加密的密钥⽤服务器⽅的证书进⾏了⾮对称加密。所以说,HTTP 安全性没有 HTTPS⾼,但是 HTTPS ⽐HTTP耗费更多服务器资源。
- 对称加密:密钥只有⼀个,加密解密为同⼀个密码,且加解密速度快,典型的对称加密算法有DES、AES等;
- ⾮对称加密:密钥成对出现(且根据公钥⽆法推知私钥,根据私钥也⽆法推知公钥),加密解密使⽤不同密钥(公钥加密需要私钥解密,私钥加密需要公钥解密),相对对称加密速度较慢,典型的⾮对称加密算法有RSA、DSA等。
HTTP 的缺点
- 通信使用明文(不加密),内容可能会被窃听
- 不验证通信方的身份,因此有可能遭遇伪装
- 无法证明报文的完整性,所以有可能已遭篡改
HTTP+ 加密 + 认证 + 完整性保护 = HTTPS
- HTTPS 是身披 SSL 外壳的 HTTP
- HTTPS 并非是应用层的一种新协议。只是 HTTP 通信接口部分用SSL(Secure Socket Layer)和 TLS(Transport Layer Security)协议代替而已。
- 通常,HTTP 直接和 TCP 通信。当使用 SSL 时,则演变成先和 SSL 通信,再由 SSL 和 TCP 通信了。简言之,所谓 HTTPS,其实就是身披SSL 协议这层外壳的 HTTP。
- 在采用 SSL 后,HTTP 就拥有了 HTTPS 的加密、证书和完整性保护这些功能。
- SSL 是独立于 HTTP 的协议,所以不光是 HTTP 协议,其他运行在应用层的 SMTP 和 Telnet 等协议均可配合 SSL 协议使用。可以说 SSL 是当今世界上应用最为广泛的网络安全技术。
操作系统
https://wizardforcel.gitbooks.io/wangdaokaoyan-os/content/13.html
操作系统概述
操作系统的概念、特征、功能和结构
-
操作系统的概念
- 计算机系统自下而上可粗分为四个部分:硬件、操作系统、应用程序和用户。操作系统管理各种计算机硬件,为应用程序提供基础,并充当计算机硬件和用户的中介。
- 操作系统是指控制和管理整个计算机系统的硬件和软件资源,并合理的组织调度计算机的工作和资源的分配,以提供给用户和其他软件方便的接口和环境集合。
-
操作系统的基本特征包括并发、共享、虚拟和异步。
- 并发:操作系统的并发性是指计算机系统中同时存在多个运行着的程序,因此它具有处理和调度多个程序同时执行的能力。在操作系统中,引入进程的目的实施程序能并发执行。
- 共享:资源共享即共享,是指系统中的资源可供内存中多个并发执行的进程共同使用。共享可以分为以下两种资源共享方式。
- 1)互斥共享方式
- 系统中的某些资源,,如打印机、磁带机,虽然他们可以提供给多个进程使用,但为使所打印的内容不致造成混淆,应规定在同一段时间内只允许一个进程方位该资源。
- 2)同时访问方式
- 系统中还有一种资源,允许在一段时间内由多个进程“同时”对它进行访问。这里所谓的“同时”往往是宏观上的,而在微观上,这些进程可能是交替的对该资源进行访问即“分时共享”。典型的可供多个进程同时访问的资源是磁盘设备,一些用重入码编写的文件也可以被“同时”共享,即若干个用户同时访问该文件。
- 并发和共享是操作系统两个最基本的特征,这两者之间又是互为存在条件的:
- 1资源共享是以程序的并发为条件的,若系统不允许程序并发执行,则自然不存在资源共享的问题;
- 2若系统不能对资源共享实施有效地管理,也必将影响到程序的并发执行,甚至根本无法并发执行。
- 虚拟
- 在操作系统中利用了多种虚拟技术,分别用来实现虚拟处理器、虚拟内存和虚拟外部设备。
- 操作系统的虚拟技术可归纳为:时分复用技术和空分复用技术。
- 异步
- 在多道程序环境下,允许多个程序并发执行,但由于资源有限,进程的执行不是一贯到底,而是走走停停,以不可预知的速度向前推进,这就是进程的异步性。
- 异步性使得操作系统运行在一种随机的环境下,可能导致进程产生于时间有关的错误。但是只要运行环境相同,操作系统必须保证多次运行进程,都获得相同的结果。
操作系统的运行环境
- 计算机系统中,通常CPU执行两种不同性质的程序,一种是操作系统内核程序;另一种是用户自编程序或系统外城的应用程序。对操作系统而言,这两种程序的作用不同,前者是后者的管理者和控制者,因此“管理程序”要执行一些特权指令,而“被管理程序”出于安全性考虑,不能执行这些指令。所谓特权指令,是指计算集中不允许用户直接使用的指令,如IO指令、置中断指令。
- 操作系统在具体实现上划分了用户态和核心态,以严格区分两种类程序。
进程管理
进程与线程
-
进程的概念
- 1)进程是程序的一次执行过程 2)进程是一个程序及其数据在处理器上顺序执行时所发生的活动。
-
进程有哪些状态
-
进程之间的通信方式
https://www.jianshu.com/p/c1015f5ffa74
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)
-
进程与线程之间的比较
进程的调度
-
调度的基本准则
- CPU利用率:
- CPU是计算机系统中最重要的资源之一,所以应尽可能使CPU保持在忙状态,是这一资源利用率最高。
- 系统吞吐量:
- 系统吞吐量表示单位时间内CPU完成作业的数量。长作业需要消耗较长的处理器时间,因此会降低系统的吞吐量。而对于短作业,他们所需要消耗的处理器时间端,因此能提高系统的吞吐量。调度算法和方式的不同,也会对系统的吞吐量产生较大的影响。
- 周转时间
- 周转时间是指从作业提交到作业完成所经历的时间,包括作业等待、在就绪队列中排队、在处理器上运行以及进行输入输出操作所花费的时间的总和。 作业的周转时间=作业完成时间-作业提交时间
- 等待时间
- 等待时间是指进程处于等处理器状态时间之和,等待时间越长,用户满意度越低。处理器调度算法实际上并不影响作业执行或输入输出操作时间,只影响作业在就绪队列中等待所花的时间。因此,衡量一个调度算法优劣常常只需简单地考察等待时间。
- 响应时间
- 响应时间是指从用户提交请求到系统首次产生响应所有的时间。在交互式系统中,周转时间不可能是最好的评测准则,一般采用响应时间作为衡量调度算法的重要准则之一。从用户的角度来看,调度策略应尽量降低响应时间,使响应时间处在用户能够接受的范围之内。
- CPU利用率:
-
典型的调度算法
- 为了确定⾸先执⾏哪个进程以及最后执⾏哪个进程以实现最⼤ CPU 利⽤率,计算机科学家已经定义了⼀些算法
- 为了确定⾸先执⾏哪个进程以及最后执⾏哪个进程以实现最⼤ CPU 利⽤率,计算机科学家已经定义了⼀些算法
线程同步
死锁
数据结构
算法
数据库
MySQL
Redis
系统设计
常用框架
Spring
SpringMVC
SpringBoot
MyBatis
JPA
Netty
认证授权
Cookie、Session
JWT
SSO
OAuth2
分布式
Elasticsearch(分布式搜索引擎)
RPC
消息队列
https://www.cnblogs.com/Java3y/p/10695609.html
https://www.cnblogs.com/Terry-Wu/p/7644279.html
https://www.cnblogs.com/linjiqin/p/5720865.html
概述
当不需要立即获得结果,但是并发量又需要进行控制的时候,差不多就是需要使用消息队列的时候。
消息队列可以简单理解为:把要传输的数据放在队列中。
消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。目前使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ
API网关
数据库扩展:分库分表、读写分离
分布式id
分布式接口幂等性
分布式限流
微服务
SpringCloud
其他
项目:遇到的问题及技术
手撕算法
源码分析
场景题设计
.