Stream流和IO流
1、Stream流
1.1 什么是Stream流?
- 也叫Stream流,是Jdk8开始新增的一套API(java.util.stream.*),可以用于操作集合或者数组的数据。
- 优势:Stream流大量的结合了Lambda的语法风格来编程,提供了一种更加强大,更加简单的方式操作集合或者数组中的数据,代码更简洁,可读性更好
Collection<String> names = new ArrayList<>();
Collections.addAll(names,"陆林轩","李星云","姬如雪","张子凡","女帝","张谏之");
// 找出 张谏之
ArrayList<String> name = new ArrayList<>();
// Stream流写法,找出 张谏之
List<String> zhangList = names.stream().filter(s -> s.startsWith("张") && s.length() == 3).filter(s -> s.endsWith("之")).collect(Collectors.toList());
System.out.println(zhangList);
1.2 获取Stream流
// List集合获取Stream流
List<String> names = new ArrayList<>();
Collections.addAll(names, "陆林轩", "李星云", "姬如雪", "张子凡", "女帝", "张谏之");
Stream<String> ListStream = names.stream();
ListStream.filter(s -> s.length() == 2).forEach(System.out::println);
// Set集合获取Stream流
HashSet<String> set = new HashSet<>();
Collections.addAll(set, "陆林轩", "李星云", "姬如雪", "张子凡", "女帝", "张谏之", "君子剑");
Stream<String> SetStream = set.stream();
List<String> zi = SetStream.filter(s -> s.contains("子")).collect(Collectors.toList());
System.out.println(zi);
zi.forEach(System.out::println);
// Map集合如何获取Stream流
HashMap<String, Double> map = new HashMap<>();
map.put("陆林轩", 8.6);
map.put("姬如雪", 9.0);
map.put("李星云", 9.6);
map.put("张子凡", 9.4);
//1. 看作一个整体写法
Set<Map.Entry<String, Double>> entries = map.entrySet();
Stream<Map.Entry<String, Double>> kvs = entries.stream();
kvs.filter(e->e.getKey().contains("张")).forEach(e -> System.out.println(e.getKey() + " : "+e.getValue()));
//2. 拆分开写法
Set<String> ks = map.keySet();
Collection<Double> vs = map.values();
// 数组如何获取Stream流
String[] arr = new String[]{"姬如雪", "张子凡", "李星云", "陆林轩"};
//方法一
Stream<String> arrStream1 = Arrays.stream(arr);
//方法二
Stream<String> arrStream2 = Stream.of(arr);
1.3 Stream流常见的中间方法
package com.dapixiu;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
List<Double> scores = new ArrayList<>();
Collections.addAll(scores, 83.2, 12.2, 34.5, 67.1, 58.5, 78.4, 99.8);
// 1.找出成绩大于等于60,升序排列,并输出。
scores.stream().filter(s -> s >= 60).sorted().forEach(System.out::println);
// 2.操作对象
ArrayList<Student> students = new ArrayList<>();
Student ss = new Student("李星云", 23, 175.0);
Student s1 = new Student("李星云", 23, 175.0);
Student s2 = new Student("姬如雪", 18, 165.0);
Student s3 = new Student("张子凡", 24, 174.0);
Student s4 = new Student("陆林轩", 19, 170.0);
Student s5 = new Student("旱魃", 30, 180.0);
Collections.addAll(students, s1, s2, s3, s4, s5, ss);
// 找出年龄在[23,30],降序排列
students.stream().filter(s -> s.getAge() >= 23 && s.getAge() <= 30).sorted((o1, o2) -> o2.getAge() - o1.getAge()).forEach(System.out::println);
// 选取身高最高的三位,输出
List<Student> collect = students.stream().sorted((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()))
// 截取前三个
.limit(3).collect(Collectors.toList());
System.out.println(collect);
// 取出身高倒数2名的,输出
students.stream().sorted((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()))
// 跳过前三个
.skip(3).forEach(System.out::println);
// 找出身高超过168的人的名字,并去重
students.stream().filter(s -> s.getHeight() > 173)
//获取名字 去重
.map(Student::getName).distinct()
.forEach(System.out::println);
}
}
1.4 Stream流常见的终结方法
// 操作对象
ArrayList<Student> students = new ArrayList<>();
Student ss = new Student("李星云", 23, 175.0);
Student s1 = new Student("李星云", 23, 175.0);
Student s2 = new Student("姬如雪", 18, 165.0);
Student s3 = new Student("张子凡", 24, 174.0);
Student s4 = new Student("陆林轩", 19, 170.0);
Student s5 = new Student("旱魃", 30, 180.0);
Collections.addAll(students, s1, s2, s3, s4, s5, ss);
// 1.计算超过170的有几人
long num = students.stream().filter(s -> s.getHeight() > 170)
.count();
System.out.println(num);
// 2. 找出身高最高的学生对象,并输出
Student student = students.stream().distinct().max((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight()))
// .get()得到对象
.get();
System.out.println(student);
// 3. 找出身高最低的学生对象,并输出
Student student1 = students.stream().distinct().min((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight()))
.get();
System.out.println(student1);
// 4. 找出身高超过170的,放到一个集合中输出
List<Student> studentList = students.stream().filter(s -> s.getHeight() > 170).collect(Collectors.toList());
System.out.println(studentList);
// 5. 找出身高超过170的学生对象,并把学生对象的名字和身高,存入到一个Map集合返回
Map<String, Double> map = students.stream().filter(s -> s.getHeight() > 170)
.collect(Collectors.toMap(
body -> body.getName(), body -> body.getHeight(), // 分别以名字和身高作为键和值
(existing, replacement) -> Math.max(existing, replacement) // 合并函数:取身高较高的值
));
System.out.println(map);
// 5.简化:
// PS: 如果要用.distinct()去重的话,需要重写 equals()和hashCode()
// 5. 找出身高超过170的学生对象,并把学生对象的名字和身高,存入到一个Map集合返回
Map<String, Double> map = students.stream().filter(s -> s.getHeight() > 170)
.collect(Collectors.toMap(
Student::getName, Student::getHeight, // 分别以名字和身高作为键和值
Math::max // 合并函数:取身高较高的值
));
2、File-操作文件
2.1 判断文件类型 获取文件信息
//1、创建File对象,指向指定路径的文件
File file = new File("D:\\Users\\ZhangYao\\Desktop\\常用方法.md");
//2、public boolean exists():判断当前文件对象,对应的文件路径是否存在,存在返回true.System.out.println(f1.exists());
boolean exists = file.exists();
System.out.println(exists);
//3、public boolean isFile():判断当前文件对象指代的是否是文件,是文件返回true,反之。
boolean isFile = file.isFile();
System.out.println(isFile);
//4、public boolean isDirectory() :判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之。
boolean isDirectory = file.isDirectory();
System.out.println(isDirectory);
//5.public string getName():获取文件的名称(包含后缀)
String name = file.getName();
System.out.println(name);
//6.public long length():获取文件的大小,返回字节个数
long size = file.length();
System.out.println(size);
// 8.public String getPath():获取创建文件对象时,使用的路径
String path = file.getPath();
System.out.println(path);
// 9.public string getAbsolutePath():获取绝对路径
File absoluteFile = file.getAbsoluteFile();
System.out.println(absoluteFile);
//7.public long lastModified():获取文件的最后修改时间
long lastModified = file.lastModified();
// 将 long 类型的时间戳转换为 LocalDateTime 对象
LocalDateTime dateTime = LocalDateTime.ofEpochSecond(lastModified / 1000,
(int) (lastModified % 1000 * 1000000),
java.time.ZoneOffset.of("+8"));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDate = formatter.format(dateTime);
System.out.println(formattedDate);
2.2 创建文件 删除文件
//1、public boolean createNewFile():创建一个新文件(文件内容为空),创建成功返回true,反之。
File file = new File("D:\\Users\\ZhangYao\\Desktop\\测试.md");
boolean newFile = file.createNewFile();
System.out.println(newFile);
//2、public boolean mkdir():用于创建文件夹,注意:只能创建一级文件夹
File filePackage = new File("D:\\Users\\ZhangYao\\Desktop\\测试文件夹");
boolean mkdir = filePackage.mkdir();
System.out.println(mkdir);
// 3、public boolean mkdirs():用于创建文件夹,注意:可以创建多级文件夹,返回创建的文件夹名称
File filePackages = new File("D:\\Users\\ZhangYao\\Desktop\\测试文件夹\\aaa\\bbb\\ccc");
filePackages.mkdirs();
System.out.println(filePackages);
// 3、public boolean delete():删除文件,或者空文件,注意:不能删除非空文件夹。
boolean delete = file.delete();
System.out.println(delete);
2.3 遍历文件夹
// 1、public String[] list():获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。
File f1 = new File("D:/Users/ZhangYao/Desktop/测试文件夹/q/w/e/r/t");
String[] list = f1.list();
for (String s : list) {
System.out.println(s);
}
//2、public File[] listFiles():(重点)获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
File[] files = f1.listFiles();
for (File file : files) {
System.out.println(file);
}
// 方法调用
searchFile(new File("D:/"),"QQ.exe");
/**
* 在指定目录中搜索具有给定名称的文件
* 如果找到匹配名称的文件,将打印出文件的绝对路径
* 如果目录为null,不存在或是一个文件,函数将直接返回,不做任何操作
*
* @param dir 要搜索的目录
* @param fileName 要搜索的文件名称
*/
public static void searchFile(File dir, String fileName) {
// 检查目录参数是否有效,无效则直接返回
if (dir == null || !dir.exists() || dir.isFile()) return;
// 获取目录下的所有文件和子目录
File[] files = dir.listFiles();
// 如果文件数组不为空,则遍历每个文件或子目录
if (files != null) {
for (File f : files) {
// 如果是文件,则检查文件名是否包含目标名称
if (f.isFile()) {
// 如果文件名包含目标名称,则打印文件的绝对路径
if (f.getName().contains(fileName)) System.out.println("找到了" + f.getAbsoluteFile());
} else {
// 如果是子目录,则递归调用searchFile函数继续搜索
searchFile(f, fileName);
}
}
}
}
// 找到后启动
Runtime runtime = Runtime.getRuntime();
runtime.exec(f.getAbsolutePath());
2.4 删除非空文件夹
// 方法一
Path dir = Paths.get("path/to/your/directory"); // 替换为你要删除的目录路径
try {
deleteDirectory(dir);
System.out.println("目录已成功删除");
} catch (IOException e) {
System.err.println("删除目录时发生错误: " + e.getMessage());
}
//自定义方法
public static void deleteDirectory(Path directory) throws IOException {
if (Files.exists(directory)) {
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
} else {
System.out.println("指定的目录不存在");
}
}
// 方法二
deleteDir(new File("D:\\Users\\ZhangYao\\Desktop\\测试.md"));
public static void deleteDir(File dir) {
if (dir == null || !dir.exists()) return;
if (dir.isFile()) {
dir.delete();
return;
}
//dir存在,且是文件夹。拿到里边的一级文件对象。
File[] files = dir.listFiles();
if (files == null) return;
//这是一个有内容的文件夹,干掉里边的内容,再干掉自己。
for (File file : files) {
if (file.isFile()) {
file.delete();
} else {
deleteDir(file);
}
dir.delete();
}
System.out.println("删除已完成");
}
3、IO流-字节流
// 编码
String data = "传国玉玺";
byte[] bytes = data.getBytes("UTF-8");
System.out.println(Arrays.toString(bytes));
// 解码
String s = new String(bytes,"UTF-8");
System.out.println(s);
- 字节流—>适合操作所有类型的文件
- 字符流—>只适合操作纯文本文件
3.1 文件字节输入流
读取文本内容:
// 创建文件输入流,指向待读取的文本文件
InputStream is = new FileInputStream("D:\\Users\\ZhangYao\\Desktop\\测试文本.txt");
// 读取文件中的第一个字节,并转换为字符打印出来
int read = is.read();
System.out.println((char) read);
// 初始化变量b用于存储从文件中读取的字节
int b;
// 循环读取文件中的字节,直到文件结束(返回-1)
while ((b = is.read()) != -1) {
// 将读取的字节转换为字符,并打印出来
System.out.print((char) b);
}
// 文件读取完毕后,关闭输入流释放资源
is.close();
// 创建文件输入流,指向待读取的文本文件
InputStream is = new FileInputStream("D:/Users/ZhangYao/Desktop/测试文本.txt");
byte[] buffer = new byte[2];
int len = is.read(buffer);
String rs = new String(buffer);
System.out.println(rs);
System.out.println("当前读取的字节数量: " + len);
int len2 = is.read(buffer);
String rs2 = new String(buffer);
System.out.println(rs2);
System.out.println("当前读取的字节数量: " + len2);
// 文档内容是Scs, 第一次读取结果是Sc,读取的字节数量是2. 第二次读取结果是Sc,读取的字节数量是1
// 显然第二次读取多了个c,来自上一次读取结果的最后位
// 都多少,倒出多少
String s = new String(buffer, 0, len2);
System.out.println(s);
// 每次读取多个字节到字节数组中去,返回读取的字节数量,读取完毕会返回-1.
int len3 = is.read(buffer);
System.out.println(len3); // 读取完毕, -1
// 改进:
InputStream is = new FileInputStream("D:/Users/ZhangYao/Desktop/测试文本.txt");
byte[] buffer = new byte[2];
int len; // 记录每次读取的数量
while ((len = is.read(buffer)) != -1){
String s = new String (buffer, 0, len);
System.out.print(s);
}
is.close();
// 创建文件输入流,指向待读取的文本文件
InputStream is = new FileInputStream("D:/Users/ZhangYao/Desktop/测试文本.txt");
File f = new File("D:/Users/ZhangYao/Desktop/测试文本.txt");
int size = (int) f.length();
byte[] buffer = new byte[size];
int len = is.read(buffer, 0, size);
String s = new String(buffer);
System.out.println(s);
System.out.println(size == len);
is.close();
// 方法一:
// 创建文件输入流,指向待读取的文本文件
InputStream is = new FileInputStream("D:/Users/ZhangYao/Desktop/测试文本.txt");
// 获取文件对象,用于获取文件长度
File f = new File("D:/Users/ZhangYao/Desktop/测试文本.txt");
// 获取文件长度,用于初始化字节数组
int size = (int) f.length();
// 根据文件长度创建字节数组,用于存储文件内容
byte[] buffer = new byte[size];
// 从文件中读取内容到字节数组中
int len = is.read(buffer);
// 将字节数组转换为字符串
String s = new String(buffer, 0, size);
// 输出读取到的字符串内容
System.out.println(s);
// 检查读取的字节数是否与文件长度一致,以验证是否完整读取了文件内容
System.out.println(size == len);
is.close();
// 方法二:
// 创建文件输入流,指向待读取的文本文件
InputStream is = new FileInputStream("D:/Users/ZhangYao/Desktop/测试文本.txt");
// 读取文件所有字节到字节数组buffer中
byte[] buffer = is.readAllBytes();
// 将字节数组转换为字符串
String s = new String(buffer);
// 输出字符串到控制台
System.out.println(s);
is.close();
3.2 文件字节输出流
- 写入会覆盖原文件内容。弱文件不存在,可自动产生文件。
// 1、创建文件输出流,指向待写入的文本文件
FileOutputStream os = new FileOutputStream("D:/Users/ZhangYao/Desktop/测试文本.txt");
// 2. 写字节出去到文件
// 写入一个字节
os.write(97);
// 写入一个字节数组
byte[] bytes = "残尸败蜕(降臣)、血染河山(侯卿)、赤地千里(焊魃)、冥海无岸(萤勾)".getBytes();
os.write(bytes);
// 写入字节数组的一部分
os.write(bytes, 0, 6);
os.close();
- 文件输出流追加参数
true
,则不再覆盖原文件内容
new FileOutputStream("D:/Users/ZhangYao/Desktop/测试文本.txt",true);
- 换行:
os.write(bytes);
4、IO流-字符流
- 字节流,适合文本复制,不适合读写文本文件。
- 字符流,非常适合读写文本文件内容。
4.1 文件字符输入流
// 创建一个字节输入流,与源文件连接
String path = "D:/Users/ZhangYao/Desktop/测试文本.txt";
try (Reader fr = new FileReader(path);
) {
// 读取文件内容,每次读取一个字符,性能是比较差的。
int c;
while ((c = fr.read()) != -1) {
System.out.print((char) c);
}
} catch (Exception e) {
e.printStackTrace();
}
// 创建一个字节输入流,与源文件连接
String path = "D:/Users/ZhangYao/Desktop/测试文本.txt";
try (Reader fr = new FileReader(path);
) {
// 改进后: 读取文件内容
int len;
char[] buffer = new char[10];
while ((len = fr.read(buffer)) != -1){
String s = new String(buffer, 0, len);
System.out.print(s);
}
} catch (Exception e) {
e.printStackTrace();
}
4.2 文件字符输出流
String path = "D:/Users/ZhangYao/Desktop/测试文本.txt";
try (
// 创建一个字节输出流,与源文件连接
FileWriter fw = new FileWriter(path)
) {
// 读取文件内容
fw.write('f');
fw.write("君艺心");
fw.write("\r\n");
fw.write(new char[]{'1', '2', '3'});
fw.write("君有云", 0, 2);
fw.write(new char[]{'1', '3', '4', '6', '7'}, 1, 4);
fw.close();
// 刷新
fw.flush();
} catch (Exception e) {
e.printStackTrace();
}
字符流、字节流–>小结
5、缓冲流
5.1 字符缓冲输入流
原理:
- 字节缓冲输入流自带了8KB缓冲池;
- 字节缓冲输出流也自带了8KB缓冲池;
原始流的文件复制:
// 原始文件路径
String path = "D:/Users/ZhangYao/Desktop/测试文本.txt";
// 目标复制文件路径
String copy = "D:/Users/ZhangYao/Desktop/测试文本copy.txt";
// 使用try-with-resources确保文件流在操作后能自动关闭
try (
// 创建文件输入流,用于读取原始文件
InputStream is = new FileInputStream(path);
// 创建文件输出流,用于写入目标复制文件
OutputStream os = new FileOutputStream(copy)
) {
// 创建一个缓冲区,用于临时存储从原始文件读取的数据
byte[] buffer = new byte[1024];
int len;
// 循环读取原始文件数据,直到文件结束
while ((len = is.read(buffer)) != -1) {
// 将读取的数据写入目标复制文件
os.write(buffer, 0, len);
}
// 打印复制完成的消息
System.out.println("copy完成");
} catch (Exception e) {
// 打印异常信息,便于调试和错误追踪
e.printStackTrace();
}
缓冲流的文件复制:
// 原始文件路径
String path = "D:/Users/ZhangYao/Desktop/测试文本.txt";
// 目标复制文件路径
String copy = "D:/Users/ZhangYao/Desktop/测试文本copy.txt";
// 使用try-with-resources确保文件流在操作后能自动关闭
try (
// 创建文件输入流,用于读取原始文件
InputStream is = new FileInputStream(path);
InputStream bis = new BufferedInputStream(is);
// 创建文件输出流,用于写入目标复制文件
OutputStream os = new FileOutputStream(copy);
OutputStream bos = new BufferedOutputStream(os);
) {
// 创建一个缓冲区,用于临时存储从原始文件读取的数据
byte[] buffer = new byte[1024];
int len;
// 循环读取原始文件数据,直到文件结束
while ((len = bis.read(buffer)) != -1) {
// 将读取的数据写入目标复制文件
bos.write(buffer, 0, len);
}
// 打印复制完成的消息
System.out.println("copy完成");
} catch (Exception e) {
// 打印异常信息,便于调试和错误追踪
e.printStackTrace();
}
// 原始文件路径
String path = "D:/Users/ZhangYao/Desktop/测试文本.txt";
FileReader fr = new FileReader(path);
BufferedReader bfr = new BufferedReader(fr);
String line;
while ((line = bfr.readLine()) != null){
System.out.println(line);
}
5.2 字符缓冲输出流
// 原始文件路径
String path = "D:/Users/ZhangYao/Desktop/测试文本.txt";
FileWriter fw = new FileWriter(path);
BufferedWriter bfw = new BufferedWriter(fw);
bfw.write("暴富 暴富");
bfw.flush();
bfw.close();
6、转换流
6.1 字符输入转换流
6.2 字符输出转换流
7、打印流
String path = "D:/Users/ZhangYao/Desktop/测试文本.txt";
PrintStream ps = new PrintStream(path, Charset.forName("UTF-8"));
ps.println("天官赐福");
ps.println("百无禁忌");
- 高级流是不支持数据的追加的
- 要想数据追加,需要用低级流
String path = "D:/Users/ZhangYao/Desktop/测试文本.txt";
PrintStream ps = new PrintStream(String.valueOf(new FileOutputStream(path, true)),Charset.forName("UTF-8"));
8、数据流
String path = "D:/Users/ZhangYao/Desktop/测试文本.txt";
DataOutputStream dos = new DataOutputStream(new FileOutputStream(path));
dos.writeInt(6);
dos.writeDouble(66.7);
dos.writeUTF("哈士奇");
String path = "D:/Users/ZhangYao/Desktop/测试文本.txt";
DataInputStream dis = new DataInputStream(new FileInputStream(path));
int i = dis.readInt();
System.out.println(i);
double v = dis.readDouble();
System.out.println(v);
String s = dis.readUTF();
System.out.println(s);
- 读取的顺序要求可写入的顺序保持一致
9、序列化流
- 对象序列化:把java对象写入到文件中去。
- 对象反序列化: 把文件里的java对象读取出来。
package com.dapixiu;
import java.io.*;
import java.nio.charset.Charset;
public class Main {
/**
* 程序的入口点
* 本程序演示了如何将一个User对象序列化到文件中,然后从该文件中反序列化出来
* @param args 命令行参数,未使用
* @throws Exception 如果文件输入输出发生错误,将抛出异常
*/
public static void main(String[] args) throws Exception {
// 定义文件路径
String path = "D:/Users/ZhangYao/Desktop/测试文本.txt";
// 创建一个User对象
User u1 = new User("admin", "zhangsan", 18, "123456");
// 创建一个对象输出流,用于将User对象序列化到文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
// 将User对象写入文件
oos.writeObject(u1);
// 创建一个对象输入流,用于从文件中反序列化User对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
// 从文件中读取User对象
User u = (User) ois.readObject();
// 输出反序列化的User对象
System.out.println(u);
}
}
- 敏感信息前加
transient
,不参与序列化
public class User implements Serializable {
private String loginName;
private String userName;
private int age;
private transient String password;
······
}