想写几篇短的,奈何短篇文章确实不利于之后的翻阅, 以及工作上效率的提升.考虑到这点, 于是乎将所有的Java进阶的内容全部放到一篇文章中了, 其实前面也有零零散散的写过一些进阶的和基础的文章.

一些话想对自己以及朋友们分享:

苏格拉底曾说过:“未经审视的人生,是不值得过的”。

他老人家是想表达:生活,不是通过世俗的物质方式去度过,而是要透过理性,审视人的生命的终极需求,从而引导出一种新的生活态度。

哲学角度有点高,也不是所有人每天都会去思考生命的终极需求。但不可否认的是,我们的人生,的确需要审视!

好啦,开始本篇进阶教程;首先进阶的最重要的就是面向对象以及面向接口编程思想.

面向对象

面向对象的三大主要特性就是封装, 继承, 多态.

在面向对象之前,还有几个关键字.

Static关键字

Static可以修饰成员变量,成员方法, 代码块

  • Static修饰成员变量的时候:该变量作为类变量, 被所有的对象所共享.应用场景为计数器
  • Static修饰成员方法的时候: 该方法作为静态方法, 可以通过类名.方法名 的形式直接调用, 避免了创建对象造成的内存浪费
  • Static修饰代码块的时候: 该代码块作为静态代码块会随着类的加载而加载, 一般用于给类变量赋值.
    • 与静态代码块向对应的是实例代码块, 作用和构造器是一样的, 都是用来给实例对象初始化.

参考链接

继承

继承就是将相同的内容合并到一个父类中, 简化了代码提高了代码的复用性

封装就是将成员变量私有化, 提高了代码的安全性

参考链接:

【Java进阶】继承及应用场景 | 书下酒云赠人 (liamjohnson-w.github.io)

【Java进阶】继承之方法的重写 | 书下酒云赠人 (liamjohnson-w.github.io)

接口和多态

内部类

To be continue…..

枚举

To be continue…..

泛型

To be continue…..

常用API

StringBuilder

概述

是java编写的一个专门用于字符串拼接的类,可以当成一个字符串拼接的容器使用,提供了对容器中的数据进行增删改的功能,并提升了字符串的拼接效率;

作用

  1. 提供了字符串拼接,反转,修改内容的方法,弥补了字符串对象不能修改内容的缺点;
  2. 提升了字符串拼接的效率;

使用方式

  • 由于类中的方法都是非静态的,而且字符串工具,你没有个对象调什么方法,所以还是老老实实造对象吧。
  1. 利用构造方法创建对象;
  2. 面向创建好的对象直接调用类中的方法即可;

常用方法

方法介绍

  • 空参,全参构造
  • append(任意类型):将任意的数据类型追加到StringBuilder对象的末尾并返回对象本身
  • reverse():反转sb对象
  • 对象名.length():返回对象的长度
  • 对象名.toString():将StringBuilder对象转为String基本数据类型

image-20240124213045106

代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
练习 StringBuilder入门
*/
public class StringBuilderCourse {
public static void main(String[] args) {
// 1, 利用空参的构造方法创建对象
StringBuilder sb = new StringBuilder();
// 2, 拼接数据
sb.append(123);
sb.append(true);
sb.append("heihei");
sb.append(5.6);
sb.append('好');
System.out.println(sb);
// 3, 反转内容
sb.reverse();
System.out.println(sb);
}
}

StringBuilder应用场景

StringBuilder的两个常见的应用场景为:

  • 拼接数组中的元素
  • 判断某个字符串是否为对称字符串

Example1

说实话:Python中一行代码顶Java100行,下面代码等于一句','.join(list)

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
/*
自定义一个方法,完成对数组内容按指定格式拼接的功能
Analysis:
参数:需要的数组
返回值:结果字符串
*/
public class StringBuilderExample1 {
public static void main(String[] args) {
int[] arr = {4, 7, 8, 9, 5, 3};
// String s = toString(arr);
// 下面使用的是工具类,可以理解为不需要造对象直接调用,所以要用Static关键字
String res = toString(arr);
System.out.println(res);
}
public static String toString(int[] arr){
// 0:对参数进行健壮性校验
if (arr == null){
return null;
}
if (arr.length == 0){
return "[]";
}
// 1,提前准备一个StringBuilder用于拼接数据
StringBuilder sb = new StringBuilder("[");
// 2,循环拼接数据
for (int i = 0; i < arr.length; i++) {
// 3, 先拼接数据
sb.append(arr[i]);
// 4, 判断是否为最后一个元素,如果是则加上]
if (i != arr.length -1){
sb.append(',');
}else {
sb.append(']');
}
}
return sb.toString();
}
}

Example2

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
/*
定义一个方法,判断一个字符串是否是对称字符串
分析:
参数:需要的字符串
返回值:返回布尔类型的值boolean
*/
public class JudgeSymmetry {
public static void main(String[] args) {
String s = "accd";
System.out.println(symmetry(s));
}

public static boolean symmetry(String s){
// 1,为了将字符串s进行反转,需要将s转为StringBuilder类型
StringBuilder sb = new StringBuilder(s);
StringBuilder newSb = sb.reverse();
String newStr = newSb.toString();
if (newStr.equals(s)){
return true;
}else {
return false;
}

}
}

StringJoiner

概述

是java8提供的一个专门针对有规律的字符串拼接的一个类;

作用

  1. 能保证字符串的拼接效率;
  2. 能保证拼接字符串的简洁度;

使用场景

  1. 只能针对字符串或字符串缓冲区拼接;
  2. 必须保证要拼接的结果是有规律的!

常用方法

跟StringBuilder类的方法区别就在于StringBuilder 添加元素是append,而StringJoiner是add

  • 空参,全参构造方法
  • add(基本数据类型的数据):添加数据,类似于StringBuilder中的append
  • length:这个不多说了
  • toString:这个是个Stringxxx的方法都支持的吧

image-20240124214319741

代码案例

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
/*
定义一个方法,判断一个字符串是否是对称字符串
分析:
参数:需要的字符串
返回值:返回布尔类型的值boolean
*/
public class JudgeSymmetry {
public static void main(String[] args) {
String s = "accd";
System.out.println(symmetry(s));
}

public static boolean symmetry(String s){
// 1,为了将字符串s进行反转,需要将s转为StringBuilder类型
StringBuilder sb = new StringBuilder(s);
StringBuilder newSb = sb.reverse();
String newStr = newSb.toString();
if (newStr.equals(s)){
return true;
}else {
return false;
}

}
}

Math

概述

java提供的专门进行数学运算的工具类;所有方法都是静态方法,通过类名直接调用即可;

常用方法

image-20240124214648920

代码案例

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 MyMath {
public static void main(String[] args) {
// Math中的工具方法练习(直接通过类名.方法名调用)
System.out.println(Math.abs(-5)); // 5
// 向上取整
System.out.println(Math.ceil(3.01)); // 4.0

// 向下取整
System.out.println(Math.floor(5.9)); // 5.0

// 四舍五入
System.out.println(Math.round(5.5)); // 6

// 获取两个int值中的较大值
System.out.println(Math.max(5.0,-4)); // 5.0

// 返回a的b次幂
System.out.println(Math.pow(2, 3)); // 8.0

// 返回职位double的随机值,范围在0.0 - 1.0之间
System.out.println(Math.random()); // 0.5404016363759777
}
}

System

概述

专门用于描述系统相关操作的工具类,里面所有的方法都是静态的!

常用方法

image-20240124214850660da

代码案例

1
2
3
4
5
6
7
8
9
10
11
12
/*
练习系统工具类
*/
public class MySystem {
public static void main(String[] args) {
System.out.println("嘿嘿");
//System.exit(0); // 直接退出虚拟机
System.out.println("看看能执行吗");
// 获取当前时间毫秒值
System.out.println(System.currentTimeMillis());
}
}

Runtime

概述

与当前程序应用环境相关的工具类,获取对象的时候,利用 静态的getRuntime()方法,获取对象后,利用对象调用成员方法;

常用方法

image-20240124215012039

代码案例

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
import java.io.IOException;
import java.util.Scanner;

public class MyRuntime {
public static void main(String[] args) throws IOException {
// 1, 获取当前程序的系统运行对象
Runtime r = Runtime.getRuntime();

// 2, 查看CPU核数
System.out.println(r.availableProcessors());

// 3, 获取Java虚拟机的总内存和可用内存
System.out.println(r.totalMemory() / 1024 / 1024 + "MB");
System.out.println(r.freeMemory() / 1024 / 1024 + "MB");

// 4, 调用系统中的第三方程序(这里必须在主程序入口抛出 IOException)
// 打开记事本
Process process = r.exec("notepad");
Scanner sc = new Scanner(System.in);
System.out.println("请输入任意内容,让程序继续执行!");
sc.next();

// 5, 通过process 对象,关闭记事本
process.destroy();

}
}

BigDecimal

概述

java编写的专门用于精确运算的类;

作用

对小数可以完成精确运算;

使用方式

  1. 创建对象
  2. 调用方法

获取对象的方式

  • 方式一:利用构造方法;
    • public BigDecimal(String val) -- 把String变成BigDecimal
  • 方式二:利用静态方法;
    • public static BigDecimal valueOf(double val) -- 把double类型的变量变成BigDecimal

常用成员方法(必须要有对象

image-20240124215528590

代码案例

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
import java.math.BigDecimal;

public class MyBigDecimal {
public static void main(String[] args) {
// 如果使用double直接运算,会出现进度损失的现象,应该使用BigDecimal进行精确运算
System.out.println(0.2 + 0.1); // 0.30000000000000004 出现了精度损失

// 1, 创建两个BigDecimal对象
BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = BigDecimal.valueOf(0.2);

// 2, 使用其中一个对象调用add方法,传递另一个对象就可以完成相加的操作了
BigDecimal add = bd1.add(bd2);

// 3, 面向add对象,获取里面保存的数据
double v = add.doubleValue();
System.out.println(v); // 0.3

// 练习减 操作
System.out.println(bd1.subtract(bd2).doubleValue());
// 练习乘除 操作
System.out.println(bd1.multiply(bd2).doubleValue());
System.out.println(bd1.divide(bd2).doubleValue());
}
}

Notice: divide方法除不尽情况

可以在divide方法中传递3个参数,第1个参数是除数,第2个参数是保留的小数位数,第3个参数是java写好的枚举类中罗列的舍入模式;

比较常见的舍入模式有3个:UP, DOWN, HALF_UP
Tips: RoundingMode(枚举类名)

CodeDemo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyBigDecimal_pro {
public static void main(String[] args) {
// 1: 创建两个BigDecimal对象
BigDecimal bd1 = new BigDecimal("0.2");
BigDecimal bd2 = BigDecimal.valueOf(0.3);
// 2: 当除不尽的时候,向上取整,保留3位小数
//BigDecimal divide = bd1.divide(bd2, 3, RoundingMode.UP);
// 向下取整
//BigDecimal divide = bd1.divide(bd2, 3, RoundingMode.DOWN);
// 四舍五入
BigDecimal divide = bd1.divide(bd2, 3, RoundingMode.HALF_UP);
System.out.println(divide.doubleValue());
}
}

JDk7日期

Date概述

是java7以前提供的用于表示时间点的类,以1970年1月1日0点0分0秒为时间原点;

里面很多方法都被废弃了。

常用方法

image-20240124220315185

代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.Date;

public class MyDate {
public static void main(String[] args) {
// 1, 创建当前时间点对象
Date d = new Date();
System.out.println(d);

// 将日期转成本地日期格式
System.out.println(d.toLocaleString());

// 2, 创建指定毫秒值得时间点对象
Date d1 = new Date(-1000 * 60 * 60 * 24 * 7);
System.out.println(d1.toLocaleString());

System.out.println(d1.getTime());
System.out.println(d.getTime());
System.out.println(System.currentTimeMillis());
}
}

JDK7的日期格式化工具类

SimpleDateFormat概述

是java专门针对Date类提供的工具类;可以将Date和字符串之间进行相互转换!

如何使用

  1. 创建工具对象;(一般会指定字符串的格式,既模式字符串)
  2. 使用工具对象,调用方法,即可完成字符串和Date的相互转换;

常用方法

格式化

1
将Date转成字符串,这个过程叫做格式化;对应的方法名: format(日期对象);

解析

1
将字符串转成Date,这个过程叫做解析;对应的方法名: parse(字符串);

image-20240124221316324

Calendar

概述

是java7提供的一个用于表示日历的类,可以对时间进行推移运算;

使用方式

  1. 利用静态方法获取对象;
  2. 面向对象,调用方法;

常用方法

image-20240124221404649

代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Calendar;

public class MyCalendar {
public static void main(String[] args) {
// 1, 获取一个日历对象
Calendar ins = Calendar.getInstance();
// 2, 面向日历获取信息
int y = ins.get(Calendar.YEAR);
int m = ins.get(Calendar.MONTH); // 获取的是月份对应的索引(1月为0,12月为11
int d = ins.get(Calendar.DATE);
System.out.println("年份为:" + y + "; 月份为:" + m + "; 日期为:" + d);

// 3, 对时间进行推移
ins.add(Calendar.MONTH, -1);
System.out.println(ins.get(Calendar.MONTH));

}
}

日期类

JDk7日期

Date概述

是java7以前提供的用于表示时间点的类,以1970年1月1日0点0分0秒为时间原点;

里面很多方法都被废弃了。

常用方法

image-20240124220315185

代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.Date;

public class MyDate {
public static void main(String[] args) {
// 1, 创建当前时间点对象
Date d = new Date();
System.out.println(d);

// 将日期转成本地日期格式
System.out.println(d.toLocaleString());

// 2, 创建指定毫秒值得时间点对象
Date d1 = new Date(-1000 * 60 * 60 * 24 * 7);
System.out.println(d1.toLocaleString());

System.out.println(d1.getTime());
System.out.println(d.getTime());
System.out.println(System.currentTimeMillis());
}
}

JDK7的日期格式化工具类

SimpleDateFormat概述

是java专门针对Date类提供的工具类;可以将Date和字符串之间进行相互转换!

如何使用

  1. 创建工具对象;(一般会指定字符串的格式,既模式字符串)
  2. 使用工具对象,调用方法,即可完成字符串和Date的相互转换;

常用方法

格式化

1
将Date转成字符串,这个过程叫做格式化;对应的方法名: format(日期对象);

解析

1
将字符串转成Date,这个过程叫做解析;对应的方法名: parse(字符串);

image-20240124221316324

Calendar

概述

是java7提供的一个用于表示日历的类,可以对时间进行推移运算;

使用方式

  1. 利用静态方法获取对象;
  2. 面向对象,调用方法;

常用方法

image-20240124221404649

代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Calendar;

public class MyCalendar {
public static void main(String[] args) {
// 1, 获取一个日历对象
Calendar ins = Calendar.getInstance();
// 2, 面向日历获取信息
int y = ins.get(Calendar.YEAR);
int m = ins.get(Calendar.MONTH); // 获取的是月份对应的索引(1月为0,12月为11
int d = ins.get(Calendar.DATE);
System.out.println("年份为:" + y + "; 月份为:" + m + "; 日期为:" + d);

// 3, 对时间进行推移
ins.add(Calendar.MONTH, -1);
System.out.println(ins.get(Calendar.MONTH));

}
}

JDK7和8日期类比较

JDK8新增的日期类分得更细致一些,比如表示年月日用LocalDate类、表示时间秒用LocalTime类、而表示年月日时分秒用LocalDateTime类等;除了这些类还提供了对时区、时间间隔进行操作的类等,用起来特别方便。

image-20240127172344465

详细说明

image-20240127173048482

JDK8日期

Notes: 这里part1,2,3,4是上图中的四大模块。

PART1

LocalDate、LocalTime、以及LocalDateTime类。这三个类的用法基本大同小异,由于我之前偷懒就写了一个Demo,这里就copy一下别人的代码。

LocalDate类的基本使用

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
public class Test1_LocalDate {
public static void main(String[] args) {
// 0、获取本地日期对象(不可变对象)
LocalDate ld = LocalDate.now(); // 年 月 日
System.out.println(ld);

// 1、获取日期对象中的信息
int year = ld.getYear(); // 年
int month = ld.getMonthValue(); // 月(1-12)
int day = ld.getDayOfMonth(); // 日
int dayOfYear = ld.getDayOfYear(); // 一年中的第几天
int dayOfWeek = ld.getDayOfWeek().getValue(); // 星期几
System.out.println(year);
System.out.println(day);
System.out.println(dayOfWeek);

// 2、直接修改某个信息: withYear、withMonth、withDayOfMonth、withDayOfYear
LocalDate ld2 = ld.withYear(2099);
LocalDate ld3 = ld.withMonth(12);
System.out.println(ld2);
System.out.println(ld3);
System.out.println(ld);

// 3、把某个信息加多少: plusYears、plusMonths、plusDays、plusWeeks
LocalDate ld4 = ld.plusYears(2);
LocalDate ld5 = ld.plusMonths(2);

// 4、把某个信息减多少:minusYears、minusMonths、minusDays、minusWeeks
LocalDate ld6 = ld.minusYears(2);
LocalDate ld7 = ld.minusMonths(2);

// 5、获取指定日期的LocalDate对象: public static LocalDate of(int year, int month, int dayOfMonth)
LocalDate ld8 = LocalDate.of(2099, 12, 12);
LocalDate ld9 = LocalDate.of(2099, 12, 12);

// 6、判断2个日期对象,是否相等,在前还是在后: equals isBefore isAfter
System.out.println(ld8.equals(ld9));// true
System.out.println(ld8.isAfter(ld)); // true
System.out.println(ld8.isBefore(ld)); // false
}
}

LocalTime类的基本使用

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
public class Test2_LocalTime {
public static void main(String[] args) {
// 0、获取本地时间对象
LocalTime lt = LocalTime.now(); // 时 分 秒 纳秒 不可变的
System.out.println(lt);

// 1、获取时间中的信息
int hour = lt.getHour(); //时
int minute = lt.getMinute(); //分
int second = lt.getSecond(); //秒
int nano = lt.getNano(); //纳秒

// 2、修改时间:withHour、withMinute、withSecond、withNano
LocalTime lt3 = lt.withHour(10);
LocalTime lt4 = lt.withMinute(10);
LocalTime lt5 = lt.withSecond(10);
LocalTime lt6 = lt.withNano(10);

// 3、加多少:plusHours、plusMinutes、plusSeconds、plusNanos
LocalTime lt7 = lt.plusHours(10);
LocalTime lt8 = lt.plusMinutes(10);
LocalTime lt9 = lt.plusSeconds(10);
LocalTime lt10 = lt.plusNanos(10);

// 4、减多少:minusHours、minusMinutes、minusSeconds、minusNanos
LocalTime lt11 = lt.minusHours(10);
LocalTime lt12 = lt.minusMinutes(10);
LocalTime lt13 = lt.minusSeconds(10);
LocalTime lt14 = lt.minusNanos(10);

// 5、获取指定时间的LocalTime对象:
// public static LocalTime of(int hour, int minute, int second)
LocalTime lt15 = LocalTime.of(12, 12, 12);
LocalTime lt16 = LocalTime.of(12, 12, 12);

// 6、判断2个时间对象,是否相等,在前还是在后: equals isBefore isAfter
System.out.println(lt15.equals(lt16)); // true
System.out.println(lt15.isAfter(lt)); // false
System.out.println(lt15.isBefore(lt)); // true

}
}

LocalDateTime类的基本使用

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
49
50
51
52
53
54
55
public class Test3_LocalDateTime {
public static void main(String[] args) {
// 0、获取本地日期和时间对象。
LocalDateTime ldt = LocalDateTime.now(); // 年 月 日 时 分 秒 纳秒
System.out.println(ldt);

// 1、可以获取日期和时间的全部信息
int year = ldt.getYear(); // 年
int month = ldt.getMonthValue(); // 月
int day = ldt.getDayOfMonth(); // 日
int dayOfYear = ldt.getDayOfYear(); // 一年中的第几天
int dayOfWeek = ldt.getDayOfWeek().getValue(); // 获取是周几
int hour = ldt.getHour(); //时
int minute = ldt.getMinute(); //分
int second = ldt.getSecond(); //秒
int nano = ldt.getNano(); //纳秒

// 2、修改时间信息:
// withYear withMonth withDayOfMonth withDayOfYear withHour
// withMinute withSecond withNano
LocalDateTime ldt2 = ldt.withYear(2029);
LocalDateTime ldt3 = ldt.withMinute(59);

// 3、加多少:
// plusYears plusMonths plusDays plusWeeks plusHours plusMinutes plusSeconds plusNanos
LocalDateTime ldt4 = ldt.plusYears(2);
LocalDateTime ldt5 = ldt.plusMinutes(3);

// 4、减多少:
// minusDays minusYears minusMonths minusWeeks minusHours minusMinutes minusSeconds minusNanos
LocalDateTime ldt6 = ldt.minusYears(2);
LocalDateTime ldt7 = ldt.minusMinutes(3);


// 5、获取指定日期和时间的LocalDateTime对象:
// public static LocalDateTime of(int year, Month month, int dayOfMonth, int hour,
// int minute, int second, int nanoOfSecond)
LocalDateTime ldt8 = LocalDateTime.of(2029, 12, 12, 12, 12, 12, 1222);
LocalDateTime ldt9 = LocalDateTime.of(2029, 12, 12, 12, 12, 12, 1222);

// 6、 判断2个日期、时间对象,是否相等,在前还是在后: equals、isBefore、isAfter
System.out.println(ldt9.equals(ldt8));
System.out.println(ldt9.isAfter(ldt));
System.out.println(ldt9.isBefore(ldt));

// 7、可以把LocalDateTime转换成LocalDate和LocalTime
// public LocalDate toLocalDate()
// public LocalTime toLocalTime()
// public static LocalDateTime of(LocalDate date, LocalTime time)
LocalDate ld = ldt.toLocalDate();
LocalTime lt = ldt.toLocalTime();
LocalDateTime ldt10 = LocalDateTime.of(ld, lt);

}
}

ZoneId时区

概述
1
表示时区对象;
获取方法
1
2
3
利用静态方法 of(字符串时区); 获取指定时区对象
利用静态方法 systemDefault();获取当前系统默认时区对象
利用静态方法 getAvailableZoneIds();获取当前系统支持的所有时区对象
应用场景
1
把时区对象,当成 ZoneDateTime的参数,获取一个带时区的日期对象;
代码案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class MyZoneDate {
public static void main(String[] args) {
// 1, 获取系统默认的时区对象
System.out.println(ZoneId.systemDefault());

// 2, 查询所有支持的时区
System.out.println(ZoneId.getAvailableZoneIds());

// 3, 根据名称获取时区对象
ZoneId of = ZoneId.of("America/New_York");
System.out.println(of);

// 4, 获取带时区的日期对象
System.out.println(ZonedDateTime.now());
System.out.println(ZonedDateTime.now(of));

// 5,获取,修改,推移时间信息 参考LocalDateTime
System.out.println(ZonedDateTime.now(of).getDayOfMonth());
}
}

PART2(Instant)

通过获取Instant的对象可以拿到此刻的时间,该时间由两部分组成:从1970-01-01 00:00:00 开始走到此刻的总秒数+不够1秒的纳秒数。

概述

1
用于表示时间戳的日期对象,可以精确到纳秒;

获取方法

1
利用静态方法 now(); 

常用方法

image-20240127180026656

代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.time.Instant;

/*
时间戳对象:可以精确到纳秒
*/
public class MyInstant {
public static void main(String[] args) {
// 1, 利用静态方法获取对象
Instant now = Instant.now();
System.out.println(now);
// 2, 获取总描述
System.out.println(now.getEpochSecond());
System.out.println(System.currentTimeMillis());

// 3, 时间推移
Instant instant = now.plusSeconds(-30);
System.out.println(instant);
}
}

PART3(DateTimeFormatter)

概述

1
是jdk8提供的日期格式化的工具类,可以当成日期格式化和解析日期时候的参数使用;

获取方式

1
静态方法: ofPattern("模式字符串");

解析和格式化的方法

  1. 解析,使用 日期类(LocalDateTime)中的静态方法 parse,传递要解析的字符串和格式化工具对象;
  2. 格式化,使用 日期对象 format(格式化工具对象);

image-20240127180726137

代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
思想转变:以日期类或日期对象为主导地位,以工具对象为辅助地位
*/
public class MyDateTimeFormatter {
public static void main(String[] args) {
// 1, 获取一个工具对象
DateTimeFormatter sf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 2, 获取当前时间对象
LocalDateTime now = LocalDateTime.now();
// 3, 格式化
System.out.println(now.format(sf));
// 4, 解析
LocalDateTime dt = LocalDateTime.parse("2021-01-01 11:11:11", sf);
System.out.println(dt);
}
}

PART4(Period&Duration)

概述

1
把一个时间段看成了对象,让程序员可以面向这个时间段操作;

分类

Period: 粗粒度间隔对象

Duration: 细粒度间隔对象

获取方式

利用静态方法 between(两个时间对象)

注意事项

Period能操作只能操作LocalDate;

Duration只能操作带毫秒的时间对象;(除了LocalDate之外的时间对象)

常用方法

  1. getXxx(); 获取信息或 toXxx()获取信息
  2. Period

image-20240127180813814

  1. Duration

image-20240127180851302

Period代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class PeriodTest {
public static void main(String[] args) {
// 1, 获取两个时间对象
LocalDate now = LocalDate.now();
LocalDate of = LocalDate.of(2024, 1, 9);
// 2, 利用静态对象获取时间间隔
Period between = Period.between(of, now);
// 3, 面向间隔时间对象,获取信息
System.out.println(between.getYears());
System.out.println(between.getMonths());
System.out.println(between.getDays());
System.out.println(between.toTotalMonths());
}
}

Duration代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
概述:把一个时间段看成对象,让程序员可以面向这个时间段操作
Period:粗粒度间隔对象
Duration:细粒度间隔对象
Tips:
Period 能且只能操作LocalDate
Duration 只能操作带毫秒的时间对象,除了LocalDate之外的时间对象
*/
public class DurationTest {
public static void main(String[] args) {
// 1, 获取两个时间对象
LocalDateTime now = LocalDateTime.now();
LocalDateTime of = LocalDateTime.of(2024, 1, 25, 01, 02, 03);
// 2, 利用静态方法获取间隔对象
Duration between = Duration.between(of, now);
// 3, 获取间隔时间
System.out.println(between.toDays());
System.out.println(between.toHours());
}
}

Lambda表达式

什么是Lambda表达式:用于简化匿名内部类代码的书写。

Lambda表达式基本使用

Lamdba是有特有的格式的,按照下面的格式来编写Lamdba。

1
2
3
(被重写方法的形参列表) -> {
被重写方法的方法体代码;
}

需要给说明一下的是,在使用Lambda表达式之前,必须先有一个接口,而且接口中只能有一个抽象方法(注意:不能是抽象类,只能是接口),使用Lambda来实现该接口,就形成了Lambda表达式。

而在使用Lambda的时候的参数则参考接口中的抽象方法需不需要参数及返回值

像这样的接口,我们称之为函数式接口(FunctionInterface),只有基于函数式接口的匿名内部类才能被Lambda表达式简化。

像这样

1
2
3
4
@FunctionalInterface
public interface Swimming{
void swim();
}

代码案例

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
JAVA
public interface Swimming {
void swim();
}

public class LambdaTest {
public static void main(String[] args) {
// 想要调用go方法必须传递一个Swimming类型的对象
go(new Swimming() {
@Override
public void swim() {
System.out.println("匿名内部类方法执行了");
}
});
// 使用Lambda表达式简化上面的匿名内部类(由于接口不需要参数,所以不用传递
go(() -> {
System.out.println("Lambda表达式执行了");
});

}

public static void go(Swimming s){
s.swim();
}
}

使用Lambda简化数组排序

基本类型数组

由于接口Comparable只能传递泛型,所以基本类型不能作为参数传递进去,这里基本类型只能对其长度进行规则排序。

代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
JAVA
import java.util.Arrays;
import java.util.Comparator;

public class MyStringTest {
public static void main(String[] args) {
String[] arr = {"a", "bb", "ccc", "dddd", "e"};
// 自己创建一个规则对象
Arrays.sort(arr, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length(); // 前减后升序, 后减前降序
}
});
System.out.println(Arrays.toString(arr));

System.out.println("可以使用Lambda表达式简化对数组的排序规则");
Arrays.sort(arr, (a, b) -> b.length() - a.length());
System.out.println(Arrays.toString(arr));
}
}

泛型类型数组

代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
JAVA
/*
使用Lambda简化对数组的排序规则
*/
public class LambdaStringSort {
public static void main(String[] args) {
// 如果想要使用Comparator进行排序,不能使用基本类型的数组,因为Comparator的参数是泛型
Integer[] arr = {1, 5, 8, 2, 3, 9, 6};
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
System.out.println(Arrays.toString(arr));
System.out.println("使用Lambda对其进行简化");
Arrays.sort(arr, (a, b) -> b - a);
System.out.println(Arrays.toString(arr));
}
}

Lambda表达式省略规则(※)

Java觉得代码还不够简单,于是还提供了Lamdba表达式的几种简化写法。具体的简化规则如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PROPERTIES
1.Lambda的标准格式
(参数类型1 参数名1, 参数类型2 参数名2)->{
...方法体的代码...
return 返回值;
}

2.在标准格式的基础上()中的参数类型可以直接省略
(参数名1, 参数名2)->{
...方法体的代码...
return 返回值;
}

3.如果{}总的语句只有一条语句,则{}可以省略、return关键字、以及最后的“;”都可以省略
(参数名1, 参数名2)-> 结果

4.如果()里面只有一个参数,则()可以省略
(参数名)->结果

代码案例

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
49
50
51
52
53
54
55
56
57
58
59
60
JAVA
public class LambdaTest2 {
public static void main(String[] args) {
// 目标:使用Lambda简化函数式接口。
double[] prices = {99.8, 128, 100};
//1.对数组中的每一个元素*0.8: 匿名内部类写法
Arrays.setAll(prices, new IntToDoubleFunction() {
@Override
public double applyAsDouble(int value) {
// value = 0 1 2
return prices[value] * 0.8;
}
});
//2.需求:对数组中的每一个元素*0.8,使用Lambda表达式标准写法
Arrays.setAll(prices, (int value) -> {
return prices[value] * 0.8;
});
//3.使用Lambda表达式简化格式1——省略参数类型
Arrays.setAll(prices, (value) -> {
return prices[value] * 0.8;
});
//4.使用Lambda表达式简化格式2——省略()
Arrays.setAll(prices, value -> {
return prices[value] * 0.8;
});
//5.使用Lambda表达式简化格式3——省略{}
Arrays.setAll(prices, value -> prices[value] * 0.8 );

System.out.println(Arrays.toString(prices));

System.out.println("------------------------------------

Student[] students = new Student[4];
students[0] = new Student("蜘蛛精", 169.5, 23);
students[1] = new Student("紫霞", 163.8, 26);
students[2] = new Student("紫霞", 163.8, 26);
students[3] = new Student("至尊宝", 167.5, 24);

//1.使用匿名内部类
Arrays.sort(students, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
}
});
//2.使用Lambda表达式表达式——标准格式
Arrays.sort(students, (Student o1, Student o2) -> {
return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
});
//3.使用Lambda表达式表达式——省略参数类型
Arrays.sort(students, ( o1, o2) -> {
return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
});
//4.使用Lambda表达式表达式——省略{}
Arrays.sort(students, ( o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight()));


System.out.println(Arrays.toString(students));
}
}

集合Collection

单列集合派系(List&Set)

单列集合是单个的元素,类似于Python中的列表。

容器中的数据都是相互独立的,互不干涉;

image-20240214173151676

Collection集合常用方法

image-20240214205846050

代码案例(演示常用方法)

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 MyCollection {
public static void main(String[] args) {
// 1, 创建集合对象
Collection<String> c = new ArrayList<>();
c.add("aa");
c.add("bb");
c.add("cc");
Collection<String> c2 = new ArrayList<>();
System.out.println(c);
System.out.println(c2);
c2.addAll(c);
System.out.println(c2);

// 2, 判断集合中是否包含某个指定的元素, contains 方法判断的时候,依赖对象的equals方法
System.out.println(c2.contains(new String("dd")));

// 由于多态的限制,不能使用子类特有的方法,如果通过索引操作,只能将集合转成数组然后再操作
// c.get(0);

// 提前创建对应长度的数组, 用于保存集合中的元素
String[] arr = new String[c.size()];
c.toArray(arr);
System.out.println(Arrays.toString(arr));
}
}

List列表

List 主要分为ArrayList和LinkedList两种类型的列表。

记住:list集合的特点是:带索引→元素有序,元素可重复。

ArrayList集合特点

是数组结构,增删慢,查询快;适合经常查询,很少增删的场景;

LinkedList集合的特点

是双向链表结构,增删快,查询慢;适合经常增删或经常操作首尾,很少查询的场景;

ArrayList代码案例(多态)

上面Collection代码案例就是。

Set集合

Set 集合最大的特点就是:不带索引 →元素无序,元素不可重复。

HashSet

特点

  • 同一个对象,多次调用hashCode方法一定可以得到一个相同的值;

  • 如果两个对象比较的结果是true,那么哈希值一定相同;

  • 如果两个对象比较的结果是false,那么哈希值可能相同也可能不同;

    • 比如说没有重写hashcode和equals方法的时候

结论

如果使用Hash结构的集合,去除重复元素的依据有两个:

  1. 依赖对象的哈希值;(决定对象在哈希表中存储的位置)
  2. 依赖对象的equals方法;(确保安全)
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// 学生实体类 JavaBean
public class Student {
private String name;
private int age;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

Student student = (Student) o;

if (age != student.age) return false;
return Objects.equals(name, student.name);
}

@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

public String getName() {
return name;
}

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

public int getAge() {
return age;
}

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

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

// HashSet测试类
public class TestHashSet {
public static void main(String[] args) {
// 1, 创建集合
HashSet<Student> set = new HashSet<>();
// 2, 常用方法 参考Collection
Student s1 = new Student("张三", 18);
Student s2 = new Student("张三", 18);
Student s3 = new Student("张三", 18);
set.add(s1);
set.add(s2);
set.add(s3);
System.out.println(set);
// 如果没有在JavaBean类中重写equals和hashcode方法,则三个属性都相同的对象的hashcode就不同
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
}
}
Tips:
  • 如果注释掉JavaBean学生实体类中的equals方法,则会将三个相同的对象存进HashSet集合中,但是三者的Hash值相同

    image-20240221215042647

  • 如果注释掉Hashcode方法,则equals都不会执行

    • 因为:第一个对象来了不比较
    • 第二个对象来了和第一个对象比较,发现Hash值不同,则存进集合
    • 第三个对象来了比较发现Hash值也不同与前两个,所以也存进集合

总结:HashSet添加对象的时候先调用的是Hashcode方法,如果没有重写Hashcode方法则三个属性相同的对象的hash值也不相同

TreeSet

是set系列的可以对元素自动排序的集合容器;(需要我们自己指定排序规则)

特点

  1. 可以排序
  2. 不能保证添加元素和获取元素顺序一致
  3. 无索引
  4. 不能添加重复的元素;(判定元素是否重复不依赖对象的hashcode和equals方法,仅仅依赖排序规则是否返回了0)

构造方法

  1. 空参数的构造方法
  2. 带比较器参数的构造方法;

常用方法

(参考Collection即可)

通过第三个代码案例:

  • TreeSet判断元素重复的依据是看重写的排序Lambda方法是否返回了0
    • 返回0表示重复
    • 不为0表示不重复
    • 如果在排序规则中直接写return 0,则TreeSet集合只会取第一个元素。
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
49
50
51
52
53
54
55
56
// 简单案例,如果不定义TreeSet的排序规则,则默认是升序排序,且元素不能重复
public class TreeSetSimple {
public static void main(String[] args) {
// 1, 存参数
TreeSet<Integer> set = new TreeSet<>();
set.add(22);
set.add(22);
set.add(11);
set.add(88);
set.add(22);
set.add(99);
System.out.println(set);
}
}


// 指定倒序排序规则
public class TreeSetSimple {
public static void main(String[] args) {
// 1, 存参数
TreeSet<Integer> set = new TreeSet<>((a, b) -> b -a);
set.add(22);
set.add(22);
set.add(11);
set.add(88);
set.add(22);
set.add(99);
System.out.println(set);
}
}


// 使用TreeSet保存学生对象,按照学生的年龄排序,年龄相同,按照姓名升序排序
public class MyTreeset {
public static void main(String[] args) {
// 1, 创建集合 由于Student类没有实现接口,必须指定排序规则
TreeSet<Student> set = new TreeSet<>((stu1, stu2)->{
// 按照年龄降序排列,去重复的依据就是依赖这个方法是否返回了0, 如果返回0,则认为是重复的!
int res = stu2.getAge() - stu1.getAge();
// 判断res是不是0,如果是0,则说明stu1和stu2年龄一样,继续按照姓名排序
return res!=0?res:stu1.getName().compareTo(stu2.getName());
// 上面一句为什么不用'-'?因为String类型不能用-,看Java文档中String类实现了comparable接口了,所以必定重写了compareTo方法,comparTo最终返回Int值(Comparable接口规定的)
});
Student s1 = new Student("abc", 18);
Student s2 = new Student("abc", 18);
Student s3 = new Student("ZZz", 22);
Student s4 = new Student("xyz", 19);
Student s5 = new Student("xyz", 18);
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s5);
System.out.println(set);
}
}

单列集合总结

image-20240222104840155

单列结合综合案例(斗地主)

一个比较综合的案例吧,但只是关于单列集合的,主要知识点有:

  • ArrayList作为原始的扑克排面,里面存放的是牌对象,牌对象的属性包含牌面和编号(注意这个编号是不变的且不能重复的,是作为牌的一个唯一性标识,类似于数据库中的主键)
  • 使用Collections工具类对集合中的元素打散
  • 对三名玩家进行发牌,以及留三张底牌,最后打印这四个TreeSet的所有牌面

代码案例

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// 实体类 pai(属性:牌面 编号)
public class Pai {
private String mian;

@Override
public String toString() {
return mian;
}

private int zhi;

public String getMian() {
return mian;
}

public void setMian(String mian) {
this.mian = mian;
}

public int getZhi() {
return zhi;
}

public void setZhi(int zhi) {
this.zhi = zhi;
}

public Pai() {
}

public Pai(String mian, int zhi) {
this.mian = mian;
this.zhi = zhi;
}
}

// 斗地主测试类
/*
需求分析
1: 自定义一个牌类,里面包含两个属性,一个用于记录牌面(由花色和点数组成),另一个成员变量用于表示这张牌排序的大小数字;
2: 准备一个lists系列的集合,用于保存54个牌对象;(组装牌)
3: 随机打乱顺序;(洗牌)
4: 准备3个玩家和1个底牌容器,把上面组装的54个对象分发到3个玩家和1个底牌容器中;(发牌)
5: 对3个玩家和1个底牌容器的牌对象进行排序并打印查询结果
*/
public class DDZ {
public static void main(String[] args) {
// 1, 准备一个list系列的集合,用于保存54个牌对象
List<Pai> poke = new ArrayList<>();
// 2, 准备花色和点数集合
List<String> huase = new ArrayList<>();
Collections.addAll(huase, "♠","♥","♣","♦");
List<String> dians = new ArrayList<>();
Collections.addAll(dians,"2","A","K","Q","J","10","9","8","7","6","5","4","3");

// 3, 拼装54个排面对象
// 先把大小王存进集合中
int zhi = 54;
Pai dawagn = new Pai("大王", zhi--);
Pai xiaowang = new Pai("小王", zhi--);
poke.add(dawagn);
poke.add(xiaowang);

for (String dian : dians) {
for (String s : huase) {
poke.add(new Pai(s+dian, zhi--));
}
}
// System.out.println(poke);
// 4,洗牌,将组装好的牌进行随机打散
Collections.shuffle(poke);
// System.out.println(poke);


// 5, 准备三个玩家,三个容器,将扑克发到每个玩家手中
Comparator<Pai> rule = (a, b) -> b.getZhi() - a.getZhi() ; // 前 - 后 升序。后-前降序
TreeSet<Pai> wanjia1 = new TreeSet<>(rule);
TreeSet<Pai> wanjia2 = new TreeSet<>(rule);
TreeSet<Pai> wanjia3 = new TreeSet<>(rule);
TreeSet<Pai> dipai = new TreeSet<>(rule);
// 当大集合的索引大于等于51的时候,就是最后三张牌,直接给底牌即可
for (int i = 0; i < poke.size(); i++) {
// 根据索引获取牌对象
Pai pai = poke.get(i);
// 判断集合的索引大于等于51的时候,就是最后三张牌,直接给底牌即可
if (i >= 51){
dipai.add(pai);
}else {
if (i % 3 == 1){
wanjia1.add(pai);
}else if (i % 3 == 2){
wanjia2.add(pai);
}else {
wanjia3.add(pai);
}
}
}
// 6, 看牌
System.out.println(wanjia1);
System.out.println(wanjia2);
System.out.println(wanjia3);
System.out.println(dipai);
}
}

双列集合派系(Map)

Map集合是什么:

  • 是双列集合顶层的接口,带有两个泛型,里面存储的都是键值对数据;(键和值一一对应,键不能重复,值可以重复)
  • 其中双列集合可以理解为Python中的字典,是kv键值对形式。

特点

  1. 存储的都是键值对数据;
  2. 键不能重复,值可以重复;

Map集合体系

image-20240224214519821

用的最多的:

  • HashMap: 无序,不重复,无索引

Map集合常用方法

image-20240224215246821

用的最多的:

  • 添加元素:put()
  • 根据键来获取对应的值:get(key)
  • 获取全部键的集合:keySet()

代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyMap {
public static void main(String[] args) {
// 1, 创建Map集合对象
Map<Integer, String> map = new HashMap<>();
// 2, 存数据 增加和修改都可以通过put来实现
System.out.println(map.put(120, "牛肉拉面"));
map.put(110, "兰州牛肉拉面");
map.put(130, "西北拉面");
System.out.println(map.put(120, "河南烩面"));
System.out.println(map);
// 根据key获取对应的value
System.out.println(map.get(110));
System.out.println(map.getOrDefault(111, "自定义的默认值"));
System.out.println("----------");
// keySet 获取所有的键值, 组合成单列集合
Set<Integer> set = map.keySet();
for (Integer i : set) {
System.out.println(i + "===>" + map.get(i));
}
}
}

双列集合之-Entry

可以将Map集合中的每一个键和值看成一个整体,整体类型是Map.Entry 类型

因为将键和值看成一个整体之后,Map就变成了单列集合,因此可以使用增强for进行遍历。

具体操作案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MapEntry {
public static void main(String[] args) {
// 1, 创建map集合对象
Map<Integer, String> map = new HashMap<>();
// 2, 存数据 增加和修改都可以使用put方法
map.put(120, "牛肉拉面");
map.put(110, "兰州牛肉拉面");
map.put(130, "西北拉面");
map.put(120, "河南烩面");
// 3, 将每一个键和值看成一个整体 整体类型是Map.Entry 类型
Set<Map.Entry<Integer, String>> set = map.entrySet();
// 4, 使用增强for遍历set,可以得到每一个 Entry 类型对象
for (Map.Entry<Integer, String> entry : set) {
Integer key = entry.getKey();
String value = entry.getValue();
System.out.println(key +"===>" + value);
}
}
}

Map集合的三种遍历方式

第一种遍历方式:

  • 通过获取集合中所有键的集合,然后根据键来获取集合中所有的值。

第二种遍历方式:

  • 通过将键值对看成一个Entry整体,通过增强for的方式遍历集合

第三种遍历方式:

  • 使用forEach结合Lambda表达式进行遍历(方便快捷)

前面两种方式基本都没有什么难度,下面直接使用第三种最快接的遍历方式,上代码!

代码案例

这里最 需要关注的就是集合的第三种遍历方式,通过ForEach+Lambda.

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
/*
求数组中每个字符串出现的次数,及出现次数最多的字符串和对应的次数(模拟投票案例)
*/
public class MapVoteDemo {
public static void main(String[] args) {
// 1, 准备原始数据
String[] arr = {"D","A","D","C","B","B","D","A","D","C","A"};
// 2, 使用Map集合统计投票的结果,使用景点当成Map的key,景点被选的次数当成Map的value
Map<String, Integer> map = new HashMap<>();
// 3, 遍历数组,从数组中获取每一个元素,从map中按照key取值,如果没有key则取到null这是就将null变为1,否则就+1
for (String s : arr) {
map.put(s, map.getOrDefault(s, 0)+1);
/* 上面的和下面的多句话是一个意思
Integer i = map.get(s);
if (i == null){
map.put(s, 1);
}else {
map.put(s, i+1);
}
*/
}
System.out.println(map);
// 获取所有的value组成的单列集合,并获取最大值,再次遍历map集合,如果某个值和最大值相等,则对应的key就是我们要找的数据
Collection<Integer> values = map.values();
Integer max = Collections.max(values);
System.out.println(max);
// 再次遍历map结合,如果某个值是和最大值相等,则对应的key就是我们要找的数据
map.forEach((k,v)->{
if (v == max){
System.out.println(k + "景点人数是最多的,人数为:"+v);
}
});
}
}

应用场景(统计词频)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
键盘输入一串字符串,统计字符串中每个字符出现的次数
*/
public class CharFrequency {
public static void main(String[] args) {
// 1, 键盘输入一个字符串
Scanner sc = new Scanner(System.in);
System.out.println("请输入一串字符串:");
String next = sc.next();
// 2, 遍历字符串,将字符串中的每一个字符都当成map的key,这个字符出现的次数当成map的value存储起来
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i < next.length(); i++) {
char c = next.charAt(i);
map.put(c, map.getOrDefault(c, 0)+1);
}
System.out.println(map);
}
}

HashMap

HashMap就是Map接口中的一个实现类,底层是HashTable表结构,会根据键来进行去重,根据键去重的依据是equals和HashCode方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Student 类这里就不写了,就是带有两个私有属性,name和age,并且重写了toString和HashCode和equals方法
public class CustomerizeKey {
public static void main(String[] args) {
// 1, 创建集合
HashMap<Student, String> map = new HashMap<>();
// 2, 创建3个集合对象
Student s1 = new Student("张三", 18);
Student s2 = new Student("张三2", 19);
Student s3 = new Student("张三", 18);
map.put(s1, "河南");
map.put(s2, "河北");
map.put(s3, "山东");
System.out.println(map);
// 结果:{Student{name='张三', age=18}=山东, Student{name='张三2', age=19}=河北}
// 原因:因为HashMap根据键进行
}
}

LinkedHashMap

就是HashMap的一个子类,可以保证数据有序,其他的都和HashMap一样。

其顺序的是现实通过元素添加的顺序来维护的,键的进栈顺序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// JavaBean 就不写了,跟上面的一样
public class MyLinkedHashMap {
public static void main(String[] args) {
// 1, 创建集合
LinkedHashMap<Student, String> map = new LinkedHashMap<>();
// 2, 创建3个集合对象
Student s1 = new Student("张三", 18);
Student s2 = new Student("张三2", 19);
Student s3 = new Student("张三", 18);
Student s4 = new Student("张三3", 18);

map.put(s1, "河南");
map.put(s2, "河北");
map.put(s3, "山东");

map.put(s4, "山洞");
System.out.println(map);
// s4对象最先添加:{Student{name='张三3', age=18}=山洞, Student{name='张三', age=18}=山东, Student{name='张三2', age=19}=河北}
// s4对象最后添加:{Student{name='张三', age=18}=山东, Student{name='张三2', age=19}=河北, Student{name='张三3', age=18}=山洞}
}
}

TreeMap

可以对键按自定义规则排序, TreeMap 判断键是否重复的条件是 排序Lambda是否返回了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
// JavaBean 就不写了,跟上面的一样
public class MyTreeMap {
public static void main(String[] args) {
// 1, 创建集合
Map<Student, String> map = new TreeMap<>((stu1, stu2) -> {
// 根据对象的年龄进行降序排序
int i = stu2.getAge() - stu1.getAge();
// 如果年龄相同则根据姓名降序排序,TreeMap 判断键是否重复的条件是 排序Lambda是否返回了0
return i == 0 ? stu2.getName().compareTo(stu2.getName()) : i;
});
// 2, 创建3个集合对象
Student s1 = new Student("张三", 18);
Student s2 = new Student("张三2", 19);
Student s3 = new Student("张三", 18);
Student s4 = new Student("张三3", 18);

map.put(s1, "河南");
map.put(s2, "河北");
map.put(s3, "山东");

map.put(s4, "山洞");
System.out.println(map);
}
}

集合嵌套(三种遍历)

需求说明:定义一个map集合,使用省的名称当成键,使用省中的各个市的名称当成value;也就是一个省对应多个市

重点在于:使用三种不同的方式遍历嵌套的集合

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
public class CollectionNested {
public static void main(String[] args) {
// 1, 定义一个map集合用来保存一个省份(键)对应多个城市(值),这里的城市是不能有重复的所以用HashSet结构
HashMap<String, HashSet<String>> map = new HashMap<>();
// 2, 准备各个省份中的市
HashSet<String> hn = new HashSet<>();
Collections.addAll(hn, "周口市","洛阳市","南阳市","信阳市","濮阳市");
HashSet<String> hb = new HashSet<>();
Collections.addAll(hb, "石家庄","张家口","唐山市","保定市");

map.put("河南", hn);
map.put("河北", hb);
// 遍历并打印 方式一:keySet方式,就是先获取所有的键,通过键找对应的值
Set<String> set = map.keySet();
for (String province : set) {
HashSet<String> citys = map.get(province);
for (String city : citys) {
System.out.println(province+"下面有:"+city);
}
System.out.println("======");
}
System.out.println("-------方式一结束-------");
// 遍历并打印 方式二:entrySet方式 面向Entry对象,获取省和市组成的单列集合
Set<Map.Entry<String, HashSet<String>>> entries = map.entrySet();
for (Map.Entry<String, HashSet<String>> entry : entries) {
// 面向Entry对象,可以获取省的名称和对应市组成的单列集合
String province = entry.getKey();
HashSet<String> citys = entry.getValue();
for (String city : citys) {
System.out.println(province + "下面有:" +city);
}
System.out.println("=====");
}
System.out.println("-------方式二结束-------");
// 遍历并打印 方式三:forEach+Lambda方式
map.forEach((a, b) -> {
for (String s : b) {
System.out.println(a+"下面有:"+s);
}
System.out.println("=====");
});
}
}

迭代器

概述

专门针对集合进行遍历的一个对象;(集合的助手)

如果想使用迭代器遍历集合:

  • 首先需要有一个集合对象。
  • 其次面向集合对象通过:集合对象.iterator()的形式获取一个迭代器对象。
  • 最后面向迭代器对象,遍历集合中的所有元素。

获取方式

1
利用集合的 iterator()方法即可获取迭代器对象;

常用方法

  1. hasNext(); 判断是否有元素可以获取;
  2. next(); 获取元素;
  3. remove();删除当前遍历到的这个元素;

代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
迭代器的基本使用
*/
public class IteratorTest {
public static void main(String[] args) {
// 1, 创建集合对象
Collection<String> c = new ArrayList<>();
c.add("aa");
c.add("bb");
c.add("cc");
c.add("cc");
c.add("cc");
c.add("cc");
// 2, 面向c对象,获取一个迭代器对象
Iterator<String> it = c.iterator();

// 3, 面向迭代器对象,获取集合中的每一个元素
while (it.hasNext()){
System.out.println(it.next());
}
}
}

迭代器使用注意事项

  • 在迭代器迭代集合的过程中,不能使用集合的方法改变集合的长度;
  • 在一次遍历过程中,最多只能使用一次 next方法,否则可能会出问题;
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 IteratorDemo {
public static void main(String[] args) {
// 1, 创建集合对象
Collection<String> c = new ArrayList<>();
c.add("aa");
c.add("bb");
c.add("cc");
c.add("dd");
c.add("dd");
c.add("dd");
// 2, 面向 c对象,获取一个迭代器对象
Iterator<String> it = c.iterator();
// 3, 面向迭代器对象,获取集合中的每一个元素
while (it.hasNext()){
String next = it.next();
System.out.println(next);
if (next.equals("dd")){
// c.remove("dd") 不能使用集合的方法删除数据, 可以利用迭代器自身的方法删除数据
it.remove();
}
}
System.out.println(c);
}
}

删除集合元素的方法

image-20240222105208628

增强for

概述&实例演示

对迭代器和数组遍历方式的语法格式简化。我要是不知道Python中for循环的两种遍历格式,还就真被这个b骗了。

就是一丫的for in in list这种东西,不要认为有多么的高大上,其实Python大家都知道很大一部分简化了Java的代码。

  • 作用

遍历单列集合或数组;

  • 格式
1
2
3
for(元素类型 变量名:容器){
此时的变量名就是容器中的每一个元素
}
  • 代码案例
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
import java.util.ArrayList;
import java.util.Collection;

/*
增强for循环:对迭代器和数组遍历方式的语法格式简化,其作用是遍历单列集合或者数组
格式:for(元素类型 变量名:容器){
此时的变量名就是容器中的每一个元素
}
个人感觉就是在Python中不写for i in range 写 for i in 集合名称 这种。比较简单,要是没学过Python还就真类比不了了
*/
public class EnhanceFor {
public static void main(String[] args) {
// 1, 创建集合对象
Collection<String> c = new ArrayList<>();
c.add("aa");
c.add("bb");
c.add("cc");
c.add("cc");
c.add("cc");
// 2,使用增强for对集合进行遍历,增强for遍历集合的时候,也是利用了迭代器,所以也不能再遍历过程中修改集合的长度(删除、添加元素
for (String i:c){
// i 表示的是集合中的每个元素
System.out.println(i);
}
}
}

集合中与Lambda相关的两个方法

forEach(lambda)

遍历集合;

  • 使用方法:使用集合名称.forEach(lambda)

removeIf(lambda)

删除所有满足规则的数据;

  • 使用方法:同上

代码案例

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
/*
集合中和Lambda相关的两个方法
1,ForEach(lambda):遍历即可
2,removelf(Lambda):删除所有满足规则的数据
*/
public class CollectionWithLambda {
public static void main(String[] args) {
// 1, 创建集合对象
Collection<String> c = new ArrayList<>();
c.add("aa");
c.add("A");
c.add("bb");
c.add("dd");
c.add("cc");
c.add("cc");
c.add("cc");
c.add("abc");

// 3, 删除集合中所有满足规则的数据,在removeIf的方法中,通过Lambda描述规则即可
// 删除所有的cc
c.removeIf(s -> s.equals("cc"));
// 看看集合中是否少了cc这些元素
System.out.println(c.toString());
// 删除所有长度为2的字符串
c.removeIf(s -> s.length() == 2);

// 使用c对象可以直接调用接口中的默认方法
// c.forEach(s -> System.out.println(s)); // 正常的Lambda
c.forEach(System.out::println); // 成员方法引用
Object[] array = c.toArray();
System.out.println(Arrays.toString(array));
}
}

集合工具类Collections

Collections工具类是一个专门操作集合的工具类,通过类名.方法名即可调用集合中的方法。

常见的操作有:

  • 批量添加数据:Collections.addAll
  • 排序:Collections.sort(集合, 比较器)
  • 随机打散:Collections.shuffle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CollectionTools {
public static void main(String[] args) {
// 1, 创建一个集合容器
List<Integer> list = new ArrayList<>();
// 2, 批量添加数据
Collections.addAll(list, 2, 5, 8, 3, 6, 9, 1, 4, 7);
System.out.println(list);
// 3. 排序
Collections.sort(list, (a, b)-> a-b);
System.out.println(list);
// 4, 随机打乱顺序
Collections.shuffle(list);
System.out.println(list);
}
}

可变参数

关于可变参数就记住两点就行:语法格式 和 本质。

可变参数就是类似于Python中的*args, **kwargs,其中*args会将函数的参数以元组的形式保存起来,**kwargs会将多余的参数以键值对的方式保存起来。

语法格式为:

  • 数据类型...参数名(注意必须为3个.)

本质:数组,但是比数组接受更多情况的实际参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FlexiblePara {
public static void main(String[] args) {
System.out.println(getSum(2, 5));
System.out.println(getSum(2, 5, 8));
System.out.println(getSum(1));
System.out.println(getSum());
int[] arr = {3, 6, 9};
System.out.println(getSum(arr));
}
public static int getSum(int...arr){
int sum = 0;
for (int i : arr) {
sum += i;
}
return sum;
}
}

正则表达式

正则表达式:编程界通用的一种工具。

这个东西,你只要能看懂各种符号就行,不用很熟,再熟不复习也会淡忘的,何况实际开发中如果真需要并且不会写就只要在网上搜索在线正则表达式就行。

匹配单个字符

  • . 任意单个字符
  • [] 括号列举的字符
  • /d 数字/D 非数字
  • /s 空白 /S 非空白
  • /w 非特殊字符(数字字母汉字下划线 /W 特殊字符

匹配多个字符

  • 匹配前一个字符0到多次*
  • 匹配前一个字符1到多次+
  • 匹配前一个字符0-1次(非贪婪匹配)?
  • 匹配前一个字符m次 {m}
  • 匹配前一个字符至少m次 {m,}
  • 匹配前一个字符m到n次{m,n}

分组匹配

首先了解分组是什么东西!一个括号()中的东西就可以是一个分组.

对于分组的操作, 可以取分组中的|左右的任意字符,也可以对分组其别名,方便后面复用.

  • | 匹配左右任意一个表达式
  • (ab) 将括号中字符作为一个分组
  • \num 引用分组num匹配到的字符串
  • (?P<name>) 分组起别名
  • (?P=name) 引用别名为name分组匹配到的字符串

代码案例

验证数据是否正确

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
/*
正则表达式的精髓就在于:
匹配单个字符:. [] \\W这些带\的东西,而且还不能是一个\,并且还是两个\
匹配多个字符:
特殊类:^(取反), &&(取交集),|(取并集), \(转移字符)
*/
public class RegexApp_verifyData {
public static void main(String[] args) {
// 自定义一个字符串数据
// 匹配邮箱的
String email = "a-234@qq.com.cn.org";
String regEm= "[a-z0-9A-Z]+[-|a-z0-9A-Z._]+@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-z]{2,}";

System.out.println(email.matches(regEm));

// 匹配电话的
//String regPh = "(?:(?:+|00)86)?1[3-9]\\d{9}";
String regPh = "0[0-9]{2,3}-[0-9]{7,8}";
String tel = "031-33333383";
System.out.println(tel.matches(regPh));

// 时间的
String regTime = "[1-9]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])\\s+(20|21|22|23|[0-1]\\d):[0-5]\\d:[0-5]\\d";
String time = "1999-11-11 12:12:12";
System.out.println(time.matches(regTime));
}
}

提取想要的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
需求:从一段文字中提取出想要的信息。(比如如下匹配所有的电话号码-包括但不限于座机,手机等号码,电子邮箱)
*/
public class RegexApp_extractInfo {
public static void main(String[] args) {
String data = " 来xxx教育机构学习Java,电话:1866668888,18699997777 或者联系邮箱:boniu@itcast.cn, 座机电话:01036517895,010-98951256 邮箱:bozai@itcast.cn, 邮箱2:dlei0009@163.com, 热线电话:400-618-9090 ,400-618-4000,4006184000,4006189090";
// \\w是匹配一个非特殊字符,像\\d是匹配数字 \\D 匹配非数字
String regex = "(\\w{1,}@\\w{2,10}(\\.\\w{2,10}){1,2})|(1[3-9]\\d{9})|(0\\d{2,5}-?\\d{5,15})|400-?\\d{3,8}-?\\d{3,8}";

// 1,将正则字符串编译成一个正则对象
Pattern compile = Pattern.compile(regex);
// 2, 使用编译后的正则对象,匹配数据
Matcher matcher = compile.matcher(data);
// 3, 遍历matcher 结果对象, find是判断是否有匹配的结果
while (matcher.find()){
// group 获取匹配的结果
System.out.println(matcher.group());
}
}
}

异常

异常的体系

主要关心的就是编译时异常运行时异常

image-20240220102954164

异常

异常处理的方法有两种:

  • throws抛出异常
    • 将方法内部出现的异常交给调用者处理
    • 语法:方法名 throws Exception1, Exception2.....
  • try catch捕获异常
    • 直接捕获程序中出现的异常
    • 语法:try {} catch(Exception e) {e.printStackTrace}

image-20240220221626845

编译阶段的异常

大白话:就是在编译阶段出现的异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
练习编译时期异常
*/
public class CompileException {
public static void main(String[] args) throws ParseException{
// 1, 创建一个日期格式化工具
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 2, 解析
try {
Date d = sdf.parse("2023-02-03 11-11-11");
System.out.println(d);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println("main结束!");
}
}

注意:

如果上面的没有在main方法后面加throws Exception且没有try catch处理则会编译报红

image-20240220222859787

运行阶段的异常

顾名思义,如果单单在方法后使用throws 则会把异常踢给main函数的调用者,也就是jvm,跟throws拋不抛异常没多大区别,最终都是jvm来处理。

但是如果使用try catch就是自己来处理异常了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ExceptionDemo {
public static void main(String[] args) {
int[] arr = {2, 5, 8};
// 也可以处理
try {
int value = getValue(arr, 5);
System.out.println(value);
}catch (Exception e){
// 提示程序处理问题,面向异常对象e,直接打印信息
e.printStackTrace();
System.out.println("已经捕获到异常");
}
}
public static int getValue(int[] arr, int index){
return arr[index];
}
}

自定义异常

指的就是自定义了一个继承了Java已经写好的异常体系中的任意的类。

运行时异常(非受检异常)

只有运行时才会告诉调用者程序出现了错误。

代码案例

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
// 异常类 继承自 RuntimeException
public class Exception1 extends RuntimeException{
public Exception1() {
}

public Exception1(String message) {
super(message);
}
}

// 测试类 在运行时抛出异常
public class MyTest1 {
public static void main(String[] args) {
int[] arr = {2, 5, 8};
System.out.println(getIndex(null, 10));
}
public static int getIndex(int[] arr, int key){
if (arr == null){
// 抛出一个无法完成任务的异常,因为异常是一个对象,所以要new
throw new Exception1("亲,数组不能为null啊。。。");
}
for (int i = 0; i < arr.length; i++) {
if (arr[i] == key){
return i;
}
}
// 没有找到的话就返回-1
return -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
25
26
27
28
29
30
31
32
33
34
35
// 异常类 继承自Exception(只需要alt+insert生成两个构造方法就行
public class Exception2 extends Exception{
public Exception2() {
}

public Exception2(String message) {
super(message);
}
}

// 测试类
public class MyTest2 {
public static void main(String[] args) {
int[] arr = {2, 5, 8};
try {
System.out.println(getIndex(null, 10));
}catch (Exception2 e){
e.printStackTrace();
}
System.out.println("如果上面捕获到异常,那么虚拟机就认为main没有发生异常,我就可以执行。");
}
public static int getIndex(int[] arr, int key) throws Exception2 {
if (arr == null){
// 抛出一个无法完成任务的异常,因为异常是一个对象,所以要new
throw new Exception2("亲,数组不能为null啊。。。");
}
for (int i = 0; i < arr.length; i++) {
if (arr[i] == key){
return i;
}
}
// 没有找到的话就返回-1
return -1;
}
}

Stream流

流是什么?

是java提供的一套专门操作数组或单列集合的API,允许我们使用lambda对数据按照指定的流程处理,将处理的结果收集起来;

获取流的四种情况(三种方式)

  • 零散数据 可以利用Stream接口中的静态方法 Stream.of(T...);
  • 数组数据 可以利用Arrays的静态方法 stream(数组);
  • 单列集合 可以利用Collection接口的默认方法 stream();
  • 双列集合 需要先转成单列集合,在利用单列集合的方式获取流对象;
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 getStream {
public static void main(String[] args) {
// 1, 将一堆零散数据,封装成流对象
Stream<Integer> stream = Stream.of(2, 5, 8, 3, 6, 9);
// 2, 使用流的终结方法foreach可以遍历流中的每一个数据
stream.forEach(System.out::println);
// 3, 将数组转换成流对象
int[] arr = {1, 4, 7};
IntStream stream1 = Arrays.stream(arr);
stream1.forEach(System.out::println);
// 4, 将单列集合转换成流对象
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 5, 6, 7, 8, 9);
Stream<Integer> stream2 = list.stream();
stream2.forEach(System.out::println);

// 最好不要使用of 方法传递数组,否则可能会将数组当成一个元素,存入流对象中进行处理
Stream<int[]> arr1 = Stream.of(arr);
arr1.forEach(System.out::println);
}
}

流的终结方法

任何的流调用完终结方法之后,就不会存在了,也就是流就结束了。

常用的终结方法有:

image-20240227215326614

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
练习流对象的终结方法
*/
public class TerminateStream {
public static void main(String[] args) {
// 1, 将一堆零散数据,封装成流对象
Stream<Integer> stream = Stream.of(2, 5, 8, 3, 6, 9);
// 2, 获取数量 (流一旦调用了终结方法,流的状态就已经结束了,不能再继续调用其他方法)
System.out.println(stream.count());
// 3, 再创建一个新的流对象
Map<String, Integer> map = new HashMap<>();
map.put("A", 5);
map.put("B", 15);
map.put("C", 15);
map.put("D", 2);
// 4,将Map中所有的value获取出来组成一个单列集合,再获取单列集合的流对象,利用流的max方法获取最大值
System.out.println(map.values().stream().max((a, b) -> a - b).get());
}
}

流的中间方法

顾名思义:就是主要对流做一些数据处理的方法,处理完成后流还会存在。

image-20240227215752678

用的最多的中间方法:

  • fileter-过滤
  • map-加工处理
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
/*
练习流对象的中间方法
*/
public class MiddleMethodOfStream {
public static void main(String[] args) {
// 1, 使用集合存储一些字符串数据
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张翠山","张无忌","张翠花","张翠翠","战三","华安","张三丰","张伟");
// 2, 获取流,直接. 不建议使用变量接收流对象运行的结果(除非是确定到了最终想要的结果了)
// 找出所有姓张并且姓名长度为3的名字打印出来
list.stream()
.filter(a -> a.startsWith("张"))
.filter(s ->s.length()==3)
.forEach(System.out::println);
// 3,对数组中的元素:找出大于等于3的并且排序后只要前3个并打印
int[] arr = {2, 5, 8, 3, 6, 9};
Arrays.stream(arr)
.filter(s -> s>=3).sorted().limit(3)
.forEach(System.out::println);

// 4, 对字符串数组中的数字进行提取,然后去重,降序排列
String s = "123,66,22,88,25,99,1024,55,22,66,123";
// 顺序:切分,流转,去重,转int,排序
Arrays.stream(s.split(","))
.distinct().map(Integer::parseInt).sorted((a,b)->b-a)
.forEach(System.out::println);

// 合并流
Stream.concat(Stream.of(3, 6, 9), Stream.of(2, 5)).forEach(System.out::println);
}
}

收集流对象

一般用的最多的就是Stream提供的常用的两个收集方法。

首先

  • 将流中数据收集到数组中:流.toArray()
  • 将流中数据收集到List集合中:流.collect(Collectors.tolist())
  • 将流中数据收集到Set集合中:流.collect(Collectors.toSet())
  • 将流中数据收集到Map集合中:流.collect(Collectors.toMap(lambda1, lambda2))

image-20240227220105763

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 collectStream {
public static void main(String[] args) {
// 1, 使用集合存一些字符串数据
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张翠山","张无忌","张翠花","张翠翠","战三","华安","张三丰","张伟");
// 2, 筛选出姓张的人,并收集到数组中中
Object[] array = list.stream().filter(s -> s.startsWith("张")).toArray();
System.out.println(Arrays.toString(array));
// 3, 筛选姓张的,放到set集合中/list列表 等单列集合中
List<String> list1 = list.stream().filter(s -> s.startsWith("张")).collect(Collectors.toList());
System.out.println(list1);
// 4, 使用全名当成map的key,姓氏当成map的value,将流中的数据收集到map集合中
// toMap 方法需要传递两个Function接口类型的参数,分别告诉他元素如何转成key,如何转成value
Map<String, Character> collect = list.stream().collect(Collectors.toMap(s -> s, s -> s.charAt(0)));
// 上面如果不使用charat也可以用s -> s.substring(0,1)
System.out.println(collect);
// 5,将下面数组中的数据收集到map集合中,名称当成key,年龄当成value
String[] arr = {"张三,18","李四,20","王五,22"};
Map<String, String> collect1 = Arrays.stream(arr).collect(Collectors.toMap(s -> s.split(",")[0], s -> s.split(",")[1]));
System.out.println(collect1);
}
}

文件File

是Java专门用来操作文件或者文件夹的类,一般就是通过创建一个File类的对象来操作文件的本身。

Notice:可以操作文件的路径、大小信息、名称、创建、删除等,但是不能操作文件的内容。

三种获取File对象的方式

  • new File(文件完整路径)
  • new File(父路径, 子路径)
  • new File(父路径文件对象, 子路径)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
练习获取文件对象的三种方法
*/
public class getFileObj {
public static void main(String[] args) {
// 1, 完整路径创建File对象(不管实际文件存不存在,对象都能创建成功)
File f1 = new File("C:\\Users\\admin\\Desktop\\opt\\Java\\day08\\1.txt");
System.out.println(f1);
File f2 = new File("C:\\Users\\admin\\Desktop\\opt\\Java\\day08\\2.txt");
System.out.println(f2);
System.out.println(f1.exists()); // true
System.out.println(f2.exists()); // false
// 2, 两个参数创建File对象
File f3 = new File("C:\\Users\\admin\\Desktop\\opt\\Java\\day08", "3.txt");
System.out.println(f3);
System.out.println(f3.exists());

// 3, 上面创建文件的方式都是绝对路径,相对路径默认是从项目开始的(不准确)
File f4 = new File("day08\\1.txt");
System.out.println(f4.exists());
}
}

Flie类常用方法(一)

用的最多的方法:

  • 判断文件是否存在
  • 判断是否为一个文件
  • 获取文件的名称
  • 获取绝对路径

image-20240228222710876

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 MyFile {
public static void main(String[] args) {
// 1, 创建File对象
File f1 = new File("day08\\1.txt");
System.out.println(f1.exists());
System.out.println(f1.isDirectory());
System.out.println(f1.isFile());
// 2,获取文件名
System.out.println(f1.getName());
File f2 = new File("C:\\Users\\admin\\Desktop\\opt\\Java\\day08");
System.out.println(f2.getName());
System.out.println(f2.isDirectory());
// 3, 获取长度
System.out.println(f1.length());
// 4, 最后修改时间
System.out.println(new Date(f1.lastModified()).toLocaleString());
System.out.println(f2.lastModified());
// 5, 获取路径
System.out.println(f1.getPath());
System.out.println(f2.getPath());
System.out.println(f1.getAbsolutePath());
System.out.println(f2.getAbsolutePath());
}
}

Flie类常用方法(二)

这里主要是关于文件/文件夹的创建和删除。

注意删除文件夹的时候,该文件夹必须为空才能够删除,否则就不能删除。(文件没有要求)

创建文件相关方法

  • 创建一个新的空文件:createNewFile()
  • 创建单级文件:mkdir()
    • 不得不说这不就是Shell吗(bushi
  • 创建多级文件夹:mkdirs()
    • Shell创建多级文件夹就是加参数 -p

删除文件相关方法

  • delete(): 删除文件,空文件夹
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
练习文件对象的创建和删除
*/
public class commonMethod2 {
public static void main(String[] args) {
// 1,创建File对象
File f = new File("C:\\Users\\admin\\Desktop\\opt\\Java\\day08\\a");
System.out.println(f.mkdir());
File f3 = new File("C:\\Users\\admin\\Desktop\\opt\\Java\\day08\\aa\\bb\\cc");
// 2, 删除文件
File f2 = new File("C:\\Users\\admin\\Desktop\\opt\\Java\\day08\\aa\\cc\\2.txt");
System.out.println(f2.delete());
// 3, 删除多层文件夹
System.out.println(f.delete()); // 删除单个文件夹(如果文件夹有东西的话也是删不掉的)
System.out.println(f3.delete()); // 递归删除多层文件夹(必须是空文件夹)
}
}

遍历文件夹

遍历单层文件夹:File对象.listFiles()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
练习文件夹对象的遍历
*/
public class traverseDir {
public static void main(String[] args) {
// 1, 创建File对象
File file = new File("C:\\Users\\admin\\Desktop\\opt\\Java\\day08\\aa");
// 2, 直接使用file对象调用listFile方法,得到这个文件夹下面所有的文件或者文件夹对象
File[] files = file.listFiles();
// 3, 遍历,获取aa下面的每一个字内容
for (File f : files) {
// 打印字内容的绝对路径
System.out.println(f.getAbsolutePath());
f.delete(); // 相当于循环删除aa 目录下面的内容
}
file.delete(); // 删除aa本身
}
}

listFiles方法相关注意事项

注意标红的就好了。

image-20240302184314973

遍历多层文件及文件夹(※)

需要使用到递归(递归两个重点:1,找规律;2,找出口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
使用递归遍历某个文件夹,输出所有文件及文件夹的绝对路径
*/
public class recursionDir {
public static void main(String[] args) {
// 1,创建文件夹对象
File file = new File("C:\\Users\\admin\\Desktop\\opt\\Java\\day08\\src\\com\\xlkh");
// 2, 这里需要将递归遍历文件夹的方法封装成一个方法,方便函数调用函数,不能直接调用主函数吧
getSubFiles(file);
}
public static void getSubFiles(File file){
File[] files = file.listFiles();
for (File f : files) {
if (f.isDirectory()){
System.out.println(f);
getSubFiles(f);
} else if (f.isFile()) {
System.out.println(f.getAbsolutePath());
}
}
}
}

附:递归(Recursion)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
上一个简单的案例,使用递归来求5的阶乘
*/
public class recursion {
public static void main(String[] args) {
// 测试递归方法好不好使
System.out.println(getFactorial(5));
}
public static int getFactorial(int n){
// 1, 找规律 2, 找出口
if (n == 1){
return 1;
}else {
return n * getFactorial(n - 1);
}
}
}

IO流

框架体系

image-20240401215644411

字符集

常见的编码字符集如下:

  • ASCII字符集(ASCII编码表) 仅包含数字,字母,特殊符号和数字的对应关系;

  • GBK 中国人规定的码表,规定了汉字与数字的对应关系,但是由于汉字较多,所以需要使用两个数字对应一个汉字,且第1个数字一定是负数;

  • Unicode 万国码表,收录了全球所有国家使用的符号,方便统一管理;

    • UTF-32 unicode下的具体一种编码格式,规定所有符号都使用4个字节表示,比较浪费空间;

    • UTF-8 UTF-32的优化,尽可能少的占用字节,当能用更少的字节表示数据的时候,就采用少字节存储;在该编码格式下,一个汉字会占用3个字节;

支持中文的码表:GBK和UTF-8

解码与编码

  • 解码:把字符串变成数字的过程就是编码;

    • 可以利用String类中的getBytes方法完成;
    • 例如:byte[] var = s.getBytes() - 其中s是字符串
    • 同样也可以在getBytes方法内部指定字符集:byte[] gbks = s.getBytes("gbk");
  • 把数字转成字符串的过程就是解码;

    • 可以利用String类的构造方法完成;
    • 没有指定编码表的情况下,默认按照utf-8解码String s1 = new String(arr);
    • 指定编码String s2 = new String(gbks);
  • 注意:编码时采用的码表必须和解码时采用的码表一致,否则会出现乱码现象;

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
/*
练习字符串类中:
编码:把字符串编程二进制数字就是编码
解码:把二进制数据转成字符串的过程就是解码
*/
public class charSetDemo {
public static void main(String[] args) throws UnsupportedEncodingException {
// 1, 编码 字符串 - 数字
String s = "你好";
// 2, 编码 不传参数,默认是idea默认编码(utf-8)
byte[] arr = s.getBytes();
System.out.println(Arrays.toString(arr));

// 3, 编码的时候,也可以手动指定字符集(但是必须要抛出一个不支持编码的异常)
byte[] gbks = s.getBytes("gbk");
System.out.println(gbks);
// 4, 解码 数字 --> 字符串 利用构造方法
String s1 = new String(arr); // 没有指定编码表的情况下,默认按照utf-8解码
String s2 = new String(gbks);
System.out.println(s1);
System.out.println(s2);

// 当上面解码出现乱码的情况,需要手动指定编码表解码
String s3 = new String(gbks, "GBK");
System.out.println(s3);
}
}

常见的IO流

IO流是什么?IO流就是专门负责操作文件或网络中数据的。

大致可以分为:

  • 字节输入流 顶层代表 InputStream

    • 以内存为基准,将来自磁盘/网络中的数据以字节的形式读入到内存中
  • 字节输出流 顶层代表 OutputStream

    • 以内存为基准,把内存中的数据以字节写出到磁盘/网络中
  • 字符输入流 顶层代表 Reader

    • 以内存为基准,将来自磁盘/网络中的数据以字符的形式读入到内存中
  • 字符输出流 顶层代表 Writer

    • 以内存为基准,把内存中的数据以字符写出到磁盘/网络中

因为以字节的形式需要考虑到很多关于编码和解码的问题,且转为字节不是非常方便。

文件字节输入流

创建文件字节输入流的方式主要有两种

  • 首先创建文件,将文件对象传递进FileInputStream()类中,产生该类的对象。
  • 第二种方式是直接通过FileInputStream将文件路径传递进去,生成字节输入流对象。
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 readFileThroughBytes {
public static void main(String[] args) throws IOException {
// 1, 提前准备一个文件,并根据文件路径,创建输入流对象
FileInputStream fin = new FileInputStream("C:\\Users\\admin\\Desktop\\opt\\Java\\day09\\src\\com\\xlkh\\demo01_charSet\\a.txt");

// 2, 读取文件中的字节数字
// System.out.println((char) fin.read());
// System.out.println((char) fin.read());
// System.out.println((char) fin.read());
// System.out.println((char) fin.read());
// System.o ut.println((char) fin.read());
// System.out.println((char) fin.read());

int res;
// 3,使用while循环按照字节读取文件,如果文件对象.read()返回了-1则表明读取到了文件的末尾
while ((res = fin.read()) != -1){
System.out.println((char) res);
}
}
}

使用数组以字节方式读取文件中的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
使用数组以 字节输入流的方法读取数据 (拓展
*/
public class arrayEnhance_ByteInputStream {
public static void main(String[] args) throws IOException {
// 为了读取文件的总字节数,可以先创建文件对象,再获取文件大小
File f = new File("C:\\Users\\admin\\Desktop\\opt\\Java\\day09\\src\\com\\xlkh\\demo01_charSet\\a.txt");

// 1, 提前准备一个文件,并根据文件路径,创建输入流对象
FileInputStream fin = new FileInputStream(f);
// 2, 为了提高读取数据的效率,可以提前准备一个数组,用于保存读取到的内容,当数组读满的时候,再操作这些数据即可
byte[] arr = new byte[(int) f.length()];

// 一次读取arr数组长的数据,并将数据放到arr中
fin.read(arr);
System.out.println(new String(arr));

// 在JDK9及其以后,可以使用readAllBytes()方法读取数据
byte[] by = fin.readAllBytes();
System.out.println(Arrays.toString(by));
}
}

文件字节输出流(FileOutputStream)

将字节输出到文件中,主要分为三步,首先将打开冰箱门,第二步放入大象….不好意思,串台了。

  • 创建对象:new FileOutputStream(文件路径)

    • 构造方法不带boolean参数,表示如果文件不存在,则创建,如果文件存在,则清空内容;
    • 构造方法带boolean参数(true),表示如果文件不存在,则创建,如果文件存在,则续写内容;
      • new FileOutputStream(文件路径, true);
  • 调用方法,写数据:文件对象.write()

    • 直接写码值;

      • f.write(97) -> a
    • 直接写字节数组;

      • f.write("哈哈".getBytes())
    • 直接写字节数组的一部分

      • f.write(arr, 1, 2)表示从arr数组(byte类型)的1下标开始,写两个字节;
  • 释放资源

    • 作为输出流必须释放资源,使用 close方法即可;
      • f.close();

image-20240313224349014

文件资源释放

两种方式进行文件资源释放:

try catch finally和try with resource

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 tryCatchFinally {
public static void main(String[] args) {
// 在try的外面声明变量,在try的里面赋值,
FileOutputStream fout = null;
try {
fout = new FileOutputStream("C:\\Users\\admin\\Desktop\\opt\\Java\\day09\\src\\com\\xlkh\\demo03_tryWithResource\\b.txt");
fout.write(97);
// 需要写字符串的时候,需要将字符串转成字节数组
fout.write("97".getBytes());
} catch (IOException e) {
e.printStackTrace();
System.out.println("捕获到异常..");
} finally {
// 无论try里面的代码如何执行,finally一定会执行(除非退出虚拟机
try {
// 由于close方法本身也声明抛出了一场,所以需要单独针对这个异常,再次处理
if (fout != null){
fout.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
简化释放资源的代码
*/
public class tryWithResource {
public static void main(String[] args) {
// 在try外面声明变量,在try里面赋值,目的是为了提升作用域,可以在finally代码块中国使用
// 注意:资源必须实现了CloseAble接口,才可以放在try后面的小括号中
try(FileOutputStream fout = new FileOutputStream("C:\\Users\\admin\\Desktop\\opt\\Java\\day09\\src\\com\\xlkh\\demo03_tryWithResource\\b.txt")){
fout.write(97);
fout.write("97".getBytes());
} catch (IOException e) {
e.printStackTrace();
System.out.println("catch...over!");
}
}
}

字节流的应用

注意事项

使用字节输入流读取数据的时候:字节输入流.read()

  • 如果知识单纯的f.read(),则f.read()的返回值是读取到字节的码值
  • 如果是f.read(数组),则返回的是读取到的字节的个数

递归获取文件夹大小

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 recursionGetDirSize {
public static void main(String[] args) {
File dir = new File("C:\\Users\\admin\\Desktop\\Diary");
System.out.println(getTotalLength(dir));
}
public static long getTotalLength(File dir){
long totalLength = 0;
File[] files = dir.listFiles();
for (File file : files) {
if (file.isFile()){
totalLength += file.length();
}else {
long dirlen = getTotalLength(file);
// 少加了文件夹及其下面所有文件的长度
totalLength += dirlen;
}
}
return totalLength;
}
}

递归复制文件夹及其子目录

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
/*
递归复制某个文件夹及其下面的所有文件或文件夹
*/
public class recursionCopyDir {
public static void main(String[] args) throws IOException {
// 创建两个文件夹对象
File srcFile = new File("E:\\opt\\Java\\就业班\\Java进阶课程\\day09-字符集、IO流(一)\\a");
File dstFile = new File("E:\\opt\\Java\\就业班\\Java进阶课程\\day09-字符集、IO流(一)\\b");
copyDir(srcFile, dstFile);
}
public static void copyDir(File src, File dst) throws IOException {
// 传递两个参数(需要拷贝的源文件夹以及拷贝到的目的文件夹)来实现文件夹的复制
File[] files = src.listFiles();
for (File file : files) {
// 拼接目的文件/文件夹路径并创建File对象
File dstFile = new File(dst, file.getName());
if (file.isFile()){
// 如果是文件的话直接拷贝到目的文件夹
copyFile(file, dstFile);
}else {
// 如果是文件夹的话,需要在目的文件夹中创建该文件夹,然后递归拷贝文件夹下的内容
dstFile.mkdirs();
copyDir(file, dstFile);
}
}
}
public static void copyFile(File srcFile, File dstFile) throws IOException {
// 1,创建文件字节输入流
FileInputStream fin = new FileInputStream(srcFile);
// 2, 创建字节输出流
FileOutputStream fout = new FileOutputStream(dstFile);
// 创建数组,用于存放数据
byte[] arr = new byte[1024 * 8];
int count;
while ((count = fin.read(arr)) != -1){
fout.write(arr, 0, count);
}
}
}

字符流

字符输入流

常用的方法:

  • FileReader对象.read()
    • 每次读取一个字符返回,如果没有字符则会返回-1
  • FileReader对象.read(字符型数组)
    • 每次用字符数组读取数据,返回字符数组读取了多少字符,如果没有读到数据也会返回-1

因为使用单个字符读取数据的方式会比较低效,使用的场景非常有限,一般都是使用数组进行数据读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
字符输入流 -- 使用字符数组读取数据
*/
public class charArrReader {
public static void main(String[] args) throws IOException {
// 1, 创建文件字符输入流
FileReader fr = new FileReader("C:\\Users\\admin\\Desktop\\opt\\Java\\day09\\src\\com\\xlkh\\demo01_charSet\\a.txt");
// 2, 提前准备一个int类型的变量
int count;
char[] arr = new char[8192];
while ((count = fr.read(arr)) != -1){
System.out.println(new String(arr, 0, count));
}
fr.close();
}
}

字符输出流

常用的方法:

  • 构造方法:

    • new FileWriter(File f):使用文件对象创建字符输出流对象,覆盖写入
    • new FileWriter(String filePath):使用文件路径创建字符输出流对象, 同上
    • new FileWriter(File f, boolean true):使用文件对象创建字符输出流对象,追加写入
    • new FileWriter(String filePath, boolean true):使用文件路径创建字符输出流对象,同上
  • 成员方法:

    image-20240318224048727

字节缓冲流(了解)

  • 概述

    • 在字节基础流的基础上,封装了一个内置的数组而已;
  • 作用

    • 提升基本流逐个字节复制文件的性能;
  • 结论

    • 基本的字节流配合自定义的数组性能最高!

字符缓冲流(※)

  • 概述

    • 专门针对字符流进行包装的流,不仅仅提升了循环读取单个字符的性能,还提供了特有方法(读一行和跨平台的换行);
  • 作用

    • 提升字符操作性能;
    • 提供特有方法,更方便读写文本数据;
  • 使用套路

    • 都是利用带一个基本流的构造方法创建对象;
    • 直接调用特有方法即可;

具体API

  • BufferWriter(Writer r): 把低级的字符输出流包装成一个高级的缓冲字符输出管道,提高字符输出流写数据的性能
    • 特殊方法:public void newLine()
      • 好处:可以模拟不同平台的换行,从而实现写数据自带换行
      • Linux,Windows,MacOS换行都有一定区别。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class charBufferOutputStream {
public static void main(String[] args) throws Exception {
// 1, 准备基本的字符输入流
FileWriter fw = new FileWriter("C:\\Users\\admin\\Desktop\\opt\\Java\\day10\\src\\com\\xlkh\\test.txt");
// 2, 包装成字符输出缓冲流
BufferedWriter bw = new BufferedWriter(fw);
// 3, 循环写数据,每次写完之后 换行
for (int i = 0; i < 10; i++) {
// 写数据
bw.write("测试数据"+ i);
// 换行
bw.newLine();
// 刷新流
bw.flush();
}
// 释放资源
bw.close();
System.out.println("over!");
}
}
  • BufferReader(Reader r): 把低级的字符输入流包装成字符缓冲输入流管道,提高字符输入流的性能
    • 特殊方法:public void readLine()
      • 读取一行数据返回,如果没有数据则会返回Null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class charBufferInputStream {
public static void main(String[] args) throws Exception {
// 1, 准备基本的字符输入流
FileReader fr = new FileReader("C:\\Users\\admin\\Desktop\\opt\\Java\\day10\\src\\com\\xlkh\\test.txt");

// 2, 把基本流包装成为缓冲流
BufferedReader br = new BufferedReader(fr);

// 3, 逐行读取,每次循环读取到的结果都会封装成一个字符串,读到文件末尾会返回null
String s;
while ((s=br.readLine()) != null){
System.out.println(s);
}
br.close();
}
}

缓冲流综合练习-对文件内容排序

  • 文件 综合案例
  • 需求说明:
    • 对某个文件的内容,按照内容自带的行号排好顺序
  • 分析:
    • 文件本身不能直接对内容排序,因此需要将文件的内容,读取到内存中,在内存中完成排序后,再重新写回文件即可
      1,读取文件的时候,应该逐行读取数据
      2,遇到空白行应该排除
      3,读取的内容,应该存入到一个TreeSet集合,自定义排序规则
      4,遍历set集合,把内容逐行写回文件即可
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
public class charStreamDemo {
public static void main(String[] args) throws Exception {
// 1, 创建一个文件对象
FileReader fr = new FileReader("C:\\Users\\admin\\Desktop\\opt\\Java\\day10\\src\\com\\xlkh\\3_bak.txt");
// 2, 创建字符流输入对象
BufferedReader br = new BufferedReader(fr);
// 3, 读取数据, 并将数据放到一个TreeSet列表中,定义排序规则为每行数据的序号
TreeSet<String> ts = new TreeSet<>((a,b) -> Integer.parseInt(String.valueOf(a.charAt(0))) - Integer.parseInt(String.valueOf(b.charAt(0))));
String s;
while ((s = br.readLine())!= null){
if (!s.isEmpty()){
ts.add(s);
//System.out.println(s.charAt(0));
}
}
for (String t : ts) {
System.out.println(t);
}
// 创建字符输出流,将数据sink到本地的另一个文件中
FileWriter fw = new FileWriter("C:\\Users\\admin\\Desktop\\opt\\Java\\day10\\src\\com\\xlkh\\test.txt", true);
// 创建字符缓冲输出流,可以使用newline方法
BufferedWriter bw = new BufferedWriter(fw);
for (String t : ts) {
bw.append(t);
bw.newLine();
// 写完要么关闭文件,要么直接写到指定行数的时候刷新一次,如果不刷新数据就会保留在内存中,不会到文件中
bw.flush();
}
bw.close();
}
}

转换流

  • 概述

    • 转换流就是专门负责将字节流转成字符流;
  • 为什么要转

    • 因为在将字节流转成字符流的时候,可以指定编码格式;
  • 如何转

    • 字节输入流,转成字符输入流: InputStreamReader,构造方法中传递一个基本的字节输入流,再加一个编码表字符串即可;
    • 字节输出流,转成字符输出流: OutputStreamWriter,构造方法中传递一个基本的字节输出流,再加一个编码表字符串即可;

参考代码–读

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
/*
转换流 就是将字节流转为字符流 因为在字节流转为字符流的时候可以指定编码格式
但是在JDK11之后就不需要转了,因为字符流可以直接指定编码格式了
练习在读取文件数据的时候,指定编码
*/
public class converStreamRead {
public static void main(String[] args) throws Exception {
// 1, 创建基本的字节输入流
FileInputStream fin = new FileInputStream("C:\\Users\\admin\\Desktop\\opt\\Java\\day10\\src\\com\\xlkh\\test.txt");
// 2, 创建一个字符输入流 (采用默认编码 utf-8)
InputStreamReader isr = new InputStreamReader(fin);
// 创建对象的时候,指定文件的编码格式为 GBK
InputStreamReader isr1 = new InputStreamReader(fin, "gbk");
// 在jdk11之后,可以使用子类的构造方法,直接指定编码
FileReader isr2 = new FileReader("C:\\Users\\admin\\Desktop\\opt\\Java\\day10\\src\\com\\xlkh\\test.txt", Charset.forName("gbk"));
// 3, 逐个字符读取
int c;
// 下面如果是单纯的InputStreamReader则会出现乱码,原因在于字符流没有和源文件的编码格式统一
// 有两种解决方法:1,使用带有指定编码的字符输入流(jdk11后) 2,使用字符输入流的子类 FileReader 指定编码情况下读取数据
while ((c = isr.read()) != -1){
System.out.println((char) c);
}
isr2.close();
}
}

参考代码–写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
练习写文件数据的时候,指定编码
*/
public class MyOutputStreamWriter {
public static void main(String[] args) throws Exception {
// 1: 创建本的字节输出流
FileOutputStream fout = new FileOutputStream("day10_Teacher/file/5.txt");
// 2: 创建一个字符输出流;(采用默认编码,utf-8)
//OutputStreamWriter osw = new OutputStreamWriter(fout,"GBK");
FileWriter osw = new FileWriter("day10_Teacher/file/7.txt",Charset.forName("GBK"));
//在jdk11之后,可以使用子类的构造方法,直接指定编码了
// 3: 将字符串直接写到硬盘
osw.write("6哈6");
osw.close();

}
}

打印流(PrintStream)

  • 概述

    • 打印流就是专门负责将内存中的数据,写到指定的目的地的流;
  • 作用

    • 简化输出数据的代码;(想输出任意类型的内容,可以利用print方法直接输出即可,想换行,直接利用println方法即可);
  • 使用步骤

    • 创建打印流对象;
    • 直接调用方法打印数据即可;
    • 释放资源;

image-20240410222858623

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
字符打印流 和 字节打印流的效果一模一样,仅仅是字符打印流的构造方法,可以接收字节流, 二字节打印流不能封装字符流
PrintStream 和 PrintWriter 的区别
PrintStream继承自字节输出流OutputStream 因此支持字节数据的方法
PrintWriter集成子字符输出流Writer 因此支持写字符数据出去
*/
public class printStreamTest {
public static void main(String[] args) throws Exception {
// 1, 创建字节打印流
PrintStream ps = new PrintStream("C:\\Users\\admin\\Desktop\\opt\\Java\\day10\\src\\com\\xlkh\\a.txt", "GBK");
// 2, 直接向目的地打印数据(将字符97写到目的地,同时自动换行, 如果第一个参数是流,则不需要刷新,可以自动刷新
ps.println(97);
ps.println("Java");
ps.print("Python"); // 输出后没有换行
ps.println(18);
ps.flush();
ps.close();
}
}

数据流

  • 概述

    • 可以把数据值传递的同时也可以携带该数据的数据类型;
  • 如何创建对象

    • DataOutputStream:专门负责输出数据;
    • DataInputStream:专门负责读 DataOutputStream 曾经写的数据;
    • 构造方法中都是封装了一个基本的字节流;

常用方法-写

image-20240410223007154

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
使用数据流 最重要的特点就是 通过数据输入流和 输出流在写入(读取数据的时候)携带数据类型,只有Java能识别的数据类型
数据流的基础是字节流※
针对数据输出流的文件,应该配合数据输入流再读取一次,就可以把文件中保存的数据和数据类型读取到内存中了!!
*/
public class myWriteDataStream {
public static void main(String[] args) throws Exception {
// 1, 准备一个基本的字节输出流
FileOutputStream fout = new FileOutputStream("C:\\Users\\admin\\Desktop\\opt\\Java\\day10\\src\\com\\xlkh\\b.txt");
// 2, 把基本流包装成数据流
DataOutputStream dop = new DataOutputStream(fout);
// 3, 写数据的同时,携带数据类型
dop.writeByte(97);
dop.writeInt(88);
dop.writeBoolean(true);
dop.writeUTF("藕嫩叠");
// 4, 释放资源
dop.close();
}
}

常用方法-读

image-20230526151102296

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class myReadDataStream {
public static void main(String[] args) throws Exception {
// 1, 准备一个基本的字节输入流
FileInputStream fis = new FileInputStream("C:\\Users\\admin\\Desktop\\opt\\Java\\day10\\src\\com\\xlkh\\b.txt");
// 2, 包装成数据输入流
DataInputStream dis = new DataInputStream(fis);
// 3, 读取带数据类型的数据
byte b = dis.readByte();
int i = dis.readInt();
boolean b1 = dis.readBoolean();
String s = dis.readUTF();
System.out.println(b);
System.out.println(i);
System.out.println(b1);
System.out.println(s);
}
}

对象流(序列化流)

  • 概述

    • 可以将java内存中的对象整体写到硬盘,也可以把硬盘上的对象,再读到内存中;
  • 类型

    • 写数据用: ObjectOutputStream ,(写数据的过程称为 序列化)
    • 读数据用: ObjectInputStream , (读数据的过程称为 反序列化)
  • 构造方法

    • 里面都是包装一个基本的字节流;
  • 常用方法-写

image-20230526154711745

参考代码

  • 输出流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
对象输出流 也叫序列化流 意思就是将对象写到硬盘中
*/
public class serializableOutputStream {
public static void main(String[] args) throws Exception {
// 1, 准备一个基本的字节输出流
FileOutputStream fos = new FileOutputStream("C:\\Users\\admin\\Desktop\\opt\\Java\\day10\\src\\com\\xlkh\\obj.txt");
// 2, 包装成对象输出流
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 3, 准备一个学生对象
Student s1 = new Student("张三", 18);
// 4, 把学生对象存入硬盘(下面若想执行成功,则该对象必须实现了serializable接口,类似于深浅克隆中的Cloneable,打上了标记
oos.writeObject(s1);
oos.close();
}
}
  • 输入流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class serializableInputStream {
public static void main(String[] args) throws Exception {
// 1, 准备一个基本的字节输入流
FileInputStream fis = new FileInputStream("C:\\Users\\admin\\Desktop\\opt\\Java\\day10\\src\\com\\xlkh\\obj.txt");
// 2, 将基本字节流包装成对象输入流
ObjectInputStream ois = new ObjectInputStream(fis);
// 3, 直接读取数据
Object o = ois.readObject();
// 如果读取到文件末尾,会出现异常,为了避免异常的发生,可以规定写数据的时候,把所有的对象都存入集合容器,则反序列化的时候,直接读取一次集合容器即可,然后遍历集合容器就可以避免异常发生
// 这时,如果想直接调用getName和getAge方法则需要强转,因为成员方法,编译看左,运行看右,o父类对象中没有该方法,编译都不能通过
Student s = (Student) o;
System.out.println(s.getName() + "-------" + s.getAge());
ois.close();
}
}

注意事项

对象必须实现 Serializable 接口,否则无法序列化和反序列化!!!

image-20240410223411717

特殊文件的读取

properties

Properties 是代表属性的意思,在Java中常常用这种后缀类型的文件作为配置文件。

配置文件出来了,那必然是需要我们去读取的,如果用字符流读取可以但是没必要,用读取properties常用的方法往往无往而不利。

概述

  • 定义:属于map中的一员,也是双列集合;

类似于:

image-20240404190204009

  • 作用:专门与后缀名是properties的文件相互结合,可以方便的把集合中的数据存入文件,也能把文件中的数据读到集合中;

  • 使用步骤

    • 利用构造方法创建对象;

    • 利用成员方法,完成功能;

  • 构造方法

image-20230528102525752

  • 常用方法

image-20230528102539935

image-20230528103410774

参考代码

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
/*
Properties 是Java中常见的配置文件,一般对于配置文件的读取都是遵循一定套路的,不会直接拿着字节流/字符流来读取
*/
public class myProperties {
public static void main(String[] args) throws Exception {
// 1, 创建集合
Properties p = new Properties();
// 2, 逐个添加数据
p.setProperty("aa", "11");
p.setProperty("bb", "22");
p.setProperty("cc", "33");
System.out.println(p);
// 单纯打印有点类似于Python中的字典:{aa=11, bb=22, cc=33}
// 3, 直接读取某个文件中的数据到集合中(字符流和字节流都可以使用
//FileReader fr = new FileReader("C:\\Users\\admin\\Desktop\\opt\\Java\\day11\\files\\2.properties");
FileInputStream fr = new FileInputStream("C:\\Users\\admin\\Desktop\\opt\\Java\\day11\\files\\2.properties");
p.load(fr);
System.out.println(p);
// 4, 根据key取值
System.out.println(p.getProperty("name"));
// 5, 获取所有的Key
Set<String> set = p.stringPropertyNames();
for (String s : set) {
System.out.println(s + "---->" + p.getProperty(s));
}

}
}

xml

概述

是一门可扩展的标记语言;

  • 作用

    • 通过自定义的标记,存储或表示数据用的;通过开发中会使用xml文件当成系统的配置文件(存储着如何让程序运行的信息)使用;
  • 语法

    • 所有的内容都是由标签(标记)组成;

    • 标签由 < 自定义的名称 >,必须有开头,由结尾;

    • 文档的第一行第一列必须是固定的文档声明;

    • 属性必须使用双引号包裹;

    • 后缀名是xml

    • 有且仅有一个根标签

    • 标签嵌套不能交叉;

  • 注释

1
2
<!-- 注释  -->
如果有大量的转义字符,可以使用 CDATA区存;
  • xml文件演示
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
<?xml version="1.0" encoding="utf-8" ?>
<!-- 这是注释 -->
<abcd>
<aaa>
<b>

</b>
<b name ="嘿嘿">
&lt;为所欲为 &gt;
</b>

<![CDATA[
<水浒传>
<红楼梦>
<<<
>>>
'
""

]]>
<bbb id="345" />
</aaa>
<b>


</b>
</abcd>

xml解析

利用dom4j工具完成;

使用步骤

  • 创建解析器

  • 获取根

  • 利用根对象,获取里面的所有字标签的集合

  • 遍历集合即可获取每个子标签

  • 面向每个字标签对象继续获取里面的属性,子标签的子标签,值等;

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
<?xml version="1.0" encoding="utf-8" ?>
<!-- 这是注释 -->
<stus>
<student id="1">
<name>张三</name>
<age>18</age>
</student>
<student id="2">
<name>李四</name>
<age>19</age>
</student>
</stus>
/*
使用dom4j解析xml文本文件
*/
public class xmlParse {
public static void main(String[] args) throws Exception {
// 1, 利用空参数的构造方法, 创建一个解析器对象
SAXReader reader = new SAXReader();
// 2, 把整个xml读取成一个文档对象
Document d = reader.read("C:\\Users\\admin\\Desktop\\opt\\Java\\day11\\files\\stu.xml");
// 3, 整个文档中,一定只有一个根,直接从文档对象中,获取根即可
Element root = d.getRootElement();
// 4, 获取根下面的所有子标签
List<Element> stus = root.elements();
// 5, 遍历集合,就可以获取到每一个具体的标签对象(Student标签
for (Element stu : stus) {
// 5.1 面向每隔student标签对象,可以获取属性,也可以获取里面所有的子标签
String id = stu.attribute("id").getValue();
System.out.println("id" + id);
// 5.2 解析elem 标签对象 的子标签(各个学生属性
List<Element> prop = stu.elements();
// 5.3 遍历prop获取每隔具体的标签对象(name,age
for (Element name_age : prop) {
// 直接获取文本内容解渴
String text = name_age.getText();
// 获取标签的名称
String name = name_age.getName();
System.out.println(name + "标签下的内容是" + text);
}
}
}
}

约束

概述

  • 定义:可以对xml文件的内容进行限制的技术;

  • 作用

    • dtd文件,可以对xml文件的内容进行粗粒度的限制;
    • xsd文件可以对xml文件的内容进行细粒度的限制;
  • 分类

    • dtd约束,老的约束,粒度粗;

    • schema约束,新的约束技术,粒度细致;

  • 语法

image-20230528144034307

image-20230528144105130

xml和约束文件的引入方式

dtd引入:

1
<!DOCTYPE 书架 SYSTEM "data.dtd">

xsd引入:

1
2
3
<书架 xmlns="http://www.itcast.cn"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.itcast.cn data.xsd">

xml的内容,按照约束的提示写即可;

日志及多线程三种实现方式

日志

  • 概述:专门用于记录程序运行的状态的技术;

  • 常见日志框架

    • logback

    • log4j

  • logback的使用步骤

    • 环境级别:

      • 下载并复制jar包到idea;
      • 复制配置文件到源码目录;
    • 代码级别:

      • 修改配置文件的内容;
      • 获取一个日志对象;
      • 面向对象,调用方法;
  • 注意事项

  1. 配置文件的名称和路径必须按框架的要求写和存;(必须放在源码目录下且名称是 logback.xml)
  2. 获取日志对象的时候,注意包;

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
logback日志入门
*/
public class TestLogBack {
// 利用工具,获取一个日志对象
public static final Logger LOG= LoggerFactory.getLogger("TestLogBack");

public static void main(String[] args) {
// 利用 log对象 调用各种级别 的日志方法,就可以输出日志的内容了
System.out.println("正常代码1");
LOG.info("第一行代码走完了,没有出问题...");
System.out.println("正常代码2");
LOG.debug("第2行走完了,,,,debug级别");
System.out.println("正常代码3");
LOG.error("严重问题,代码走完了");
}
}

日志级别

  • 作用

    • 程序员可以通过设置不同的级别,从而决定记录程序中的不同问题;
  • 具体级别

image-20230528161739165

程序中会记录指定的级别以及更高的级别的问题!!!

多线程

  • 概述

    • 所谓的线程就是代码执行的通道;一个代码执行通道就是单线程程序,多个代码执行通道就是多线程程序;
  • 应用场景

    • 多人聊天,多文件同时下载…等
  • 实现方式

    • java中有3种方式可以实现多线程;

方式1-继承Thread

  • 概述:Thread是java编写的专门用于描述线程的类,只要创建这个类的对象,或者继承这个线程类,我们就相当于拥有了一个线程对象了;

  • 代码实现方式

    • 自定义一个类继承Thread类;
    • 重写run方法,指定线程的任务;
    • 在测试类中创建自定义类的对象并调用start方法启动线程即可;

参考代码

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
/*
练习多线程的创建方式1:

继承 Thread类
*/
public class MyThread extends Thread {
// 这个方法里面的代码,就是将来线程需要执行的 任务 (线程相当于是通道,马路),而run方法的内容就是这个通道内需要执行的代码,马路上的汽车
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我们自定义的线程打印了数据:"+i);
}
}
}
package com.itheima.demo05_Thread_1;

public class Test {
public static void main(String[] args) {
// 1: 创建线程对象
MyThread m1 = new MyThread();
// 2: 让我们的线程执行起来,一旦m1这个线程启动成功了,无论m1线程内的任务是否执行完了,都不影响main方法继续往下执行其他的代码;
m1.start();
// 3: 让main也打印100个数据
for (int i = 0; i < 100; i++) {
System.out.println("mian:"+i);
}
}
}

方式2-实现Runnable接口

  • 概述
    • 我们自定义的类,实现接口,仅仅算术一个任务类,将来可以将该类的对象,作为线程的参数传递进去,让线程执行这个任务即可;

代码实现

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
// 这个类目前只是一个任务类,等待线程执行它
public class MyRun implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("第二种方式创建的单纯的任务:"+i);
}
}
}
/*
利用java的Thread类,创建出一个线程对象,并将我们提前准备的任务对象,交给线程对象,让线程执行我们准备的任务
*/
public class MyTest02 {
public static void main(String[] args) {
// 1: 创建任务对象
MyRun rw = new MyRun();
// 2: 创建线程对象,,并将我们提前准备的任务对象,交给线程对象,让线程执行我们准备的任务
Thread t = new Thread(rw);
// 3: 开启线程
t.start();
// 4: mian执行任务
for (int i = 0; i < 100; i++) {
System.out.println("main:"+i);
}
}
}

方式3-实现Callable接口

  • 概述

    • 程序员可以写一个带返回值的任务类,只需要实现 Callable接口即可,但是这个接口无法直接和线程绑定,需要借助 FutureTask 类中转才可以,将来也可以面向 FutureTask 类的对象,获取线程的结果;
  • FutureTask的常用方法

image-20230528174125314

参考代码

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 MyCall implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 模拟计算需要3秒的时间
System.out.println("模拟计算中....");
Thread.sleep(3000);
System.out.println("模拟计算完....");
return 666;
}
}

public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1: 创建带返回值的任务对象
MyCall my = new MyCall();
// 2: 需要将my 任务对象包装成 FutureTask对象
FutureTask<Integer> task = new FutureTask<>(my);
// 3: 创建线程对象,并绑定 task
new Thread(task).start();
System.out.println("模拟main已经开启线程完毕...");
// 4: 找中间人要结果
Integer integer = task.get();
System.out.println("mian得到的结果是:"+integer);
}
}