JavaSE学习笔记

一. JAVA基础语法

1. 注释

  • 单行注释: //
  • 多行注释: /* */
  • 文档注释: /** */

2. 字面量

分为整数类型、小数类型、字符串类型、字符类型、布尔类型和空类型

  • 特殊字符
    • '\t': 制表符,把前面字符串的长度补齐到8或者8的整数倍,让数据对齐
    • '\r':回车符
    • '\n':换行符

3. 变量

数据类型 变量名 = 数据值;

4. 计算机存储规则

任意数据都是用二进制的形式存储的

5. 数据类型

A. 基本数据类型

数据类型 关键字
整数 byte
short
int
long
浮点数 float
double
字符 char
布尔 boolean

B. 引用数据类型

6. 标识符

自己给变量、类等起名字

  • 由数字、字母、下划线和美元符($)组成
  • 小驼峰命名(方法、变量)
    1. 标识符为一个单词,全部小写:name
    2. 标识符为多个单词组成,第一个单词首字母小写,其他单词首字母大写:firstName
  • 大驼峰命名(类名)
    1. 标识符为一个单词,首字母大写:Student
    2. 标识符由多个单词组成,每个单词首字母大写:GoodStudent

7. 键盘录入

Scanner类接收键盘输入的数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1.导包
import java.util.Scanner;

public class ScannerTest{
public static void main (String[] args) {
//2.创建对象
Scanner sc = new Scanner(System.in);
System.out.println("请输入第一个数字");
//3.接收数据
int number1 = sc.nextInt();
System.out.println("请输入第二个数字”);
int number2 = sc.nextInt() ;

System.out.println(number1 + number2);
}
}

8. 运算符

A. 算术运算符

+、-、*、/、%(取模)

  • 类型转换
    1. 隐式转换:把取值范围小的数值自动转成取值范围大的数据(byte=>short=>int=>long=>float=>double)
    2. 强制转换:目标数据类型 变量名 = (目标数据类型)被强转的数据;
      例:
1
2
double a = 12.3
int b = (int)a; //强制转换
  • 字符串相加
    “+”: 100 + “100” //“100100”
  • 字符相加:会把字符通过ASCII码表查询到相应数字再进行计算

B. 自增自减运算符

++、–

C. 赋值运算符

=、+=、-=、*=、/=、%=

D. 关系运算符

==,!=,>,>=,<,<=

E. 逻辑运算符

&(与),|(或),^(异或),!(非)

  • 短路逻辑运算符(如果左边能确定整个表达式的结果,右边不执行)
    1. &&(短路与)
    2. ||(短路或)

F. 三元运算符

关系表达式?表达式1:表达式2;

G. 运算符优先级

()优先于所有

9. 流程控制语句

A. 顺序结构

代码按顺序依次执行

B. 分支结构

1. if语句

格式一:

1
2
3
if(关系表达式){
语句体;
}

格式二:

1
2
3
4
5
if(关系表达式){
语句体1;
} else{
语句体2;
}

格式三:

1
2
3
4
5
6
7
8
9
if(关系表达式1){
语句体1;
} else if(关系表达式2){
语句体2;
}
...
else{
语句体n;
}
2. switch语句

格式:

1
2
3
4
5
6
7
8
9
10
11
12
switch(表达式){
case1:
语句体1;
break;
case2:
语句体2;
break;
...
default:
语句体n;
break;
}
  • default可以省略,语法不会有问题,但不建议省略
  • default习惯写在最下面
  • case穿透:语句中没有写break,程序会一直执行

C. 循环结构

1. for循环

格式:

1
2
3
for(初始化语句;条件判断语句;条件控制语句){
循环体语句;
}
  • 键盘录入两个数字,表示一个范围,统计这个范围中,既能被3整除,又能被5整除数字有多少个。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Scanner;

public class addNum {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入两个整数:");
int num1 = sc.nextInt();
int num2 = sc.nextInt();
int count = 0;
for (int i = num1; i <= num2; i++) {
if (i % 3 == 0 && i % 5 == 0) {
count++;
System.out.println(i + ",");
}
}
System.out.println("这样的数字有" + count + "个");
}
}
2. while循环

格式:

1
2
3
4
5
6
初始化语句;
while(条件判断语句){
循环体语句;
条件控制语句;
}
循环下面的其他语句
3. 无限循环
1
2
3
for(;;){
System.out.println("一直循环");
}
1
2
3
while(true)){
System.out.println("一直循环");
}
4. 循环跳转控制语句
  • continue: 结束本次循环,继续下次循环
  • break: 结束整个循环

10. 数组

A. 数组的介绍

数组是一种容器,可以存储同类数据类型的多个值。

B. 数组的定义和初始化

1. 定义
1
2
数据类型 [] 数组名;
int [] array;
2. 初始化
  • 静态初始化
1
2
3
4
5
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,....};
int[] array = new int[]{1,2,3};
// 简化格式
数据类型[] 数组名 = {元素1,元素2,...};
int[] array = {1,2,3};
  • 动态初始化
1
2
数据类型[] 数组名 = new 数据类型[数组长度];
int[] array = new int[3];

3. 数组元素访问

1
2
3
int[] array = {1,2,3,4,5};
int num = arr[0];//1
System.out.println(arr[1]);//2

4. 数组的遍历

用循环获取数组变量

1
2
3
4
int[] array = {1,2,3,4,5};
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}

5. 数组的内存图

JAVA内存分配
  • 栈: 方法运行时使用的内存,比如main方法运行进入方法栈执行
  • 堆: 存储对象或数组,new来创建,存储在堆内存
  • 方法区: 存储可以运行的class文件
  • 寄存器: 给CPU使用

11. 方法

A. 什么是方法

方法是程序运行中最小的执行单元

B. 方法的定义格式和调用

1
2
3
4
5
6
7
//定义
public static 返回值类型 方法名(参数){
方法体;
return 返回值;
}
//调用
方法名();

例:

1
2
3
4
5
6
7
8
9
10
public class MethodDemo{
public static void main(String[] args){
getSum(10,20); //调用
}
//定义
public static void getSum(int num1, int num2){
int result = num1 + num2;
System.out.println(result);
}
}

C. 方法重载

在同一个类中,方法名相同,参数不同的方法。不看返回值类型。

  • 参数不同:个数不同、类型不同、顺序不同

12. 二维数组

  • 二维数组的静态初始化
1
2
3
4
5
数据类型[][] 数组名 = new 数据类型[][]{{元素1,元素2},{元素1,元素2}};
int[][] array = new int[][]{{1,2,3},{1,2,3}};
// 简化格式
数据类型[][] 数组名 = {{元素1,元素2},{元素1,元素2}};
int[][] array = {{1,2,3},{1,2,3}};
  • 二维数组的动态初始化
1
2
数据类型[][] 数组名 = new 数据类型[m][n];
int[][] array = new int[m][n];
  • 遍历二维数组
1
2
3
4
5
6
for(int i = 0; i< arr.length; i++){
for(int j =0; j < arr[i].length; j++){
System.out.print(arr[i][j] + " ");
}
System.out.println();
}

二. JAVA进阶

1. 面向对象

A. 设计对象并使用

1. 类和对象
  • 类:是对象共同特征的描述
  • 对象:真实存在的具体东西
  • 必须先设计类,才能获得对象
2. 定义类
1
2
3
4
5
6
7
8
9
10
11
12
13
public class 类名{
1、成员变量(代表属性,一般是名词)
2、成员方法(代表行为,一般是动词)
3、构造器
4、代码块
5、内部类
}

// 获取类的对象
类名 对象名 = new 类名();

对象名.成员变量//访问属性
对象名.方法名(...)//访问行为

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Phone{
//属性(成员变量)
String brand;
double price;
//行为(方法)
public void call(){

}
public void playGame(){

}
}
// 获取类的对象
Phone p = new Phone();
3. 注意事项
  • Javabean类:用来描述一类事物的类,是不写main方法的
  • 测试类:编写main方法的类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//javabean类
public class GirlFriend {
String name;
int age;
String gender;

public void sleep() {
System.out.println("睡觉觉...");
}

public void shopping() {
System.out.println("逛商场ing");
}

}

//测试类
public class GirlfriendTest {
public static void main(String[] args) {
GirlFriend gf1 = new GirlFriend();
gf1.name = "小李子";
gf1.age = 18;
gf1.gender = "female";
System.out.println(gf1.name);
System.out.println(gf1.age);
System.out.println(gf1.gender);

gf1.sleep();
gf1.shopping();
}
}

B. 封装

对象代表什么,就得封装对应的数据,并提供数据对应的行为

C. private关键字(保证数据的安全性)

  • 是一个权限修饰符
  • 可以修饰成员(成员变量和成员方法)
  • 被private修饰的成员只能在本类中才能访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//确保age成员变量的值是正确范围内的
public class GirlFriend {
private int age;

public void setAge(int a) {
if (a >= 18 && a <= 50) {
age = a;
} else {
System.out.println("非法数据");
}
}

public int getAge() {
return age;
}
}

D. this关键字

区别成员变量和局部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
public class GirlFriend {
private int age; //成员变量age

public void method(){
int age = 10; //局部变量age
System.out.println(this.age); //如果只打印age会触发就近原则打印局部变量age;使用this.age才会打印成员变量age
}
}

//使用场景
public void setName(String name){
this.name = name; //给成员变量age赋值
}

E. 构造方法

在创建对象的时候给成员变量进行赋值的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Student{
修饰符 类名(参数){
方法体;
}
}

//例子
public class Student{
private String name;
private int age;

public Student(){
//空参构造,系统默认会有
}

public Student(String name, int age){
//带参构造
this.name = name;
this.age = age;
}
}

Student s = new Student("小李",20); //调用带参构造进行赋值
  • 方法名与类名相同
  • 没有返回值类型,没有返回值
  • 无论是否使用,都要写上空参构造和带参构造

F. 标准JavaBean

  • 类名见名知意
  • 成员变量使用private修饰
  • 提供两种构造方法(无参的和带全部参)
  • 成员方法:提供每个成员变量对应的setXX()和getXX()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class User {
private String username;
private String password;
private int age;

public User() { //空参构造方法
}

public User(String username, String password, int age) { //带全部参构造方法
this.username = username;
this.password = password;
this.age = age;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

G. 基本数据类型和引用数据类型

  • 基本数据类型(如 int、double、char 等)直接存储在栈内存中,它们的值是实际的数据。
  • 引用数据类型(如类、数组等)在栈内存中存储的是对象的引用(存储的是地址值),而对象本身存储在堆内存中。

2. 字符串

A. String

java.lang.String类

  • 字符串的内容是不会发生改变的,它的对象在创建后不能被更改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//1.使用直接赋值的方式获取一个字符串对象
String s1 = "abc";
System.out.println(s1);//abc

//2.使用new的方式来获取一个字符串对象. 空参构造:可以获取一个空白的字符串对象
String s2 = new String();
System.out.println("@" + s2 + "!");//""

//3. 传递一个字符数组,根据字符数组的内容再创建一个新的字符串对象
char[] chs = {'a','b' ,'c','d'};
String s4 = new String(chs);
System.out.println(s4);//abcd

//4.传递一个字节数组,根据字节数组的内容再创建一个新的字符串对象
byte[] bytes = {97, 98, 99, 100};
String s5 = new String(bytes);
System.out.println(s5);//abcd

B. 字符串常用方法

1. 字符串比较
  • equals(要比较的字符串): 完全一样才是true
  • equalsIgnoreCase(要比较的字符串): 忽略大小写
2. 遍历字符串
  • charAt(int index): 根据索引返回字符
  • length(): 返回此字符串的长度

键盘录入一个字符串,统计该字符串中大写字母字符,小写字母字符和数字字符出现的次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串");
String str = sc.next();

int bigCount = 0;
int smallCount = 0;
int numberCount = 0;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c >= 'a' && c <= 'z') {
smallCount++;
} else if (c >= 'A' && c <= 'Z') {
bigCount++;
} else if (c >= '0' && c <= '9') {
numberCount++;
}
}

System.out.println("小写字母有:" + smallCount + "个");
System.out.println("大写字母有:" + bigCount + "个");
System.out.println("数字字母有:" + numberCount + "个");
3. 字符串屏蔽
  • substring(int beginIndex, int endIndex): 截取,截取范围是[beginIndex, endIndex)
  • substring(int beginIndex): 截取到末尾
  • replace(旧值,新值): 替换

手机号码屏蔽

1
2
3
4
5
6
7
8
String phoneNumber = "13112349468";
//截取手机号码前面三位
String start = phoneNumber.substring(0, 3);
//截取手机号码后面四位
String end = phoneNumber.substring(7);
//拼接
String result = start + " **** " + end;
System.out.println(result);

敏感词屏蔽

1
2
3
4
5
6
7
8
String talk="你玩的真好,以后不要再玩了,TMD,CNM";
//定义一个敏感词库
String[] arr = {"TMD", "CNM", "SB", "MLGB"};
//循环得到数组中的每一个敏感词,依次进行替换
for (int i = 0; i < arr.length; i++) {
talk = talk.replace(arr[i], "***");
}
System.out.println(talk);

C. StringBuilder

可以看成一个容器,创建之后里面的内容是可变的

  • 用于提高字符串的操作效率
  • 使用场景:1.字符串的拼接 2.字符串的反转
1. StringBuilder构造方法
方法名 说明
public StringBuilder() 创建一个空白可变字符串对象,不含有任何内容
public StringBuilder(String str) 根据字符串的内容,创建可变字符串对象
2. StringBuilder常用方法
方法名 说明
public StringBuilder append(任意类型) 添加数据,返回对象本身
public StringBuilder reverse() 反转容器中的内容
public int length() 返回长度
public String toString 通过toString()可以实现把Stringbuilder转换为String
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class StringStudy {
public static void main(String[] args) {
//创建对象
StringBuilder sb = new StringBuilder("abc");
//添加
sb.append("123");
sb.append("abc");
sb.append("scasc").append("qwer").append("zxcv");//链式编程
System.out.println(sb);
//反转
sb.reverse();
System.out.println(sb);
//获取长度
int len = sb.length();
System.out.println(len);
//把StringBuilder变回String类型
String str = sb.toString();
System.out.println(str);
}
}

D. StringJoiner

主要用于字符串的拼接

1. StringJoiner构造方法
方法名 说明
public StringJoiner(间隔符号) 创建一个StringJoiner对象,指定拼接时的间隔符号
public StringJoiner(间隔符号,开始符号,结束符号) 创建一个StringJoiner对象,指定拼接时的间隔符号、开始符号、结束符号
2. StringJoiner成员方法
方法名 说明
public StringJoiner add(添加的内容) 添加数据,并返回对象本身
public int length() 返回长度
public String toString() 返回一个字符串
1
2
3
4
5
6
7
8
9
StringJoiner sj = new StringJoiner(", ", "[", "]");
//添加元素
sj.add("aaa").add("bbb").add("ccc");

int len = sj.length();
System.out.println(len);//15

String str = sj.toString();
System.out.println(str);//[aaa, bbb, ccc]

3. 集合

A. 数组和集合的区别

区别 数组 集合
长度 数组长度固定 集合长度可变
存储类型 可以存基本数据类型和引用数据类型 只能存引用数据类型

B. 创建集合对象

1
ArrayList<String> list = new ArrayList<>();

C. ArrayList成员方法

增删改查

方法名 说明
boolean add(E e) 添加元素,返回值表示是否添加成功
boolean remove(E e) 删除指定元素,返回值表示是否删除成功
E remove(int index) 删除指定索引的元素,返回被删除元素
E set(int index,E e) 修改指定索引下的元素,返回原来的元素
E get(int index) 获取指定索引的元素
int size() 集合的长度,也就是集合中元素的个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ArraylistStudy {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
//添加元素
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
System.out.println(list);
//删除元素
boolean result2 = list.remove("ccc");//根据指定元素删除
System.out.println(list);
String str = list.remove(1);//根据索引删除元素
System.out.println(list);

//修改元素
String result = list.set(0, "ccc");
System.out.println(list);
//查询元素
String s = list.get(0);
System.out.println(s);
//遍历
for (int i = 0; i < list.size(); i++) {
String str1 = list.get(i);
System.out.println(str1);
}
}
}

4. 面向对象进阶

A. static

表示静态,是java中一个修饰符,可以修饰成员方法、成员变量

1. 静态变量:被static修饰的成员变量
  • 特点:被该类所有对象共享
  • 不属于对象,属于类;随着类的加载而加载,优先于对象存在
  • 调用方法
    1. 类名调用(推荐)
    2. 对象名调用
2. 静态方法:被static修饰的成员方法

多用于测试类、工具类中,javabean类中很少会用

  • 调用方法
    1. 类名调用(推荐)
    2. 对象名调用
3. static的注意事项
  • 静态方法中只能访问静态(静态变量和静态方法)
  • 非静态方法可以访问所有
  • 静态方法中没有this关键字
4. main方法
1
2
3
4
5
public class HelloWorld {
public static void main(String[] args) {
System.out.println("helloworld");
}
}
  • public: 被JVM调用,访问权限足够大
  • static: 被JVM调用,不用创建对象,直接类名访问
  • void: 因为main方法是静态的,所以测试类中其他方法也需要是静态的。
    被JVM调用,不需要给JVM返回值
  • main: 一个通用的名称,虽然不是关键字,但是被JVM识别
  • String[] args: 用于接收键盘录入数据,已经没有用了

B. 继承

1. 什么是继承
  • 封装:对象代表什么,就得封装对应的数据,并提供数据对应的行为
  • java中提供extends用于一个类和另一个类建立起继承关系
    public class Student extends Preson{}
    public class 子类 extends 父类{}
  • Student称为子类(派生类),Person称为父类(基类或超类)
  • 使用继承的好处
    1. 可以把多个子类中重复的代码抽取到父类中,提高代码的复用性
    2. 子类可以在父类的基础上增加其他功能,使子类更强大
  • 什么时候用继承:当类与类之间,存在相同的内容,并满足子类是父类的一种,就可以考虑使用继承
2. 继承的特点

Java只支持单继承,不支持多继承,但支持多层继承

  • 单继承: 一个子类只能继承一个父类
  • 多继承:子类不能同时继承多个父类(不支持!)
  • 多层继承:子类A继承父类B,父类B可以继承父类C
  • JAVA中所有每个类都直接或间接的继承于Object类
  • 子类只能访问父类中非私有的成员
3. 子类能继承父类哪些内容
父类中的内容 非私有 private
构造方法 不能继承 不能继承
成员变量 能继承 能继承
成员方法 能继承 不能继承
  • 父类中的构造方法不能被子类继承
  • 父类中的成员变量可以被子类继承,但私有的变量不能直接被使用
  • 父类中的虚方法才能被子类继承
    • 虚方法表:非private,非static,非final
4. 继承中成员变量的访问特点
  • 就近原则:谁离我近,我就用谁
  • 先在局部位置找,本类成员位置找,父类成员位置找,逐级往上
1
2
3
4
5
6
7
8
9
10
11
12
class Fu {
String name = "Fu";
}
class Zi extends Fu {
String name = "Zi";
public void ziShow() {
String name = "ziShow";
System.out.println(name);//ziShow
System.out.println(this.name);//Zi
System.out.println(super.name);//Fu
}
}
5. 继承中成员方法的访问特点
  • 直接调用满足就近原则:谁离我近,我就用谁
  • super调用,直接访问父类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Person {
public void eat() {
System.out.println("吃米饭,吃菜");
}
public void drink() {
System.out.println("喝开水");

}
}

//留学生
class OverseasStudent extends Person {
public void lunch() {
this.eat();//吃意大利面
this.drink();//喝凉水

super.eat();//吃米饭,吃菜
super.drink();//喝开水
}
public void eat() {
System.out.println("吃意大利面");
}
public void drink() {
System.out.println("喝凉水");
}
}
6. 方法的重写

当父类的方法不能满足子类现在的需求时,需要进行方法重写

  • 书写格式: 当继承体系中,子类出现了和父类中一模一样的方法声明,就称子类这个方法是重写的方法
  • @Override重写注解(建议加上):放在重写后的方法上,校验子类重写的语法是否正确
  • 重写方法的名称、形参列表必须和父类一致,重写的方法尽量与父类保持一致
7. 构造方法的访问特点
  • 父类中的构造方法不会被子类继承
  • 子类中所有的构造方法默认先访问父类中的无参构造,再执行自己
  • 子类可以通过super调用父类的构造方法
  • 子类构造方法的第一行有一个默认的super();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Person {
String name;
int age;

public Person() {
System.out.println("父类的无参构造");
}

public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

public class Student extends Person {
public Student() {
//子类构造方法中隐藏的super()去访问父类的无参构造
super();
System.out.println("子类的无参构造");
}

public Student(String name, int age) {
super(name, age);
}
}
8. this和super
  • this: 理解成一个变量,表示当前方法调用者的地址值
  • super: 代表父类存储空间
关键字 访问成员变量 访问成员方法 访问构造方法
this this.成员变量
访问本类成员变量
this.成员方法(…)
访问本类成员方法
this(…)
访问本类构造方法
super super.成员变量
访问父类成员变量
super.成员方法(…)
访问父类成员方法
super(…)
访问父类构造方法
9. 带有继承结构的标准Javabean类

父类:员工

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class Employee {
private String id;
private String name;
private double salary;

public Employee() {
}

public Employee(String id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public double getSalary() {
return salary;
}

public void setSalary(double salary) {
this.salary = salary;
}

//工作
public void work() {
System.out.println("Employee worked!");
}

//吃饭
public void eat() {
System.out.println("Employee eat!");
}
}

子类1:经理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Manager extends Employee {
private double bouns;

public Manager() {
}

public Manager(String id, String name, double salary, double bouns) {
super(id, name, salary);
this.bouns = bouns;
}

public double getBouns() {
return bouns;
}

public void setBouns(double bouns) {
this.bouns = bouns;
}

@Override
public void work() {
System.out.println("管理其他人");
}
}

子类2:厨师

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    public class Cook extends Employee {

public Cook() {
}

public Cook(String id, String name, double salary) {
super(id, name, salary);
}

@Override
public void work() {
System.out.println("厨师在做饭");
}
}

C. 多态

1. 什么是多态
  • 同类型的对象,表现出的不同形态(对象的多种形态)
  • 多态的表现形式:父类类型 对象名称 = 子类对象;Animal a = new Dog();
  • 多态的前提
    • 有继承关系
    • 有父类引用指向子类对象
    • 有方法重写
2. 多态调用成员的特点
  • 调用成员变量的特点:编译看左边,运行也看左边
  • 调用成员方法的特点:编译看左边,运行看右边
3. 多态的优势和缺点
  • 优势:定义方法时,使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性和便利
  • 缺点:不能使用子类的特有功能(方法)
  • 如果想要调用子类特有的方法,使用强制类型转换
1
2
3
4
5
6
7
8
9
//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是,则不强转,结果是false
if(a instanceof Dog d) {
d.lookHome();
}else if(a instanceof Cat c){
c.catchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}

D. 包

包就是文件夹。用来管理不同功能的类。

  • 导包的规则
    • 使用同一个包中的类不需要导包
    • 使用java.lang包中的类不需要导包
    • 其他都需要导包
    • 如果同时使用两个包中的同名类,需要用全类名(含包名)
1
2
3
package java.study

import java.study.Student //导入java.study包中的Student类

E. final

final修饰 作用
方法 表明该方法是最终方法,不能被重写
表明该类是最终类,不能被继承
变量 此时是常量
  • final修改基本数据类型:记录的值不能发生改变
  • final修饰引用数据类型:记录的地址值不能发生改变,内部的属性值还是可以改变的

F. 权限修饰符

用来控制一个成员能够被访问的范围
四种:private<默认(空着不写)<protected<public

修饰符 同一个类中 同一个包中其他类 不同包下的子类 不同包下的无关类
private
默认(空着不写)
protected
public
  • 实际开发一般只用private和public
    • 成员变量私有
    • 方法公开

G. 静态代码块

做数据的初始化

1
2
3
4
5
6
7
8
public class App{
static ArrayList<User> list = new ArrayList<>();

static{
//添加一些用户信息
list.add(new User("aaa","12345678","301001200101011234","13812341234"));
}
}

H. 抽象类

如果一个类中存在抽象方法,那么该类就必须声明为抽象类

1. 抽象类的定义

将共性的行为(方法)抽取到父类之后。由于每个子类执行的内容是不一样,所以在父类中不能确定具体的方法体。该方法就可以定义为抽象类。
抽象方法:

1
public abstract 返回值类型 方法名(参数列表);

抽象类

1
public abstract class 类名{}
2. 抽象类的特点
  • 抽象类不能实例化(创建对象)
  • 抽象类不一定有抽象方法,有抽象方法的类一定是抽象类
  • 可以有构造方法
  • 抽象类的子类
    • 要么重写抽象类中的所有抽象方法
    • 要么是抽象类

I. 接口

1. 接口的定义
  • 使用interface定义
    public interface 接口名{}
  • 接口不能实例化(不能创建对象)
  • 接口和类之间是实现关系,通过implements表示
    public class 类名 implements 接口名{}
  • 接口的子类(实现类)
    • 要么重写接口中所有抽象方法
    • 要么是抽象类
  • 接口和类的实现关系,可以单实现,也可以多实现
    public class 类名 implements 接口名1, 接口名2{}
  • 实现类还可以在继承一个类的同时实现多个接口
    public class 类名 extends 父类 implements 接口名1, 接口名2{}
2. 接口中成员的特点
成员 特点
成员变量 只能是常量
public static final
构造方法 没有
成员方法 只能是抽象方法
public abstract
3. 接口和类之间的关系
  • 类和类的关系
    继承关系
  • 类和接口的关系
    实现关系,可以单实现,可以多实现,可以在继承一个类时实现多个接口
  • 接口和接口的关系
    继承关系,可以单继承,可以多继承
4. 接口中的默认方法

public default 返回值类型 方法名(参数列表){}

  • 默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字
  • public可以省略,default不能省略
  • 如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写
5. 接口中的静态方法

public static 返回值类型 方法名(参数列表){}
public static void show(){}

  • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
  • public可以省略,static不能省略
6. 接口中的私有方法

private 返回值类型 方法名(参数列表){}
private void show(){}

private static 返回值类型 方法名(参数列表){}
private static void method(){}

7. 接口的应用

各种行为的规则,是行为的抽象

8. 接口的适配器设计模式

当一个接口中抽象方法过多,但是我只要使用其中一部分的时候,就可以适配器设计模式

  • 书写步骤:
    1. 编写中间类XXXAdapter,实现对应的接口
    2. 对接口中的抽象方法进行空实现
    3. 让真正的实现类继承中间类,并重写需要用的方法
    4. 为了避免其他类创建适配器类的对象,中间的适配器类用abstract进行修饰

J. 内部类

1. 内部类的定义

在一个类的里面再定义一个类

1
2
3
4
5
6
7
8
9
10
public class Car{  //外部类
String carName;
int carAge;
int carColor;

class Engine{ //内部类
String engineName;
int engineAge;
}
}
  • 内部类可以直接访问外部类的成员(包括私有)
  • 外部类必须创建对象访问内部类的成员
2. 成员内部类(了解)
  • 写在成员位置,属于外部类的成员
  • 可以被修饰符修饰

创建对象方式:

1
2
外部类名.内部类名 对象名 =new 外部类对象.内部类对象;
Outer.Inner oi = new Outer().new Inner();

成员内部类如何获取外部类的成员变量:

1
2
3
4
5
6
7
8
9
10
11
12
public class Outer {
private int a = 10;
class Inner {
private int a = 20;
public void show() {
int a = 30;
System.out.println(Outer.this.a);//10
System.out.println(this.a); //20
System.out.println(a); //30
}
}
}
3. 静态内部类(了解)

静态内部类只能访问外部类中的静态变量和静态方法,如果想要访问非静态的需要创建对象

1
2
3
public class Outer{
static class Inner{}
}

创建静态内部对象方式:

1
2
外部类名.内部类名 对象名 = new 外部类名.内部类名;
Outer.Inner oi = new Outer.Inner();

调用静态方法:外部类名.内部类名.方法名();

4. 匿名内部类(重点)
  • 本质是隐藏了名字的内部类
  • 整体就是一个类的子类对象或者接口的实现类对象
1
2
3
new 类名或接口名(){
重写方法;
};
1
2
3
4
5
new Inter(){
public void show(){
//重写方法
}
}

例:

1
2
3
4
5
6
new Animal(){
@Override
public void eat() {
System.out.println("重写了eat方法");
}
};
  • 使用场景:当方法的参数是接口或类时,如果实现类只要使用一次,就可以用匿名内部类简化代码

5. 常用API

A. Math

提供数学计算的工具类

B. System

提供与系统相关的方法

  • exit(): 停止虚拟机
  • currentTimeMillis(): 获取当前时间的毫秒值
  • arraycopy(): 拷贝数组

C. Runtime

当前虚拟机的运行环境

D. Object

Object是Java的顶级父类。所有的类都直接或间接继承于Object类

  • 只有无参构造
  • toString(): 返回字符串
  • equals(Object obj): 比较两个对象是否相等,比较的是地址值

E. 对象克隆(对象复制)

把A对象的属性值完全拷贝给B对象
protected Object clone(int a)

  • 浅克隆:不管对象内部的属性是基本数据类型还是引用数据类型,都完全拷贝过来
  • 深克隆:
    1. 基本数据类型拷贝过来
    2. 字符串复用
    3. 引用数据类型会重新创建新的

F. Objects

  • equals(Object a,Object b):先做非空判断,比较两个对象
  • isNull(Object obj): 判断对象是否为NULL,是返回true
  • nonNull(Object obj): 判断对象是否为NULL,是返回false

G. BigInteger

获取大整数并进行数学运算

H. BigDecima

用于小数的精确计算,用于表示很大的小数

I. 正则表达式

  • 校验字符串是否满足一定的规则,校验数据格式的合法性
  • 可以在一段文本中查找满足条件的字符串

java.util.regex包中

  • Pattern:表示正则表达式
  • Matcher:文本匹配器,按照正则表达式的规则读取字符串
1
2
3
4
5
6
7
8
9
String str = "abdbdfewgewgewvwrebwebtehrejtmiymolpurjergergew";

Pattern p = Pattern.compile("a\\d{0,2}"); //定义正则表达式
Matcher m = p.Matcher(str); //m在str中找到符合p规则的字符串

while(m.find()){//判断是否有符合的小串
String s = m.group(); //获取小串
System.out.println(s);
}

J. JDK7前的时间相关类

1. Date

描述时间,精确到毫秒
从1970-1-1 0:0:0秒开始

2. SimpleDateFormat

把时间变成我们喜欢的格式

3. Calendar

代表系统当前时间的日历对象,可以单独修改、获取时间中的年月日

K. JDK8新增时间相关类

类名 作用
ZoneId 时区
Instant 时间戳
ZoneDateTime 带时区的时间
DateTimeFormatter 用于时间的格式化和解析
LocalDate 年、月、日
LocalTime 时、分、秒
LocalDateTime 年月日、时分秒
Duration 时间间隔(秒,纳秒)
Period 时间间隔(年、月、日)
ChronoUnit 时间间隔(所有单位)

L. 包装类

  • 把基本数据类型变成引用数据类型,也就是把基本数据类型变成一个对象
  • 用一个对象把数据包装起来
基本数据类型 包装类
byte Byte
short Short
char Character
int Integer
long Long
float Float
double Double
boolean Boolean

M. Integer

  • parseInt(String s):将字符串参数转为十进制整数
  • toString():将一个整数转换为字符串表示形式。
  • valueOf(String s):将字符串转换为Integer对象。
  • intValue():将Integer对象转换为基本数据类型int。
1
2
3
4
5
String str = "123";
int num = Integer.parseInt(str); // 将字符串转换为整数
Integer integer = Integer.valueOf(str); // 将字符串转换为Integer对象
String str2 = integer.toString(); // 将Integer对象转换为字符串
int num2 = integer.intValue(); // 将Integer对象转换为基本数据类型int

6. 基本算法

A. 查找

七种查找:基本查找、二分查找、插值查找、斐波那契查找、分块查找、哈希查找、树表查找

1. 顺序查找
1
2
3
4
5
6
7
8
public static boolean basicSearch(int[] arr, int number) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == number) {
return true;
}
}
return false;
}
2. 二分查找

前提条件:数组中的数据必须是有序的

  • min和max表示当前要查找的范围
  • mid是在min和max中间的
  • 如果要查找的元素在mid的左边,缩小范围时,min不变,max=mid-1
  • 如果要查找的元素在mid的右边,缩小范围时,max不变,max=mid+1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static int binarySearch(int[] arr, int number) {
//定义两个变量记录要查找的范围
int min = 0;
int max = arr.length - 1;

//利用循环不断的去找要查找的数据
while (true) {
if (min > max) {
return -1;
}
//找到min和max的中间位置
int mid = (min + max) / 2;
//拿着mid指向的元素跟要查找的元素进行比较
if (arr[mid] > number) {
//number在mid的左边
max = mid - 1;
} else if (arr[mid] < number) {
//number在mid的右边
} else {
return mid;
}
}
}
3. 分块查找
  • 原则:
    1. 前一块中的最大数据小于后一块中所有数据(块内无序,块间有序)
    2. 块数数量一般等于数字的个数开根号。16个数字分为4左右
  • 核心思路:先确定要查找的元素在哪一块,然后在块内查找
    要创建一个块类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Block {
/*block的索引,用来标识块中元素*/
public int index;
/*该block的开始位置*/
public int start;
/*块元素长度,在该例子中0代表空元素,不计入block长度*/
public int length;

public Block(int index, int start, int length) {
this.index = index;
this.start = start;
this.length = length;
}
}

B. 排序

十种排序算法:冒泡排序、选择排序、插入排序、快速排序、希尔排序、归并排序、堆排序、基数排序、桶排序、计数排序

1. 冒泡排序

相邻的数据两两比较,小的在前面,大的放后面

  • 相邻的元素两两比较,大的放右边,小的放左边
  • 第一轮比较完毕之后,最大值就已经确定,后面依次类推继续比较
  • 如果数组有n个数据,总共执行n-1轮代码
1
2
3
4
5
6
7
8
9
10
11
int[] arr = {2, 4, 5, 1, 3};

for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
2. 选择排序

从0索引开始,拿着每一个索引上的元素和后面的元素依次比较,小的放前面,大的放后面

1
2
3
4
5
6
7
8
9
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
3. 插入排序
  • 把数组分为已排序和未排序两部分
  • 从第二个元素开始,将未排序元素逐个插入到已排序部分的合适位置,直到整个数组都有序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int[] arr = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};
//找到无序的哪一组数组是从哪个索引开始的
int startIndex = -1;
for (int i = 0; i < arr.length; i++) {
if (arr[i] > arr[i + 1]) {
startIndex = i + 1;
break;
}
}

//遍历从startIndex开始到最后一个元素,依次得到无序的哪一组数据中的每一个元素
for (int i = startIndex; i < arr.length; i++) {
int j = i;//记录当前要插入数据的索引
while (j > 0 && arr[j] < arr[j - 1]) {
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
j--;
}
}
4. 递归算法
  • 要有终止条件
  • 要找到递归方程
5. 快速排序
  1. 选择一个基准元素
  2. 将数组分成两部分,小于基准的元素在左边,大于基准的元素在右边。
  3. 递归地对左边和右边的子数组进行快速排序。
  4. 直到子数组的大小为 1 或 0 时停止递归。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
// 获取基准元素的索引
int pivotIndex = partition(arr, low, high);

// 递归排序左边子数组
quickSort(arr, low, pivotIndex - 1);

// 递归排序右边子数组
quickSort(arr, pivotIndex + 1, high);
}
}

// 分区函数,返回基准元素的正确位置
public static int partition(int[] arr, int low, int high) {
// 选择最右边的元素作为基准
int pivot = arr[high];
int i = low - 1;

// 遍历数组,调整元素位置
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
// 交换 arr[i] 和 arr[j]
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}

// 将基准元素放到正确的位置
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;

return i + 1; // 返回基准元素的索引
}

C. Arrays

常用方法 作用
String toString(数组) 把数组拼接成一个字符串
int binarySearch(数组,查找的元素) 二分查找法查找元素
int[] copyof(原数组,新数组长度) 拷贝数组
int[] copyOfRange(原数组,起始索引,结束索引) 拷贝数组(指定范围)
void fill(数组,元素) 填充数组
void sort(数组) 按照默认方式进行数组排序
void sort(数组,排序规则) 按照指定的规则排序

D. Lambda表达式

格式:

1
2
3
() ->{

}
  • ()对应着方法的形参
  • ->固定格式
  • {}对应方法的方法体
1
2
3
4
5
6
method(new Swim() {
@Override
public void swimming() {
System.out.println("正在游泳 ~~~ ");
}
});

转为:

1
2
3
4
5
method(
()->{
System.out.println("正在游泳 ~~~ ");
}
);
  • 使用前提:必须是接口的匿名内部类,接口中只能有一个抽象方法

7. 集合进阶

A. 集合体系结构

  • 单列集合(Collection):每次只能添加一个元素
    • List系列集合:添加的元素是有序、可重复、有索引
    • Set系列集合:添加的元素是无序、不重复、无索引

  • 双列集合(Map):每次添加一对元素

B. Collection

单列集合的祖宗接口

1. 常用方法
常用方法 作用
boolean add(E e) 添加
void clear() 清空
boolean remove(E e) 删除
boolean contains(Object obj) 判断是否包含
boolean isEmpty() 判断是否为空
int size() 集合长度
2. Collection的遍历方式
  • 迭代器遍历
    类是Iterator,迭代器是集合专用的遍历方式
    如果在遍历的过程中需要删除元素,最好使用迭代器
1
2
3
4
5
6
Iterator<String> it = list.iterator();//创建指针,指向集合第一个位置

while(it.hasNext()){ //循环判断是否有元素
String str = it.next(); //获取元素,移动指针
System.out.println(str);
}
  • 增强for遍历
    所有的单列集合和数组才能用增强for遍历
1
2
3
for(元素的数据类型 变量名:数组或者集合){

}

例:

1
2
3
for(String s : list){
System.out.println(s); //变量s代表集合中每个数据
}

修改增强for中的变量s,s是第三方变量,不会改变集合中的原本数据

  • lambda表达式遍历

C. List集合

1. List集合的特点
  • 有序:存和取的元素顺序一致
  • 有索引:可以通过索引操作元素
  • 可重复:存储的元素可以重复
2. List集合的特有方法

增删改查对应的方法

方法名称 说明
void add(int index, E element) 在此集合中的指定位置插入指定的元素
E remove(int index) 删除指定索引处的元素,返回被删除的元素
E set(int index, E element) 修改指定索引处的元素,返回被修改的元素
E get(int index) 返回指定索引处的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
List<String> list = new ArrayList<>();

// 1. 增加元素
list.add("Apple");
list.add("Banana");
list.add(1, "Orange"); // 在索引1处插入元素
System.out.println("添加元素后的列表: " + list); // 输出: [Apple, Orange, Banana]

// 2. 删除元素
list.remove(1); // 删除索引1的元素("Orange")
list.remove("Banana"); // 删除指定元素("Banana")
System.out.println("删除元素后的列表: " + list); // 输出: [Apple]

// 3. 修改元素
list.set(0, "Grapes"); // 将索引0的元素修改为"Grapes"
System.out.println("修改元素后的列表: " + list); // 输出: [Grapes]

// 4. 查询元素
list.add("Apple");
list.add("Banana");
list.add("Apple");
System.out.println("当前列表: " + list); // 输出: [Grapes, Apple, Banana, Apple]
3. List集合的遍历方式

五种遍历方式

  • 迭代器遍历
  • 列表迭代器
  • 普通for循环
  • 增强for
  • Lamada表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Orange");

// 1. 使用 for 循环遍历
System.out.println("1. 使用 for 循环遍历:");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}

// 2. 使用增强 for 循环遍历
System.out.println("2. 使用增强 for 循环遍历:");
for (String fruit : list) {
System.out.println(fruit);
}

// 3. 使用 迭代器Iterator 遍历
System.out.println("3. 使用 Iterator 遍历:");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}

// 4. 使用 列表迭代器ListIterator 遍历
System.out.println("4. 使用 ListIterator 正向遍历:");
ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext()) {
System.out.println(listIterator.next());
}

System.out.println("4. 使用 ListIterator 反向遍历:");
while (listIterator.hasPrevious()) {
System.out.println(listIterator.previous());
}

// 5. 使用 Lamada表达式forEach 方法遍历
System.out.println("5. 使用 forEach 方法遍历:");
list.forEach(fruit -> System.out.println(fruit));

D. ArrayList集合底层原理(动态数组)

  1. 利用空参创建的集合,在底层会创建一个默认长度为0的数组
  2. 添加第一个元素,底层会创建一个新的长度为10的数组
  3. 存满时,会扩容1.5倍
  4. 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准

E. LinkedList集合

底层数据结构是双向链表,查询慢,增删快

F. 泛型

用来检查存储数据的数据类型

  • 泛型的格式:<数据类型>
  • 注意:泛型只能支持引用数据类型
1. 泛型的好处
  • 统一数据类型
  • 避免强制类型转换可能出现的异常
2. 泛型类

当一个类中,某个变量的数据类型不确定,就可以定义带有泛型的类
public class ArrayList<E>{}E理解为变量,代表数据类型

3. 泛型方法

当方法中形参类型不确定时
1. 使用类名后面定义的泛型
2. 在方法申明上定义自己的泛型

public<T> void show(T t){}T理解为变量,记录数据类型

4. 泛型接口

修饰符 interface 接口名<类型>{}
public interface List<E>{}

5. 泛型的继承和通配符
  • 泛型不具备继承性,但数据具备继承性
1
2
3
4
5
ArrayList<Ye> list1 = new ArrayList<>();

list1.add(new Ye());
list1.add(new Fu());
list1.add(new Zi());

希望:本方法虽然不确定类型,但是以后我希望只能传递Ye Fu Zi(这三个继承体系的数据类型)

此时我们就可以使用泛型的通配符:
?也表示不确定的类型,他可以进行类型的限定
?extends E:表示可以传递E或者E所有的子类类型
?super E:表示可以传递E或者E所有的父类类型

应用场景:
1.如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口。
2.如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以泛型的通配符

1
2
3
4
5
6
7
public static void method(ArrayList<? extends Ye> list){
//此时泛型的数据类型只能是Ye的子类类型(包括Ye,Fu,Zi)
}

public static void method(ArrayList<? super Fu> list){
//此时泛型的数据类型只能是Fu的父类类型(包括Ye和Fu)
}

G. Set系列集合

无序、不重复、无索引
Set接口的方法和Collection的API一致

  • 无序:存取顺序不一致

  • 不重复:可以去除重复

  • 无索引:没有带索引的方法,不能用普通for循环遍历

  • HashSet:无序、不重复、无索引

  • LinkedHashSet: 有序、不重复、无索引

  • TreeSet:可排序、不重复、无索引

H. HashSet

HashSet集合底层采取哈希表存储数据
哈希表组成:数组+链表+红黑树

1. 哈希值
  • 根据hashCode方法算出来的int类型的整数
  • 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
  • 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值
2. 对象的哈希值特点
  • 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
  • 如果已经重写hashcode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
  • 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)
3. HashSet底层原理

4. HashSet的三个问题
  • 问题1:HashSet为什么存和取的顺序不一样
    哈希值大小不同,挂在链表上的数据的顺序不一致
  • 问题2:HashSet为什么没有索引
    因为不单单是靠数组存储的,还有链表和红黑树无法确定索引
  • 问题3:HashSet利用什么机制保证数据去重的
    HashCode()方法和equals()方法

I. LinkedHashSet

有序、不重复、无索引
底层仍然是哈希表,每个元素额外多加一个双链表的机制记录存储的顺序

J. TreeSet

不重复、无索引、可排序

  • 可排序:按照元素从小到大排序(因为基于红黑树实现)
1. TreeSet集合排序规则
  • 对于数值类型:Integer,Double,默认按照从小到大的顺序进行排序。
  • 对于字符、字符串类型:按照字符在ASCII码表中的数字升序进行排序。
2. TreeSet的两种比较规则
方法一

默认排序:Javabean类实现Comparable接口指定比较规则

1
2
3
4
5
6
7
8
9
10
11
12
//1.创建三个学生对象
Student s1 = new Student( name: "zhangsan", age: 23);
Student s2 = new Student( name: "lisi", age: 24);
Student s3 = new Student( name: "wangwu", age: 25);
//2.创建集合对象
TreeSet<Student> ts = new TreeSet<>();
//3.添加元素
ts.add(s1);
ts.add(s2);
ts.add(s3);
//4.打印集合
System.out.println(ts);//23,24,25
1
2
3
4
5
6
@Override
public int compareTo(Student o) {
//指定排序的规则
//只看年龄,我想要按照年龄的升序进行排列
return this.getAge() - o.getAge();
}
  • this:表示当前要添加的元素
  • o:表示已经在红黑树存在的元素
  • 返回值:
    负数:认为要添加的元素是小的,存左边
    正数:认为要添加的元素是大的,存右边
    0:认为要添加的元素已经存在,舍弃
方法二

比较器排序:创建TreeSet对象时,传递比较器Comparator指定规则

1
2
3
4
5
6
7
8
9
10
11
12
// 要求:存入四个字符串,“c”,“ab”,“df”,"qwer”
//按照长度排序,如果一样长则按照首字母排序
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2){
// 按照长度排序
int i = o1.length() - o2.length();
//如果一样长则按照首字母排序
i = i == 0 ? o1.compareTo(o2) : i;
return i;
}
});

K. 单列集合总结

  1. 如果想要集合中的元素可重复
    用ArrayList集合,基于数组的。(用的最多)
  2. 如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
    用LinkedList集合,基于链表的。
  3. 如果想对集合中的元素去重
    用HashSet集合,基于哈希表的。(用的最多)
  4. 如果想对集合中的元素去重,而且保证存取顺序
    用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet。
  5. 如果想对集合中的元素进行排序
    用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。

L. 双列集合的特点

  • 双列集合一次需要存一对数据,分别为键和值
  • 键不能重复,值可以重复
  • 键和值是一一对应的,每一个键只能找到自己对应的值
  • 键+值这个整体 我们称之为“键值对”或者“键值对对象”,在Java中叫做“Entry对象

M. Map集合

Map是双列集合的顶层接口,功能是全部双列集合都可以继承的

1. Map常见API
方法 功能
V put(K key,V value) 添加元素
V remove(Object key) 根据键删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Object key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中键值对的个数
  • put(): 添加数据时,如果键是存在的,则会把原有的键值对对象覆盖,返回被覆盖的值
2. Map的遍历方式(键找值)

将键放在单列集合中,通过单列集合依次遍历找值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1.创建Map集合的对象
Map<String, String> map = new HashMap<>();

//2.添加元素
map.put("尹志平","小龙女");
map.put("郭靖","穆念慈");
map.put("欧阳克","黄蓉");

//3.1获取所有的键,把这些键放到一个单列集合当中
Set<String> keys = map.keySet();
//3.2遍历单列集合,得到每一个键
for (String key : keys) {
//3.3 利用map集合中的键获取对应的值
String value = map.get(key);
System.out.println(key + " = " + value);
}
3. Map的遍历方式(键值对)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//1.创建Map集合的对象
Map<String, String> map = new HashMap<>();

//2.添加元素
map.put("标枪选手","马超");
map.put("人物挂件","明世隐");
map.put("御龙骑士","尹志平");

//通过键值对对象进行遍历
//3.1 通过一个方法获取所有的键值对对象,返回一个Set集合
Set<Map. Entry<String, String>> entries = map.entrySet();
//3.2 遍历entries这个集合,去得到里面的每一个键值对对象
for (Map.Entry<String, String> entry : entries) {
//3.3 利用entry调用get方法获取键和值
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=" + value);
}

N. HashMap

是Map里面的一个实现类
由键决定:无序、不重复、无索引

HashMap的特点
  • HashMap底层是哈希表结构的(与HashSet一样)
  • 依赖hashCode方法和equals方法保证键的唯一
  • 如果键存储的是自定义对象,需要重写hashCode和equals方法
  • 如果值存储自定义对象,不需要重写hashCode和equals方法

O. LinkedHashMap

由键决定:有序、不重复、无索引

  • 有序:保证存储和取出的元素顺序一致(额外加了一个双链表机制记录存储的顺序)

P. TreeMap

TreeMap跟TreeSet底层原理一样,都是红黑树结构的。
由键决定特性:不重复、无索引、可排序

  • 可排序:对键进行排序
  • 注意:默认按照键的从小到大进行排序,也可以自己规定键的排序规则
    • 实现Comparable接口,指定比较规则。
    • 创建集合时传递Comparator比较器对象,指定比较规则。
统计字符串中每一个字符出现的次数

可以使用HashMap或者TreeMap进行统计;

  • 键:代表要统计的内容
  • 值:表示次数
  • 如果题目中没有要求对结果进行排序使用HashMap,要排序的话使用TreeMap

Q. 可变参数

方法的形参的个数是可以变化的
格式:属性类型...名字
int...args
public static int getSum(int...args){}

  • 形参列表中可变参数只能有一个
  • 可变参数必须放在形参列表的最后面

R. Collections

Collections不是集合,是集合的工具类

常用方法 作用
public static boolean addAll(Collection c, T… elements) 批量添加元素
public static void shuffle(List<?> list) 打乱List集合元素的顺序

S. 不可变集合

不可以被修改的集合,长度不能变,内容不能变(只能查询,不能增删改)

  • List、Set、Map接口中,都存在静态的of方法可以创建不可变集合
方法名称 说明
static List of(E…elements) 创建一个具有指定元素的List集合对象
static Set of(E…elements) 创建一个具有指定元素的Set集合对象
static <K, V> Map<K, V> of(E…elements) 创建一个具有指定元素的Map集合对象

三种方式的细节

  • List: 直接用
  • Set:元素不能重复
  • Map:元素不能重复、键值对数量最多是10个。
    • 超过10个用ofEntries方法

8. 数据结构

数据结构是计算机底层存储、组织数据的方式

A. 栈

后进先出,先进后出

B. 队列

先进先出,后进后出

C. 数组

元素在内存中是连续存储的

  • 查询速度快:查询数据通过地址值和索引定位
  • 删除效率低:要将数据删除需将后面所有数据前移
  • 添加效率低:添加一个数据,后面所有数据都要后移

D. 链表

每个节点都是独立的对象,在内存中不连续;每个节点包含数据值和下一个节点的地址

  • 查询慢:要从链表头节点开始查询
  • 增删快

E. 树

父节点,左子节点,右子节点,左子树,右子树

  • 度:每个节点的子节点数量(二叉树的度小于等于2>)
  • 树高:树的总层数
  • 根节点:最顶层的节点
1. 二叉查找树
  • 任意节点左子树上的值都小于当前节点
  • 任意节点右子树上的值都大于当前节点

  1. 添加节点
  • 小的存左边
  • 大的存右边
  • 一样的不存
  1. 查找节点
    小的在左边,大的在右边
2. 二叉树的遍历方式

前序遍历,中序遍历,后序遍历,层序遍历

  1. 前序遍历(根左右)
    从根节点开始,依次按照当前节点,左子节点,右子节点顺序遍历
  2. 中序遍历(左根右)
    从左子节点开始,依次按照左子节点,当前节点,右子节点顺序遍历
  3. 后序遍历(左右根)
    从左子节点开始,依次按照左子节点,右子节点,当前节点顺序遍历
  4. 层序遍历
    从根节点开始一层一层的遍历
3. 平衡二叉树

查找效率更高
任意节点左右子树高度差不超过1 (旋转机制)

平衡二叉树的左旋

平衡二叉树的右旋

平衡二叉树需要旋转的四种情况

左左、左右、右右、右左

  1. 左左(一次右旋):当根节点左子树的左子树有节点插入,导致二叉树不平衡
  2. 左右(先局部左旋,再整体右旋):当根节点左子树的右子树有节点插入,导致二叉树不平衡
  3. 右右(一次左旋):当根节点右子树的右子树有节点插入,导致二叉树不平衡
  4. 右左(先局部右旋,再整体左旋):当根节点右子树的左子树有节点插入,导致二叉树不平衡
4. 红黑树

是一种特殊的二叉查找树,每个节点都有存储位表示节点的颜色(红或黑)

  • 是二叉查找树
  • 但不是高度平衡的
  • 特有的红黑规则

红黑规则:

添加节点的规则:
默认颜色:添加节点默认是红色的(效率高)

9. Stream流

A. Stream流介绍

  • 作用: 结合Lambda表达式,简化集合、数组的操作
  • 使用步骤:
    1. 先得到一条Stream流(流水线),并把数据放上去
    2. 利用Stream流的API进行操作
      • 中间方法(过滤 转换):方法调用完毕之后,还可以调用其他方法
      • 终结方法(统计 打印): 最后一步,调用完毕之后,不能调用其他方法
    3. 使用中间方法对流水线上的数据进行操作
    4. 使用终结方法对流水线上的数据进行操作

B. 获取Stream流

获取方式 方法名 说明
单列集合 default Stream stream() Collection中的默认方法
双列集合 无法直接使用stream流
数组 public static Stream stream(T[] array) Arrays工具类中的静态方法
一堆零散数据 public static Stream of(T … values) Stream接口中的静态方法
  • 单列集合
1
2
3
ArrayList<String> list = new ArrayList<>();
Collections.addAlL(list, "a","b","c", "d", "e");
list.stream().forEach(s -> System.out.println(s));//获取
  • 双列集合
1
2
3
4
5
6
7
8
9
10
11
12
//1.创建双列集合
HashMap<String, Integer> hm = new HashMap<>();
//2.添加数据
hm.put("aaa",111);
hm.put("bbb",222);
hm.put("ccc",333);
hm.put("ddd",444);

//3.第一种获取stream流
hm.keySet().stream().forEach(s -> System.out.println(s));
//4.第二种获取stream流
hm. entrySet().stream().forEach(s-> System.out.println(s));
  • 数组
1
2
3
4
5
6
7
//1.创建数组
int[] arr1= {1,2,3,4,5,6,7,8,9,10};
String[] arr2 = {"a","b","c"};

//2.获取stream流
Arrays.stream(arr1).forEach(s-> System.out.println(s));
Arrays.stream(arr2).forEach(s-> System.out.println(s));
  • 零散数据
1
2
Stream.of(1,2,3,4,5).forEach(s-> System.out.println(s));
Stream.of("a","b","c","d","e").forEach(s-> System.out.println(s));

C. Stream流的中间方法

方法 作用
Stream filter(Predicate<? super T> predicate) 过滤
Stream limit(long maxSize) 获取前几个元素
Stream skip(long n) 跳过前几个元素
Stream distinct() 元素去重,依赖(hashCode和equals方法)
static Stream concat(Stream a, Stream b) 合并a和b两个流为一个流
Stream map(Function<T, R> mapper) 转换流中的数据类型
  • 注意1: 中间方法,返回新的Stream流,原来的Stream流只能使用一次,建议使用链式编程
  • 注意2: 修改Stream流中的数据,不会影响原来集合或者数组中的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 找到集合中以张开头并且是三个字的姓名
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(s -> System.out.println(s));

list.stream().limit(3).forEach(s -> System.out.println(s)); //获取集合前三个元素
list.stream().skip(4) .forEach(s -> System.out.println(s));//跳过前四个元素


list1.stream().distinct().forEach(s -> System.out.println(s));//对list1集合去重
Stream.concat(list1.stream(),list2.stream()).forEach(s -> System.out.println(s));//合并list1和list2集合

list.stream()
.map(s-> Integer.parseInt(s.split("-") [1]))
.forEach(s-> System.out.println(s));//将集合中-之后的数字提取出来打印

D. Stream流的终结方法

方法 作用
void forEach(Consumer action) 遍历
long count() 统计
toArray() 收集流中的数据,放到数组中
collect(Collector collector) 收集流中的数据,放到集合中(List Set Map)
1
2
3
4
list.stream().forEach(s -> System.out.println(s)); //遍历打印
long count = list.stream().count();//统计集合元素个数

Object[] arr1 = list.stream().toArray();//收集流中的数据放在数组中
  • collect方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//我要把所有的男性收集起来
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌-男-15","周芷若-女-14","赵敏-女-13","张强-男-20","张三丰-男-100","张翠山-男-40","张良-男-35","王二麻子-男-37","谢广坤-男-41");

//收集List集合当中
List<String> newList1 = list.stream()
.filter(s -> "男".equals(s.split("-") [1]))
.collect(Collectors.toList());

//收集Set集合当中,会去重
Set<String> newList2 = list.stream().filter(s -> "男".equals(s.split("-") [1]))
.collect(Collectors.toSet());

//收集到Map集合中,键对应名字,值对应年龄
Map<String, Integer> map2 = list.stream()
.filter(s -> "y'".equals(s.split("-") [1]))
.collect(Collectors.toMap(
s -> s.split("-") [0],
s -> Integer.parseInt(s.split("-")[2])));

10. 方法引用

把已经有的方法拿过来用,当做函数式接口中抽象方法的方法体

  1. 引用处需要是函数式接口
  2. 被引用的方法需要已经存在
  3. 被引用方法的形参和返回值需要跟抽象方法的形参和返回值保持一致

A. 引用静态方法

格式: 类名::静态方法
示例: Integer::parseInt

1
2
3
4
5
6
7
8
//将集合中数字变成int类型
//1.创建集合并添加元素
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"1","2","3", "4", "5");

list.stream()
.map(Integer::parseInt)
.forEach(s-> System.out.println(s));

B. 引用成员方法

格式: 对象::成员方法

  1. 其他类:其他类对象::方法名
  2. 本类:this::方法名(引用处不能是静态方法)
  3. 父类:super::方法名(引用处不能是静态方法)
1
2
3
StringOperation so = new StringOperation();
list.stream().filter(so::stringJudge) //调用StringOperation类中的stringJudge方法
.forEach(s-> System.out.println(s));

C. 引用构造方法

格式: 类名::new
示例: Student::new

1
2
3
4
5
6
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌,15","周芷若,14","赵敏,13")

//封装成Student对象并收集到List集合中
List<Student> newList2 = list.stream().map(Student::new).collect(Collectors.tolist());
System.out.println(newList2);

D. 类名引用成员方法

格式: 类名::成员方法
示例: String::substring

1
2
3
4
5
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"aaa", "bbb", "ccc", "ddd");

//拿着流里面的每一个数据,去调用String类中的toUpperCase方法,方法的返回值就是转换之后的结果。|
list.stream().map(String::toUpperCase).forEach(s -> System.out.println(s));

E. 引用数组的构造方法

格式: 数据类型[]::new
示例: int[]::new

1
2
3
4
5
6
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1, 2, 3, 4, 5);

//收集到数组当中
Integer[] arr2 = list.stream().toArray(Integer[]::new);
System.out.println(Arrays.toString(arr2));

三. JAVA核心

1. 异常

A. 异常体系介绍

异常(Exception)就是程序出现的问题

B. 编译时异常和运行时异常

  • 编译时异常:为了提醒程序员代码有问题
  • 运行时异常(仅有RuntimeException):代码运行出错产生异常

C. 异常的作用

  1. 用来查询bug的关键参考信息
  2. 可以作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况

D. 异常的处理方式

1. JVM默认处理方式

在控制台提示异常问题,代码不在运行

2. 自己处理(捕获异常try…catch)

当代码出现异常,可以让程序继续往下执行

1
2
3
4
5
try{
可能出现异常的代码;
}catch(异常类名 变量名){
异常的处理代码;
}
1
2
3
4
5
6
7
8
int[] arr = {1, 2, 3, 4, 5, 6};
try{
System.out.println(arr[10]); //可能出现异常的代码;
}catch(ArrayIndexOutOfBoundsException e){
//如果出现了ArrayIndexOutOfBoundsException异常,我该如何处理
System.out.println("索引越界了");
}
System.out.println("看看我执行了吗?");
3. 自己处理(灵魂四问)

灵魂一问:如果try中没有遇到问题,怎么执行?
正常执行

灵魂二问:如果try中可能会遇到多个问题,怎么执行?
会写多个catch与之对应
细节:如果我们要捕获多个异常,这些异常中如果存在父子关系的话,那么父类一定要写在下面

灵魂三问:如果try中遇到的问题没有被捕获,怎么执行?
相当于try..catch白写了,最终交给虚拟机处理

灵魂四问:如果try中遇到了问题,那么try下面的其他代码还会执行吗?
下面的代码就不会执行了,直接跳转到对应的catch当中,执行catch里面的语句体

4. 抛出处理(throw和throws)

throws
写在方法定义处,表示声明一个异常,编译时异常必须要写
public void 方法()throws 异常类名1,异常类名2 ... {}

throw
写在方法中,结束方法

1
2
3
4
public void (){
throw new NullPointerException();
//下面代码就不执行了
}

E. 异常的常见方法

Throwable的成员方法
public void printStackTrace(): 把异常的错误信息输出在控制台

F. 自定义异常

为了让控制台的报错信息见名知意

  1. 定义异常类
  2. 写继承关系
  3. 空参构造和带参构造

2. File

File对象表示路径,可以是文件和文件夹

A. File类的构造方法

构造方法 作用
public File(String pathname) 根据文件路径创建文件对象
public File(String parent, String child) 根据父路径名字符串和子路径名字符串创建文件对象
public File(File parent, String child) 根据父路径对应文件对象和子路径名字符串创建文件对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//1.根据字符串表示的路径,变成File对象
String str = "C:\\Users\\alienware\\Desktop\\a.txt";
File f1 = new File(str);
System.out.println(f1);

//2.父级路径:C:\Users\alienware\Desktop
//子级路径:a.txt
String parent = "C:\\Users\\alienware\\Desktop";
String child = "a.txt";
File f2 = new File(parent, child);
System.out.println(f2);

//3.把一个File表示的路径和String表示路径进行拼接
File parent2 = new File("C:\\Users\\alienware\\Desktop");
String child2 = "a.txt";
File f4 = new File(parent2,child2);
System.out.println(f4);

B. File类的常见成员方法

1. 判断和获取
常见方法 作用
public boolean isDirectory() 判断此路径名表示的File是否为文件夹
public boolean isFile() 判断此路径名表示的File是否为文件
public boolean exists() 判断此路径名表示的File是否存在
public long length() 返回文件的大小(字节数量)
public String getAbsolutePath() 返回文件的绝对路径
public String getPath() 返回定义文件时使用的路径
public String getName() 返回文件的名称,带后缀
public long lastModified() 返回文件的最后修改时间(时间毫秒值)
2. 创建和删除
常见方法 作用
public boolean createNewFile() 创建一个新的空的文件
public boolean mkdir() 创建单级文件夹
public boolean mkdirs() 创建多级文件夹
public boolean delete() 删除文件、空文件夹
3. 获取并遍历
常见方法 作用
public File[] listFiles() 获取当前该路径下所有内容

3. IO流

A. IO流的概述

用于读写文件中的数据
输出流(output): 程序–>文件
输入流(input): 文件–>程序

B. IO流的体系

C. 字节输出流(FileOutputStream)

操作本地文件的字节输出流,可以把程序中的数据写出到本地文件中

1. 书写步骤:
  1. 创建字节输出流对象
  2. 写数据
  3. 释放资源
2. 字节输出流的细节:
  1. 创建字节输出流对象
    细节1:参数是字符串表示的路径或者是File对象都是可以的
    细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。
    细节3:如果文件已经存在,则会覆盖清空文件
  2. 写数据
    细节:write方法的参数是整数,但是实际上写到本地文件中的是整数在ASCII上对应的字符
3. FileOutputStream的三种方法
方法 作用
void write(int b) 一次写一个字节数据
void write(byte[] b) 一次写一个字节数组数据
void write(byte[] b, int off, int len) 一次写一个字节数组的部分数据
1
2
3
4
5
6
7
8
9
10
11
12
//1.创建对象
FileOutputStream fos = new FileOutputStream("myio\\a.txt");

//2.写出数据
fos.write(97); // a
fos.write(98); // b
byte[] bytes = {97, 98, 99, 100, 101};
fos.write(bytes) ;
fos.write(bytes, 1, 2);// b c

//3.释放资源
fos.close();
4. 换行和续写

换行符:

  • windows: \r\n
  • Linux: \n
  • Mac: \r

续写:
如果想要续写,打开续写开关即可
开关位置:创建对象的第二个参数
默认false:表示关闭续写,此时创建对象会清空文件
手动传递true:表示打开续写,此时创建对象不会清空文件

D. 字节输入流(FileInputStream)

操作本地文件的字节输出流,可以把本地文件中的数据读取到程序中

1. 书写步骤:
  1. 创建字节输入流对象
  2. 读数据
  3. 释放资源
2. FileInputStream的细节
  1. 创建字节输入流对象
    如果文件不存在,就直接报错
  2. 读取数据
    一次读一个字节,读的是ASCII码对应的数字
    读到文件末尾,read方法返回-1
3. FileInputStream循环读取
1
2
3
4
5
6
7
8
9
//1.创建对象
FileInputStream fis = new FileInputStream("myio\\a.txt");
//2.循环读取
int b;
while ((b = fis.read()) != -1) {
System.out.println((char) b);
}
//3.释放资源
fis.close();
4. 文件拷贝

使用字节流拷贝的速度很慢:因为一次只读取一个字节

1
2
3
4
5
6
7
8
9
10
11
//1.创建对象
FileInputStream fis = new FileInputStream("D:\\itheima\\movie.mp4");
FileOutputStream fos = new FileOutputStream("myio\\copy.mp4");
//2.拷贝
int b;
while((b = fis.read()) != -1){
fos.write(b);//核心思想:边读边写
}
//3.释放资源 规则:先开的最后关闭
fos.close();
fis.close();
5. FileInputStream一次读取多个字节

public int read(byte[] buffer): 一次读取一个字节数组数据

1
2
3
4
5
6
7
8
9
10
11
12
//1.创建对象
FileInputStream fis = new FileInputStream("D:\\itheima\\movie.mp4");
FileOutputStream fos = new FileOutputStream("myio\\copy.mp4");
//2.拷贝
int len;
byte[] bytes = new byte[1024 * 1024 * 5];
while((len = fis.read(bytes)) != -1){
fos.write(bytes,0,len); //读多少写多少
}
//3.释放资源
fos.close();
fis.close();

E. 字符集

ASCII字符集、GBK字符集、Unicode字符集

  • ASCII字符集:一个英文占一个字节
  • GBK字符集:一个英文占一个字节,一个中文占两个字节
  • Unicode字符集:
    UTF-8编码规则:用1~4个字节保存(英文一个字节,中文三个字节)

F. 字符输入流(FileReader)

1. 读取步骤
  1. 创建字符输入流对象
构造方法 作用
public FileReader(File file) 创建字符输入流关联本地文件
public FileReader(String pathname) 创建字符输入流关联本地文件
  1. 读取数据
成员方法 作用
public int read() 读取数据,读到末尾返回-1
public int read(char[] buffer) 读取多个数据,读到末尾返回-1
  1. 释放资源
2. 空参read读取数据
1
2
3
4
5
6
7
8
9
//1. 创建对象
FileReader fr = new FileReader("myio\\a.txt");
//2. 读取数据
int ch;
while((ch = fr.read()) != -1){
System.out.print((char)ch);
}
//3. 释放资源
fr.close();
3. 带参read读取数据
1
2
3
4
5
6
7
8
9
10
11
//1.创建对象
FileReader fr = new FileReader("myio\\a.txt");
//2.读取数据
char[] chars = new char[2];
int len;
while((len = fr.read(chars)) != -1){
//把数组中的数据变成字符串再进行打印
System.out.print(new String(chars,0,len));
}
//3.释放资源
fr.close();

G. 字符输出流(FileWriter)

书写步骤:

  1. 创建字符输出流对象
  2. 写数据
  3. 释放资源
构造方法 作用
public FileWriter(File file) 创建字符输出流关联本地文件
public FileWriter(String pathname) 创建字符输出流关联本地文件
public FileWriter(File file, boolean append) 创建字符输出流关联本地文件,续写
public FileWriter(String pathname, boolean append) 创建字符输出流关联本地文件,续写
成员方法 作用
void write(int c) 写出一个字符
void write(String str) 写出一个字符串
void write(String str, int off, int len) 写出一个字符串的一部分
void write(char[] cbuf) 写出一个字符数组
void write(char[] cbuf, int off, int len) 写出字符数组的一部分

H. 字节流和字符流的使用场景

字节流: 可以拷贝任意类型的文件
字符流:
读取纯文本文件中的数据
往纯文本文件中写数据

I. 缓冲流

1. 字节缓冲流

字节缓冲输入流的构造方法:
public BufferedInputStream(InputStream is)
字节缓冲输出流的构造方法:
public BufferedOutputStream(OutputStream os)

1
2
3
4
5
6
7
8
9
10
11
//1.创建缓冲流的对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("myio\\a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myio\\copy.txt"));
//2.循环读取并写到目的地
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
//3.释放资源
bos.close();
bis.close();
2. 字符缓冲流

字符缓冲输入流
构造方法: public BufferedReader(Reader r)
特有方法: public String readLine() 读一整行

字符缓冲输出流
构造方法: public BufferedWriter(Writer r)
特有方法: public void newLine() 跨平台的换行

J. 转换流

是字符流和字节流之间的桥梁
字节流使用字符流中的方法就需要转换流

K. 序列化流/反序列化流

1. 序列化流/对象操作输出流

可以把java中的对象写到本地文件中
构造方法: public ObjectOutputStream(OutputStream out)把基本流变成高级流
成员方法: public final void writeObject(Object obj)把对象序列化(写出)到文件中去

1
2
3
4
5
6
7
8
//1.创建对象
Student stu = new Student("zhangsan",23);
//2.创建序列化流的对象/对象操作输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myio\\a.txt"));
//3.写出数据
oos.writeObject(stu);
//4.释放资源
oos.close();
2. 反序列化流/对象操作输入流

可以把序列化到本地文件中的对象读取到程序中
构造方法: public ObjectInputStream(InputStream out)把基本流变成高级流
成员方法: public Object readObject()把序列化到本地文件中的对象,读取到程序中来

3. 序列化流的细节

序列化对象不能修改Javabean类,否则不能反序列化
解决方案:在javabean类中添加版本号
private static final long serialVersionUID = -6357601841666449654L;

L. 打印流

  • 只有写,没有读
  • PrintStream和PrintWriter两个类
1. 字节打印流
构造方法 作用
public PrintStream(OutputStream/File/String) 关联字节输出流/文件/文件路径
public PrintStream(String fileName, Charset charset) 指定字符编码
public PrintStream(OutputStreamout, boolean autoFlush) 自动刷新
public PrintStream(OutputStream out, boolean autoFlush, String encoding) 指定字符编码且自动刷新
成员方法 作用
public void write(int b) 常规方法:规则跟之前一样,将指定的字节写出
public void println(Xxx xx) 特有方法:打印任意数据,自动刷新,自动换行
public void print(Xxx xx) 特有方法:打印任意数据,不换行
public void printf(String format, Object … args) 特有方法:带有占位符的打印语句,不换行
1
2
3
4
5
6
7
8
9
//1.创建字节打印流的对象
PrintStream ps = new PrintStream(new FileOutputStream( "myio\\a.txt"),true, "UTF-8");
//2.写出数据
ps.println(97);//写出+自动刷新+自动换行
ps.print(true);
ps.println();
ps.printf("%s爱上了%s","阿珍","阿强");
//3.释放资源
ps.close();
2. 字符打印流

字符打印流的构造方法和成员方法和字节打印流一样
字符流底层有缓冲区,想要自动刷新需要开启

M. 解压缩流/压缩流

1. 解压缩流
2. 压缩流

压缩本质:把每个文件/文件夹看出ZipEntry对象放到压缩包中

N. Commons-io和Hutool

有关IO操作的开源工具包,提高IO流的开发效率

4. 多线程

A. 什么是多线程

  • 进程是程序的基本执行实体
  • 线程是操作系统能够进行运算调度的最小单位。被包含在进程中,是进程的实际运作单位。
  • 有了多线程,就可以让程序同时做多件事情,提高效率

B. 并发和并行

  • 并发: 在同一时刻,有多个指令在单个CPU上交替执行
  • 并行: 在同一时刻,有多个指令在多个CPU上同时执行

C. 多线程的实现方式

1. 继承Thread类的方式
  1. 自己定义一个类继承Thread
  2. 重写run方法
  3. 创建子类的对象,并启动线程
1
2
3
4
5
6
7
8
9
10
11
12
public class MyThread extends Thread{
//重写run方法
}

MyThread t1 = new MyThread(); //MyThread是自己创建的类
MyThread t2 = new MyThread();

t1.setName("线程1");
t2.setName("线程2");

t1.start();
t2.start();
2. 实现Runnable接口的方式
  1. 自己定义一个类实现Runnable接口
  2. 重写里面的run方法
  3. 创建自己的类的对象
  4. 创建一个Thread类的对象,并开启线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyRun implements Runnable{
//重写run方法
}

//创建MyRun的对象
MyRun mr = new MyRun();

//创建线程对象
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);

//给线程设置名字
t1.setName("线程1");
t2.setName("线程2");

//开启线程
t1.start();
t2.start();
3. 利用Callable接口和Future接口

特点:可以获取到多线程运行的结果

  1. 创建一个类MyCallable实现Callable接口
  2. 重写call(是有返回值的,表示多线程运行的结果)
  3. 创建MyCallable的对象(表示多线程要执行的任务)
  4. 创建FutureTask的对象(作用管理多线程运行的结果)
  5. 创建Thread类的对象,并启动(表示线程)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyCallable implements Callable<Integer>{
//重写run方法
}

//创建MyCallable的对象(表示多线程要执行的任务)
MyCallable mc = new MyCallable();
//创建FutureTask的对象(作用管理多线程运行的结果)
FutureTask<Integer> ft = new FutureTask<>(mc);
//创建线程的对象
Thread t1 = new Thread(ft);
//启动线程
t1.start();

//获取多线程运行的结果
Integer result = ft.get();
System.out.println(result);|
4. 三种实现方式对比

D. Thread常见的成员方法

成员方法 作用
String getName() 返回此线程的名称
void setName(String name) 设置线程的名字(构造方法也可以设置名字)
static Thread currentThread() 获取当前线程的对象
static void sleep(long time) 让线程休眠指定的时间,单位为毫秒
setPriority(int newPriority) 设置线程的优先级
final int getPriority() 获取线程的优先级
final void setDaemon(boolean on) 设置为守护线程
public static void yield() 出让线程/礼让线程
public static void join() 插入线程/插队线程

细节:

  1. 如果我们没有给线程设置名字,线程也是有默认的名字的
    格式:Thread-X(X序号,从0开始的)
  2. 如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置
  3. 优先级:1~10,1最低,10最高(java是抢占式调度)
  4. 守护线程:当其他非守护线程执行完毕之后,守护线程会尽快结束不再进行

E. 线程的生命周期

F. 线程安全

当多个线程同时操作同一个资源时,可能会导致数据出错或程序行为异常

G. 同步代码块

把操作共享数据的代码锁起来
格式:

1
2
3
synchronized(锁){
操作共享数据的代码
}

特点:
1. 锁默认打开,有一个线程进去了,锁自动关闭
2. 里面的代码全部执行完毕,线程出来,锁自动打开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//表示这个类所有的对象,都共享ticket数据
static int ticket = 0;//0 ~ 99
//锁对象,一定要是唯一的
static Object obj = new Object();
@Override
public void run() {
while (true){
//同步代码块
synchronized (obj){
if(ticket < 100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票! ! ");
}else{
break;
}
}
}
}

H. 同步方法

就是把synchronized关键字加到方法上
格式:修饰符 synchronized 返回值类型 方法名(方法参数) {...}

  1. 同步方法是锁住方法里面所有代码
  2. 锁对象不能自己指定
    • 非静态:this
    • 静态:当前类的字节码文件对象

I. Lock锁

可以手动上锁、手动解锁

  • void lock(): 获得锁
  • void unlock(): 释放锁
  • Lock是接口不能直接实例化,采用它的实现类ReentrantLock来实例化
    • ReentrantLock():创建一个ReentrantLock的实例

J. 生产者和消费者(等待唤醒机制)

1. 常见方法
常见方法 作用
void wait() 当前线程等待,直到被其他线程唤醒
void notify() 随机唤醒单个线程
void notifyAll() 唤醒所有线程
2. 阻塞队列实现等待唤醒机制

生产者和消费者必须使用同一个阻塞队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}

@Override
public void run() {
while(true){
//不断从阻塞队列中获取面条
try {
String food = queue.take();
System.out.println(food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

K. 线程的六种状态

L. 线程池

1. 线程池主要原理
  1. 创建一个池子,池子中是空的
  2. 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
  3. 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
2. 代码实现:
  1. 创建线程池
  2. 提交任务
  3. 所有任务全部执行完毕,关闭线程池
    Executors:线程池的工具类通过调用方法返回不同类型的线程池对象
方法名称 作用
public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
public static ExecutorService newFixedThreadPool(int nThreads) 创建有上限的线程池
1
2
3
4
5
6
//1.获取线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();
//2.提交任务
pool1.submit(new MyRunnable());
//3.销毁线程池
pool1.shutdown();

M. 自定义线程池

创建线程池对象:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
参数一:核心线程数量 不能小于0
参数二:最大线程数 不能小于0,最大数量 >=核心线程数量
参数三:空闲线程最大存活时间 不能小于e
参数四:时间单位 用TimeUnit指定
参数五:任务队列 不能为null
参数六:创建线程工厂 不能为null
参数七:任务的拒绝策略 不能为null

1
2
3
4
5
6
7
8
9
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,//核心线程数量,能小于0
6, //最大线程数,不能小于0,最大数量>=核心线程数量
60,//空闲线程最大存活时间
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(3),//任务队列
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
);

5. 网络编程

计算机之间通过网络进行数据传输

A. CS架构和BS架构

  • C/S: Client/Server 客户端/服务器
  • B/S: Browser/Server 浏览器/服务器
    BS架构的优缺点
    • 不需要开发客户端,只需要页面+服务器
    • 用户不用下载,打开浏览器就能用
    • 如果应用过大,用户体验差

CS架构的优缺点

  • 画面更精美,用户体验好
  • 需要开发客户端,也要开发服务器
  • 用户下载和更新麻烦

B. 网络编程三要素

IP、端口号、协议

  1. IP
    • 设备在网络中的地址,是唯一的标识。
  2. 端口号
    • 应用程序在设备中唯一的标识。
  3. 协议
    • 数据在网络中传输的规则,常见的协议有UDP、TCP、http、https、ftp。

C. IP

1. IPv4

分为公网地址和私网地址(192.168.1.1~192.168.255.255)

  • 127.0.0.1: 本机地址
  • ipconfig:查看本机IP地址
  • ping:检查网络是否连通
2. InetAddress类
方法名 作用
static InetAddress getByName(String host) 确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
String getHostName() 获取此IP地址的主机名
String getHostAddress() 返回文本显示中的IP地址字符串
1
2
3
4
5
6
7
8
9
//获取InetAddress的对象
InetAddress address = InetAddress.getByName("DESKTOP-50JJSAM");
System.out.println(address);

String name = address.getHostName();
System.out.println(name);//DESKTOP-50JJSAM

String ip = address.getHostAddress();
System.out.println(ip);//192.168.1.100

D. 端口号

  • 取值范围0~65535
    其中0~1023用于知名应用或网络服务,自己只能用1024以上的端口号
  • 一个端口号只能被一个应用程序使用

E. 协议

计算机网络中,连接和通信的规则称为网络通信协议

1. UDP协议

用户数据报协议(User Datagram Protocol)

  • UDP是面向无连接通信协议。
  • 速度快,有大小限制一次最多发送64K,数据不安全,易丢失数据
2. TCP协议

传输控制协议TCP(Transmission Control Protocol)

  • TCP协议是面向连接的通信协议。
  • 速度慢,没有大小限制,数据安全。

F. UDP通信程序

1. 发送数据
  1. 创建发送端的DatagramSocket对象
  2. 数据打包(DatagramPacket)
  3. 发送数据
  4. 释放资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1. 创建对象
DatagramSocket ds = new DatagramSocket();

//2.打包数据
String str="你好!!!";
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10086;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length, address, port);

//3.发送数据
ds.send(dp);
//4.释放资源
ds.close();
2. 接收数据
  1. 创建接收端的DatagramSocket对象
  2. 接收打包好的数据
  3. 解析数据包
  4. 释放资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//1.接收的端口和发送的端口保持一致
DatagramSocket ds = new DatagramSocket(10086);

//2.接收数据包
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
ds.receive(dp);

//3.解析数据包
byte[] data = dp.getData();
int len = dp.getLength();
InetAddress address = dp.getAddress();
int port = dp.getPort();
System.out.println("按收到数据"+new String(data, 0,len));
System.out.println("该数据是从"+address+"这台电脑中的"+port+"这个端口发出的");

//4.释放资源
ds.close();
3. 单播组播广播
  • 单播:指定接收端的IP地址
  • 组播:224.0.0.0~239.255.255.255
  • 广播:255.255.255.255

G. TCP通信程序

  • TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象
  • 通信之前要保证连接已经建立
  • 通过Socket产生I0流来进行网络通信
1. 客户端
  1. 创建客户端的Socket对象(Socket)与指定服务端连接
    Socket(String host, int port)
  2. 获取输出流,写数据
    OutputStream getOutputStream()
  3. 释放资源
    void close()
1
2
3
4
5
6
7
8
9
10
//1.创建Socket对象
Socket socket = new Socket("127.0.0.1", 10000);

//2.可以从连接通道中获取输出流
OutputStream os = socket.getOutputStream();
os.write("你好你好".getBytes());

//3.释放资源
os.close();
socket.close();
2. 服务器端
  1. 创建服务器端的Socket对象(ServerSocket)
    ServerSocket (int port)
  2. 监听客户端连接,返回一个Socket对象
    Socket accept()
  3. 获取输入流,读数据,并把数据显示在控制台
    InputStream getInputStream()
  4. 释放资源
    void close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1.创建对象ServerSocker
ServerSocket ss = new ServerSocket(10000);

//2.监听客户端的链接
Socket socket = ss.accept();

//3.从连接通道中获取输入流读取数据
InputStream is = socket.getInputStream();
int b;
while ((b = is.read()) != -1){
System.out.println((char) b);
}

//4.释放资源
socket.close();
ss.close();
3. 三次握手和四次挥手


6. 反射

A. 什么是反射

反射允许对成员变量、成员方法和构造方法的信息进行编程访问
反射就是从类中拿东西

B. 获取class对象的三种方式

  1. Class.forName(“全类名”);(常用)
  2. 类名.class
  3. 对象.getClass();
1
2
3
4
5
6
7
8
9
//1. 第一种方式 全类名:包名+类名
Class clazz1 = Class.forName("com.itheima.myreflect1.Student");

//2. 第二种方式
Class clazz2 = Student.class;

//3.第三种方式
Student s = new Student();
Class clazz3 = s.getClass();

C. 反射获取构造方法

Class类中用于获取构造方法的方法 作用
Constructor<?>[] getConstructors() 返回所有公共构造方法对象的数组
Constructor<?>[] getDeclaredConstructors() 返回所有构造方法对象的数组
Constructor getConstructor(Class<?>…parameterTypes) 返回单个公共构造方法对象
ConstructorgetDeclaredConstructor(Class<?>… parameterTypes) 返回单个构造方法对象
Constructor类中用于创建对象的方法 作用
T newlnstance(Object …initargs) 根据指定的构造方法创建对象
setAccessible(boolean flag) 设置为true,表示取消访问检查

D. 反射获取成员变量

Class类中用于获取成员变量的方法 作用
Field[] getFields() 返回所有公共成员变量对象的数组
Field[] getDeclaredFields() 返回所有成员变量对象的数组
Field getField(String name) 返回单个公共成员变量对象
Field getDeclaredField(String name) 返回单个成员变量对象
Field类中用于创建对象的方法 作用
void set(Object obj, Object value) 赋值
Object get(Object obj) 获取值

E. 反射获取成员方法

Class类中用于获取成员方法的方法 作用
Method[] getMethods() 返回所有公共成员方法对象的数组,包括继承的
Method[] getDeclaredMethods() 返回所有成员方法对象的数组,不包括继承的
Method getMethod(String name,Class<?> … parameterTypes) 返回单个公共成员方法对象
Method getDeclaredMethod(String name,Class<?> … parameterTypes) 返回单个成员方法对象

Method类中用于创建对象的方法
Object invoke(Object obj,Object ...args):运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)

7. 动态代理

  • 无侵入式给代码增加额外的功能
  • 代理可以无侵入式给对象增加其他功能
    调用者–>代理–>对象
  • JAVA通过接口保证代理,接口就是被代理的所有方法
  • java.lang.reflect.Proxy类:提供为对象产生代理对象的方法

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数一:用于指定用哪个类加载器,去加载生成的代理类
参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
参数三:用来指定生成的代理对象要干什么事情

END