目录
1.Stream流
1.1 概括
package com.itheima.d1_stream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class StreamTest1 {
public static void main(String[] args) {
// 目标:体验Stream流的使用
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
// 1、拿出姓张的放到新集合中去。
// List<String> newList = new ArrayList<>();
// for (String s : list) {
// if(s.startsWith("张") && s.length() == 3) {
// newList.add(s);
// }
// }
// System.out.println(newList);
// 2、体验Stream流
List<String> newList = list.stream().filter(s -> s.startsWith("张") && s.length() == 3).collect(Collectors.toList());
System.out.println(newList);
}
}
1.2 集合获取Stream流
package com.itheima.d1_stream;
import java.util.*;
import java.util.stream.Stream;
/**
*
*/
public class StreamTest2 {
public static void main(String[] args) {
// 目标:获取Stream流。
// 1、获取集合的Stream流: default Stream<E> stream()
Collection<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若","赵敏","张强","张三丰");
Stream<String> listS = list.stream();
// 2、获取Map集合的Stream流
Map<String,Integer> map = new HashMap<>();
// a、获取键流
Stream<String> keyS = map.keySet().stream();
// b、获取值流
Stream<Integer> valueS = map.values().stream();
// c、获取键值对流.
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
// 3、获取数组的Stream
String[] names = {"赵敏", "王菲", "小昭"};
Stream<String> stream1 = Arrays.stream(names);
Stream<String> stream2 = Stream.of(names);
}
}
1.3 Stream流的常用方法
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
public class StreamTest3 {
public static void main(String[] args) {
// 目标:掌握Stream流的常见方法。
List<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若","赵敏","张强","张三丰");
// 1、过滤方法
list.stream().filter(s -> s.startsWith("张") ).forEach(s -> System.out.println(s));
// 2、准备一个集合,排序。
List<Movie> movies = new ArrayList<>();
movies.add(new Movie("摔跤吧,爸爸", 9.5, "阿米尔汗"));
movies.add(new Movie("三傻宝莱坞", 8.5, "阿米尔汗2"));
movies.add(new Movie("三傻宝莱坞", 8.5, "阿米尔汗2"));
movies.add(new Movie("阿甘正传", 7.5, "汤姆汉克斯"));
// 集合中如果存储对象,方式一:对象类可以实现Comparable接口,指定比较规则
// sorted方法就可以按照规则进行排序,否则报错!
movies.stream().sorted().forEach(s -> System.out.println(s));
System.out.println("-----------------------------------------------");
movies.stream().sorted((m1, m2) -> Double.compare(m2.getScore(), m1.getScore()))
.forEach(System.out::println);
// 3、limit取前几个
System.out.println("-----------------------------------------------");
movies.stream().sorted((m1, m2) -> Double.compare(m2.getScore(), m1.getScore()))
.limit(2).forEach(System.out::println);
// 4、跳过前几个skip
System.out.println("-----------------------------------------------");
movies.stream().sorted((m1, m2) -> Double.compare(m2.getScore(), m1.getScore()))
.skip(2).forEach(System.out::println);
// 5、distinct去重复
System.out.println("-----------------------------------------------");
// 去重复:需要重写对象类的hashCode和equals方法
movies.stream().sorted((m1, m2) -> Double.compare(m2.getScore(), m1.getScore()))
.distinct().forEach(System.out::println);
// 6、map加工方法:把流上的数据加工成新数据。
System.out.println("-----------------------------------------------");
movies.stream().map(m -> {
m.setScore(m.getScore() + 2);
return m;
}).forEach(System.out::println);
//接6
System.out.println("============================");
movies.stream().map(m -> m.getName() + "=>" + m.getScore()).forEach(System.out::println);
// 7、合并流。
// 把两个流接起来 。
Stream<String> s1 = Stream.of("张三", "楚留香", "西门吹牛");
Stream<String> s2 = Stream.of("李四", "石观音");
//如果要合并的类型是猫和狗,那么合并流的类型就是Animal
Stream<String> allStream = Stream.count(s1, s2);
long count = allStream.count(); // count:统计个数
System.out.println(count);
}
}
在 Java 中,
Stream.count()
方法返回的是long
类型,而不是int
类型。这是因为流中的元素数量可能非常大,超出了int
的表示范围(即Integer.MAX_VALUE
,约为 21 亿)。
1.4 Stream流的终结方法
package com.itheima.d1_stream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamTest4 {
public static void main(String[] args) {
// 目标:Stream流的常见终结方法。
List<Movie> movies = new ArrayList<>();
movies.add(new Movie("摔跤吧,爸爸", 9.5, "阿米尔汗"));
movies.add(new Movie("三傻宝莱坞", 8.5, "阿米尔汗2"));
movies.add(new Movie("三傻宝莱坞", 8.5, "阿米尔汗2"));
movies.add(new Movie("阿甘正传", 7.5, "汤姆汉克斯"));
// 1、forEach
movies.stream().forEach(System.out::println);
// 2、count
long count = movies.stream().skip(2).count();
System.out.println(count);
// 3、取最大值
Movie movie = movies.stream().max((m1, m2) -> Double.compare(m1.getScore(), m2.getScore())).get();
System.out.println(movie);
Movie m = movies.stream().max((m1,m2) -> Double.compare(m1.getScore(),m2.getScore())).get();
System.out.println(m);
Movie movie2 = movies.stream().min((m1, m2) -> Double.compare(m1.getScore(), m2.getScore())).get();
System.out.println(movie2);
// 4、收集Stream流:把流中的数据恢复到集合或者数组中去。
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
list.add("张三丰");
// 收集到List集合
Stream<String> stream = list.stream();//流只能用一次
//也在后面用.toList() 但是JDK11才开始支持,在大部分公司还是需要collect(Collectors.toList())
List<String> newList = stream.filter(s -> s.startsWith("张")).collect(Collectors.toList());
System.out.println(newList);
newList.add("张麻子");
System.out.println(newList);
System.out.println("---------------------------------------");
// 收集到Set集合
Stream<String> stream1 = list.stream();
Set<String> set = stream1.collect(Collectors.toSet());
System.out.println(set);
// 收集到数组
System.out.println("---------------------------------------");
Object[] names3 = list.stream().filter(s -> s.length() == 3).toArray();
System.out.println(Arrays.toString(names3));
// 拓展
// String[] names4 = list.stream().filter(s -> s.length() == 3).toArray(String[]::new);
// System.out.println(Arrays.toString(names4));
System.out.println("---------------------------------------");
// 收到到Map集合。
List<Movie> movies1 = new ArrayList<>();
movies1.add(new Movie("摔跤吧,爸爸", 9.5, "阿米尔汗"));
movies1.add(new Movie("三傻宝莱坞", 8.5, "阿米尔汗2"));
movies1.add(new Movie("三傻宝莱坞", 8.5, "阿米尔汗2"));
movies1.add(new Movie("阿甘正传", 7.5, "汤姆汉克斯"));
//取出流为Map时,如果有相同的键和值,要给出提示应该选择哪一个(v1,v2) -> v2
Map<String, Double> map = movies1.stream().limit(3).collect(Collectors.toMap(m1 -> m1.getName()
, m2 -> m2.getScore(), (v1,v2) -> v2));
System.out.println(map);
}
}
2.File、IO流
2.1 概述
目前我们已经学过的存储数据的方案有哪些呢?
但是他们都有一个问题,他们都是内存中的数据容器,但是内存条所记住的数据,在断电或者程序终止时会丢失,不会长期存储。
File:代表文本
IO流:读写数据
2.2 File创建文件对象
File file = new File("文件 / 文件夹 / 绝对路径 / 相对路径 / 不存在文件")
绝对路径是带盘符的
相对路径是不带盘符的,默认到当前工程下寻找文件
2.3 File判断方法
package com.itheima.d2_file;
import java.io.File;
import java.text.SimpleDateFormat;
/**
目标:掌握File提供的判断文件类型、获取文件信息功能
*/
public class FileTest2 {
public static void main(String[] args) {
// 1.创建文件对象,指代某个文件
// File f1 = new File("E:/resource/meinv.jpg");
//File f1 = new File("day08-stream-file-charset\\src\\dlei01.txt");
File f1 = new File("E:/resource/meinv.jpg");
// 2、public boolean exists():判断当前文件对象,对应的文件路径是否存在,存在返回true.
System.out.println(f1.exists());
// 3、public boolean isFile() : 判断当前文件对象指代的是否是文件,是文件返回true,反之。
System.out.println(f1.isFile());
// 4、public boolean isDirectory() : 判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之。
System.out.println(f1.isDirectory());
// 5.public String getName():获取文件的名称(包含后缀)
System.out.println(f1.getName()); // meinv.jpg
// 6.public long length():获取文件的大小,返回字节个数
System.out.println(f1.length());
// 7.public long lastModified():获取文件的最后修改时间。
long time = f1.lastModified();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE a");
System.out.println(sdf.format(time));
// 8.public String getPath():获取创建文件对象时,使用的路径
System.out.println(f1.getPath());
// 9.public String getAbsolutePath():获取绝对路径
System.out.println(f1.getAbsolutePath());
//找到真正的相对路径相对到哪里
File file = new File("");
System.out.println(file.getAbsoluteFile());
}
}
2.4 File的创建和删除
package com.itheima.d2_file;
import java.io.File;
/**
* 目标:掌握File创建和删除文件相关的方法。
*/
public class FileTest3 {
public static void main(String[] args) throws Exception {
// 1、public boolean createNewFile():创建一个新文件(文件内容为空),创建成功返回true,反之。
File f = new File("day08-stream-file-charset/src/dlei02.txt");
System.out.println(f.createNewFile());
// 2、public boolean mkdir():用于创建文件夹,注意:只能创建一级文件夹
File f2 = new File("E:/resource/aaa2");
System.out.println(f2.mkdir()); //true
// 3、public boolean mkdirs()(重点):用于创建文件夹,注意:可以创建多级文件夹
File f3 = new File("E:/resource/aaa3/bb/cc/dd/ee/ff/gg");
System.out.println(f3.mkdirs()); //true
// 3、public boolean delete():删除文件,或者空文件,注意:不能删除非空文件夹。
System.out.println(f.delete()); // 删除文件,即便文件中有内容一样被删除。
// File f4 = new File("E:/resource"); // 不能删除非空文件夹。
// System.out.println(f4.delete());
File f4 = new File("E:/resource/aaa2"); // 只能删除空文件夹。
System.out.println(f4.delete());
}
}
2.5 File的遍历
package com.itheima.d2_file;
import java.io.File;
import java.util.Arrays;
/**
* 目标:掌握File提供的遍历文件夹的方法。
*/
public class FileTest4 {
public static void main(String[] args) {
File f = new File("E:\\磊哥面授\\Java入门至大牛课程");
// 1、public String[] list():获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。
String[] names = f.list();
for (String name : names) {
System.out.println(name);
}
// 2、public File[] listFiles():(重点)获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
File[] files = f.listFiles();
for (File file : files) {
System.out.println(file.getAbsolutePath());
}
}
}
3.方法递归
3.1 概述
什么是递归方法?
- 递归是一种算法,在程序设计语言中广泛应用
- 从形式上说:方法调用自身的形式称为方法递归
递归的形式
- 直接递归:方法自己调自己
- 简介递归:方法调用其他方法,其他方法又回调方法自己
package com.itheima.d3_recursion;
public class RecursionTest1 {
public static void main(String[] args) {
// 目标:认识递归的形式。
test1();//直接递归
test2();//间接递归
}
public static void test2(){
System.out.println("==test2==");
test3();
}
public static void test3() {
System.out.println("==test3==");
test2(); // 间接递归:
}
public static void test1(){
System.out.println("==test1==");
test1(); // 直接递归:自己调用自己
}
}
3.2 递归算法
package com.itheima.d3_recursion;
public class RecursionTest2 {
public static void main(String[] args) {
// 目标:递归的算法基础:解决求阶乘。
System.out.println("1-5的阶乘:" + f(5));
}
public static int f(int n){
if (n == 1) {
return 1;
}
return f(n-1) * n;
}
}
环环相扣,一个结果确定之后当次调用方法就出栈
递归算法三要素:
- 递归的公式:f (n) = f(n - 1) * n;
- 递归的终结点:f(1)
- 递归的方向必须走向终结点: f(5) = f(4) * 5
f(4) = f(3) * 2
f(3) = f(2) * 3
f(2) = f(1) * 2
f(1) = 1
方向一定要有终结点
3.3 递归案例
package com.itheima.d3_recursion;
public class RecursionTest4 {
public static void main(String[] args) {
// 目标:猴子吃桃问题、
//终结点:f(10) = 1
//f(n + 1) = f(n) - f(n) / 2 -1
//变形:2f(n + 1) = 2f(n) - f(n) - 2
//2f(n + 1) = f(n) - 2
//f(n) = 2f(n+ 1) + 2
//递归的方向要朝着终结点走
//递归的方向 :f(1) = ?
System.out.println(f(1));
System.out.println(f(2));
System.out.println(f(3));
}
public static int f(int n){
if (n == 10){
return 1;
}
return 2 * f(n + 1) + 2;
}
}
但并不是所有的递归都会有公式,比如我们下面要学习的文件搜索,要根据用户的线性思维来写代码
3.3 文件搜索
package com.itheima.d3_recursion;
import java.io.File;
public class FileSearchTest5 {
public static void main(String[] args) throws Exception {
// 目标:文件搜索。
File dir = new File("D:/");
searchFile(dir, "QQ.exe");
}
/**
* 文件搜索
* @param dir 传需要查询的目录
* @param fileName 传需要查询的文件名
* 用户思维
*/
public static void searchFile(File dir, String fileName) throws Exception {
// 1、极端情况判断。
if(dir == null || fileName == null || !dir.exists() || dir.isFile()) {
return; // 不搜索的情况
}
// 2、拿当前目录下的全部一级文件对象。
File[] files = dir.listFiles();
// 判断是否可以获取一级文件对象,且一级文件对象的个数是的大于0,存在才可以搜索
if( files != null && files.length > 0) {
// 3、遍历当前全部一级文件对象
for (File f : files) {
// 4、判断f对应的文件对象是文件,还是文件夹
if(f.isFile()) {
//是文件
//5.看这个文件是不是我们要找的
if(f.getName().contains(fileName)) {
System.out.println(f.getAbsolutePath());
// 找到并启动!
Runtime r = Runtime.getRuntime();
r.exec(f.getAbsolutePath());
}
}else {
// 6、是文件夹,递归进入继续寻找
searchFile(f, fileName);
}
}
}
}
}
学到这里,我们会发现File只能操作文件本身,并没有办法读取文件内容,这就需要用到 IO 流l来读取文件内容数据,但是在学习 IO 流之前,我们先学一点前置知识:字符集,提前学会数据在计算机内是怎么存储的。
4.字符集
4.1 常见字符集介绍
标准ASCII字符集:
- ASCII:美国信息交换标准代码,包括了英文、符号等
- 标准ASCII使用1个字节存储一个字符,首位是0,总共可表示123个字符,对美国老来说完全够用
'0' -> 48
'A' -> 65
'a' -> 97
中国编码(GBK编码集(汉字内码扩展规范,国标)):
Unicode字符集(统一码):基本不用,有些专业领域才用
UTF - 8(被全世界所采用):技术人员在开发时都应该使用UTF-8编码
4.2 字符集的编码、解码操作
package com.itheima.d4_cjarset;
import java.util.Arrays;
public class CharSetDemo1 {
public static void main(String[] args) throws Exception {
//目标:掌握字符的编码和解码
String info = "abc我在黑马听磊哥吹nb!";
//1.编码成字节
byte[] bytes = info.getBytes();//默认用平台编码UTF-8编码
System.out.println(Arrays.toString(bytes));
byte[] bytes2 = info.getBytes("GBK");//默认用平台编码UTF-8编码
System.out.println(Arrays.toString(bytes2));
//2.解码成字符
String rs1 = new String(bytes);
System.out.println(rs1);
String rs2 = new String(bytes2,"GBK");
System.out.println(rs2);
}
}
5.作业
5.1 打印文件目录
windows资源管理器中会展示目录中的内容结构,但是java并没有提供展示目录结构的操作,本案例完成按照指定格式打印文件夹的目录结构(包含子文件夹和文件),效果如下图:
package work.Test2;
import java.io.File;
public class test2 {
public static void main(String[] args) {
//创建要遍历的文件夹对象
File file = new File("...");
printDir(file,0);
}
//定义printFileName方法,负责格式化输出文件或文件夹名称,使用制表符进行缩进,形成层级结构
public static void printFileName(int num, File file) {
//打印num个制表符,用于缩进
for (int i = 0; i < num; i++) {
//错误:已经使用了制表符"\t",所以这里不能再使用println换行,要用print
System.out.print("\t");
}
//打印文件或文件夹的名称,格式为 |-名称
System.out.println("|-" + file.getName());
}
//定义printDir方法逐层递进打印文件夹
public static void printDir(File dir,int num) {
//打印当前文件夹的名称
printFileName(num,dir);
num++;
//获取当前文件夹中的所有文件和文件夹
//(重点)获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回
File[] files = dir.listFiles();
//如果文件夹为空或无法访问,直接返回
//files == null代表 1.文件指向一个非文件夹 2.无权限访问 3.路径不存在
//files.length == 0代表 文件夹存在但为空
if (files == null || files.length == 0) {
return;
}
//遍历所有的文件和子文件夹
for (File file : files) {
if (file.isFile()){
//如果是文件,直接调用printFile()方法打印文件名
printFileName(num,file);
} else if (file.isDirectory()) {
//如果是文件夹,递归调用printDir方法
printDir(file,num);
}
}
}
}
在这个目录结构打印程序中,递归的出口(终止条件)是通过一下语句实现的:
//如果文件夹为空或无法访问,直接返回
//files == null代表 1.文件指向一个非文件夹 2.无权限访问 3.路径不存在
//files.length == 0代表 文件夹存在但为空
if (files == null || files.length == 0) {
return;
}
这两个条件是必要的,如果没有这些出口条件,当遇到空文件夹或者无效路径时,程序还会继续调用自身,导致栈溢出错误(StackOverflowError),作为整个程序的终止条件防止无限递归。
在这个案例中,只有一个递归终结条件,而遍历所有文件和子文件夹的循环结束后没有return语句,这是因为此方法会自然结束,当文件夹是非空并且可访问时,遍历完所有的子项后,方法自然结束(无显式return),另外一个原因就是因为此方法是void类型,无需返回结果,可以与下一个案例进行对比,实际上此处也可以添加return,但是没有必要,不会影响程序行为。
5.2 计算整个文件夹的大小
在windows操作系统中可以查看文件夹的大小,直接右键文件夹/属性,如下图: 但是在java中没有提供直接查看文件夹大小的操作,请使用File类和IO流的相关知识,计算文件夹的大小。 要求通过键盘录入文件夹路径(必须保证该文件夹路径是存在的)
package work.Test3;
import java.io.File;
public class test3 {
public static void main(String[] args) {
File dir = new File("D:\\【黑马】2024年11月AI版Java全栈开发V15课程\\配套资料\\02阶段:java基础进阶\\java基础进阶\\day08-Stream、File、方法递归");
long length = getSize(dir);
System.out.println("大小:" + length + "字节");
}
//定义获取文件夹大小的方法
public static long getSize(File dir) {
//1.初始化size变量用于累加文件大小
long size = 0;
//2.获取当前文件夹下的所有文件和子文件夹
File[] files = dir.listFiles();
//3.处理边界情况/定义递归终结条件:文件夹不存在、不可访问或为空
if (files == null || files.length == 0) {
return size;
}
//4.遍历当前文件夹下的所有项
for (File file : files) {
if (file.isFile()) {
//5.如果是文件,直接累加其大小到size中
size += file.length();
} else if (file.isDirectory()) {
//6.如果是文件夹,递归计算其大小并累加
size += getSize(file);
}
}
//7.返回累加的总大小
return size;
}
}
这个案例的递归终结条件是:
//3.处理边界情况/定义递归终结条件:文件夹不存在、不可访问或为空
if (files == null || files.length == 0) {
return size;
}
这两个条件共同确保递归在遇到无法继续处理的情况时安全终止,并正确返回结果size,避免无限递归和错误计算。
计算文件夹大小为什么会有多次return?
这是因为方法返回值类型为long,必须通过return size返回计算结果。