深入探究Java IO流操作与Lambda表达式的优雅结合
- 1. IO流
- 1.1 IO流的概念
- 1.2 IO流的分类
- 1.3 常见的IO流操作
- 1.3.1 字节流操作
- 1.3.2 字符流操作
- 1.3.3 缓冲流
- 2. Lambda表达式
- 2.1 Lambda 表达式使用条件
- 2.2 Lambda 表达式的分类
- 2.3 Lambda 表达式在 IO 流操作中的应用
- 2.4 常见的 IO 流和 Lambda 表达式使用场景
- 3. 流式操作
- 3.1 流操作的概念
- 3.2 流操作的分类
- 3.3 常用流操作的API
- 3.4 案例
- 4. 结语
欢迎来到本文,今天我们将一同探讨Java中关于IO流操作和Lambda表达式的精妙结合。随着JDK8的到来,Java语言引入了Lambda表达式,使得代码编写更加简洁、优雅。而在IO流操作中,这种简洁的编程风格同样能够发挥出色的作用。让我们一起深入了解,如何将Lambda表达式融入IO操作,使得我们的代码更加高效、易读。
1. IO流
1.1 IO流的概念
在开始讨论Lambda表达式的应用之前,我们首先需要理解IO流操作的基础概念。IO流(Input/Output Stream
)是一种数据传输方式,用于在程序与外部资源(如文件、网络等)之间进行数据交换。IO流分为字符流和字节流两种,字符流适用于处理字符数据,而字节流则用于处理二进制数据。
1.2 IO流的分类
- 根据数据处理的不同类型分为:字节流和字符流
- 根据数据流向不同分为:输入流和输出流
- 根据流的角色来分:节点流和处理流
- 节点流:可以从一个特定的IO设备(如磁盘、网路)读/写数据的流。也被称为低级流。节点流的另一端是明确的,是实际读写数据的流,读写一定是建立在节点流基础上进行的。
- 处理流:用于对一个已存在的流进行连接或封装,通过封装后的流来实现数据读/写功能。也称为高级流。处理流不能独立存在,必须连接在其他流上,目的是当数据流经过当前流时对数据进行加工处理来简化我们对数据的操作。
- 字符流的由来:因为数据编码的不同,而有了对字符进行高效操作的流对象,本质上其实就是对于字节流的读取时,去查了指定的码表。
- 字节流和字符流的区别:
读写单位的不同
:字节流以字节(8bit)为单位。字符流以字符为单位,根据码表映射字符,一次可能读多个字节。处理对象不同
:字节流可以处理任何类型的数据,如图片、avi等,而字符流只能处理字符流类型的数据。
1.3 常见的IO流操作
1.3.1 字节流操作
字节流适用于处理图片、音频、视频等二进制文件,常见的字节流类包括InputStream
和OutputStream
。例如,通过FileInputStream
读取二进制文件,通过FileOutputStream
写入二进制文件。
-
文件操作字节输入流
public class TestFileInputStream { public static void main(String[] args) throws IOException { readFile2(); } public static void readFile() throws IOException { // 1. 确定文件 String filePath = "D:/test/a.txt"; // 2. 开启FileInputStream通道 FileInputStream fis = new FileInputStream(filePath); // 3. 读取数据,需要准备一个byte类型数组 当前缓冲数组的容量为8kb byte[] buf = new byte[1024 * 8]; int length = -1; while ((length = fis.read(buf)) != -1) { // 将读取到的数据,转换成一个String字符串 System.out.println(new String(buf, 0, length)); } // 4. 关闭资源 fis.close(); } public static void readFile2() throws IOException { // 1. 确定文件 String filePath = "D:/test/a.txt"; // 2. 开启FileInputStream通道 FileInputStream fis = new FileInputStream(filePath); int content = -1; while ((content = fis.read()) != -1) { System.out.println((char) content); } // 4. 关闭资源 fis.close(); } }
-
文件操作字节输出流
public class TestFileOutputStream { public static void main(String[] args) throws IOException { writeFile(); } public static void writeFile() throws IOException { // 1. 确定文件位置 String filePath = "D:/test/a.txt"; // 2. 创建FileOutputStream流管道 FileOutputStream fos = new FileOutputStream(filePath); // 3. 写入数据 fos.write(550); fos.write("ABCDEFG".getBytes("UTF-8")); fos.write("ABCDEFG".getBytes("UTF-8"), 2, 5); // 4. 关闭资源 fos.close(); } }
-
字节流拷贝文件
public class TestCopyFile { public static void main(String[] args) throws IOException { long begin = System.currentTimeMillis(); // 1. 创建FileInputStream 对象 FileInputStream fis = new FileInputStream("D:/test/a.txt"); // 2. 创建FileOutputStream对象 FileOutputStream fos = new FileOutputStream("D:/test/b.txt"); // 3. 准备缓冲数组 byte[] buf = new byte[1024 * 8]; int length = -1; // 4. 读取数据,写入数据 while ((length = fis.read(buf)) != -1) { fos.write(buf, 0, length); } // 5. 关闭资源,先开后关,后开先关 fos.close(); fis.close(); long end = System.currentTimeMillis(); System.out.println("time: " + (end - begin)); } }
1.3.2 字符流操作
字符流适合处理文本文件,常见的字符流类包括Reader
和Writer
。例如,通过FileReader
读取文本文件,通过BufferedWriter
写入文本文件,可以有效地提高IO效率。
字符流操作限制
- 操作对应的文件有且只能是记事本可以打开的可视化文本文件。
- 要求操作过程中,文件对应的编码集和当前程序对应编码集一致。
- 文件操作字符输入流
public class TestFileReader { public static void main(String[] args) throws IOException { // 1. 创建文件字符流对象 Reader reader = new FileReader("D:/test/a.txt"); // 2. 读取数据 char[] buf = new char[1024 * 8]; int length = -1; while ((length = reader.read(buf)) != -1) { System.out.println(new String(buf, 0, length)); } // 3. 关闭资源 reader.close(); } }
- 文件操作字符输出流
public class TestFileWriter { public static void main(String[] args) throws IOException { // 1. 创建文件字符输出流 第二个参数为append,true表示在文件中追加内容,false不追加,直接覆盖 FileWriter fileWriter = new FileWriter("D:/test/a.txt", true); // 2. 写入数据 fileWriter.write("== 张三真好"); // 3. 关闭资源 fileWriter.close(); } }
- 字符流拷贝文件
public class TestCopyFileByString { public static void main(String[] args) throws IOException { long begin = System.currentTimeMillis(); // 1. 创建文件字符输入流 FileReader fr = new FileReader("D:/test/a.txt"); // 2. 创建文件字符输出流 FileWriter fw = new FileWriter("D:/test/c.txt"); // 3. 拷贝数据 char[] buf = new char[1024 * 8]; int length = -1; while ((length = fr.read(buf)) != -1) { fw.write(buf, 0, length); } // 4. 关闭资源 fw.close(); fr.close(); long end = System.currentTimeMillis(); System.out.println("Time : " + (end - begin) + "ms"); } }
1.3.3 缓冲流
为了提高IO效率,Java提供了缓冲流,如BufferedReader
和BufferedWriter
。它们可以减少实际IO操作次数,从而提高读写效率。
-
字节缓冲流控制输入输出
public class TestBufferCharStream { public static void main(String[] args) throws IOException { bufferWrite(); bufferRead(); } public static void bufferWrite() throws IOException { long begin = System.currentTimeMillis(); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:/test/a.txt")); String str = "如何学好Java\n"; int num = 1000000; while (num > 0) { bos.write(str.getBytes()); num--; } bos.close(); long end = System.currentTimeMillis(); // 118ms System.out.println(end - begin + "ms"); } public static void bufferRead() throws IOException { long begin = System.currentTimeMillis(); BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:/test/a.txt")); byte[] buf = new byte[1024 * 8]; int length = -1; while ((length = bis.read(buf)) != -1) { // System.out.println(new String(buf, 0, length)); } bis.close(); long end = System.currentTimeMillis(); // 9ms System.out.println((end - begin) + "ms"); } }
-
字符缓冲流控制输入输出
public class TestBufferStringStream { public static void main(String[] args) throws IOException { // bufferWrite(); bufferRead(); } public static void bufferWrite() throws IOException { long begin = System.currentTimeMillis(); BufferedWriter bos = new BufferedWriter(new FileWriter("D:/test/a.txt")); String str = "如何学好Java\n"; int num = 1000000; while (num > 0) { bos.write(str); num--; } bos.close(); long end = System.currentTimeMillis(); // 71ms System.out.println(end - begin + "ms"); } public static void bufferRead() throws IOException { long begin = System.currentTimeMillis(); BufferedReader bis = new BufferedReader(new FileReader("D:/test/a.txt")); char[] buf = new char[1024 * 8]; int length = -1; while ((length = bis.read(buf)) != -1) { // System.out.println(new String(buf, 0, length)); } bis.close(); long end = System.currentTimeMillis(); // 55ms System.out.println((end - begin) + "ms"); } }
2. Lambda表达式
在JDK8之后,Lambda表达式成为了Java的一个重要特性,它可以让我们以更简洁的方式实现函数式编程。Lambda表达式的语法形式是 (参数列表) -> 表达式或代码块。结合Lambda表达式,我们可以以更紧凑的代码来完成各种操作。
2.1 Lambda 表达式使用条件
- 存在接口
- 接口中有且只有一个未实现的方法,该接口可以认为是
[函数式接口]
。在源码中存在使用注解@FunctionalInterface
来约束当前接口 - Lambda 表达式用于方法中,要求当前方法的参数是接口,需要为接口的实现类对象。
2.2 Lambda 表达式的分类
- 无参无返回值
/* * @FunctionalInterface 函数式接口注解,要求当前接口中有且只有一个未实现方法 */ @FunctionalInterface interface A { void test(); } public class TestNoArgsVoid { public static void main(String[] args) { testLambda(() -> { System.out.println("无参无返回值Lambda表达式"); }); // 当Lambda表达式有且只有一行,可以省略大括号 testLambda(() -> System.out.println("当Lambda表达式有且只有一行,推荐这样写")); } public static void testLambda(A a) { a.test(); } }
- 有参无返回值
@FunctionalInterface interface Consumer<T> { // 接口数据方法 需要的类型是泛型 void accept(T t); } public class TestAllArgsVoid { public static void main(String[] args) { testLambda("让我看到你的眼睛!!!", // 脑补数据类型 t ===> String 类型 t会成为当前Lambda表达式对应代码块中的局部变量参数 (str) -> System.out.println(str)); ArrayList<String> list = new ArrayList<>(); list.add("雪碧"); list.add("芬达"); list.add("可乐"); list.add("王老吉"); testLambda(list, (scopeList) -> { for (String str : scopeList) { System.out.println(str); } }); testLambda(10, (i) -> System.out.println("low : " + i)); } /** * 当前方法带有自定义泛型,泛型对应的具体数据类型,通过第一个参数约束 * * @param <T> 自定义泛型 * @param t 用于约束当前泛型对应具体数据类型的参数 * @param consumer 消费处理数据的接口 */ public static <T> void testLambda(T t, Consumer<T> consumer) { consumer.accept(t); } }
- 无参有返回值
interface Supplier<T> { T get(); } public class TestNoArgs { public static void main(String[] args) { String str = "张三去上网"; // 方法中使用的Lambda表达式是可以在当前大括号范围以内使用对应的局部变量 String reverseStr = testLambda(str, () -> new StringBuilder(str).reverse().toString()); System.out.println(reverseStr); int[] arr = {2, 3, 4, 12, 13, 56, 71, 1}; int maxValue = testLambda2(arr, () -> { int max = arr[0]; for (int i = 1; i < arr.length - 1; i++) { if (arr[i-1] < arr[i]) { max = arr[i]; } } return max; }); System.out.println(maxValue); } public static <T> T testLambda(T t, Supplier<T> supplier) { return supplier.get(); } public static int testLambda2(int[] arr, Supplier<Integer> supplier) { return supplier.get(); } }
- 有参有返回值
interface Function<T, R> { R apply(T t); } public class TestAllArgs { public static void main(String[] args) { Student[] students = new Student[10]; for (int i = 0; i < students.length; i++) { int id = i + 1; String name = "荀彧" + (i + 1); int age = (int) (Math.random() * 17); char gender = '男'; students[i] = new Student(id, name, age, gender); } Arrays.sort(students, (stu1, stu2) -> stu1.getAge() - stu2.getAge()); for (Student student : students) { System.out.println(student.toString()); } System.out.println(); // 字符串转Student对象 String str = "11,张三,99,X"; Student transformStu = transform(str, Student.class, (info) -> { String[] split = info.split(","); int id = Integer.parseInt(split[0]); String name = split[1]; int age = Integer.parseInt(split[2]); char gender = split[3].charAt(0); return new Student(id, name, age, gender); }); System.out.println(transformStu.toString()); } public static <T, R> R transform(T t, Class<R> cls, Function<T, R> fun) { return fun.apply(t); } } class Student { int id; String name; int age; char gender; public int getId() { return id; } public void setId(int id) { this.id = id; } 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 char getGender() { return gender; } public void setGender(char gender) { this.gender = gender; } public Student(int id, String name, int age, char gender) { this.id = id; this.name = name; this.age = age; this.gender = gender; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", gender=" + gender + '}'; } }
2.3 Lambda 表达式在 IO 流操作中的应用
让我们看看如何将Lambda表达式与IO流操作相结合,提升代码的可读性和简洁性。
-
文件读取操作
传统的文件读取方式可能需
要一些繁琐的代码,如文件的打开、关闭等。然而,结合Lambda表达式,我们可以通过Files类中的lines()方法,更加轻松地读取文件内容。import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.stream.Stream; public class FileReadWithLambda { public static void main(String[] args) { String filePath = "example.txt"; try (Stream<String> lines = Files.lines(Paths.get(filePath))) { lines.forEach(line -> System.out.println(line)); } catch (IOException e) { e.printStackTrace(); } } }
-
文件写入操作
同样地,Lambda表达式也可以在文件写入操作中大显身手。通过Files类中的write()方法,我们可以更加简洁地将内容写入文件。import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; public class FileWriteWithLambda { public static void main(String[] args) { String filePath = "output.txt"; List<String> content = List.of("Hello", "Lambda", "IO"); try { Files.write(Paths.get(filePath), content); } catch (IOException e) { e.printStackTrace(); } } }
2.4 常见的 IO 流和 Lambda 表达式使用场景
-
字符流读取文本文件:使用
Files.lines()
结合Lambda表达式,轻松地读取文本文件的每一行内容。 -
字节流复制二进制文件:通过
Files.copy()
方法,将一个二进制文件复制到另一个位置。结合Lambda表达式,可以在复制的同时进行一些处理。 -
基于Lambda的过滤和转换:在读取文件时,结合Lambda表达式可以轻松实现过滤、转换等操作,如过滤掉某些行或对每行进行处理。
-
使用Lambda表达式处理异常:在IO操作中,异常处理是必不可少的。Lambda表达式可以在代码块中直接处理异常,使代码更加紧凑。
3. 流式操作
3.1 流操作的概念
Stream 中文成为“流”,通过将集合转换为这么一种叫做“流”的元素序列,通过声明性方式,能够对集合中的每个元素进行一系列并行或串行的流水线操作。
3.2 流操作的分类
3.3 常用流操作的API
- 中间操作:
filter()
:对元素进行过滤map()
:元素映射sorted()
:对元素排序distinct()
:去除重复的元素
- 最终操作:
forEach()
:遍历每个元素findFirst()
:找到第一个符合要求的元素reduce()
:把 Stream 元素组合起来。例如,字符串拼接,数值的sum、min、max、average 都是特殊的 reducecollect()
:返回一个新的数据结构,基于 Collectors 有丰富的处理方法min()
:找到最小值max()
:找到最大值
- 中间操作:中间操作包括去重、过滤、映射等操作,值得说明的是,如果没有为流定义终端操作,为了避免无所谓的计算, 中间操作也不会执行
- 终端操作:终端产生最终的执行结果,并中断式编程的衔接链,因此结束操作是最后一个操作
3.4 案例
public class Client {
public static void main(String[] args) {
List<DemoVo> list = initList();
testFilter(list); // [{"key":"a","value":"str-a"}]
listToMap(list); // {"a":{"key":"a","value":"str-a"},"b":{"key":"b","value":"str-b"},"c":{"key":"c","value":"str-c"}}
testSorted(list); // [{"key":"a","value":"str-a"},{"key":"b","value":"str-b"},{"key":"c","value":"str-c"}]
// 添加一条重复数据
DemoVo demo4 = new DemoVo("c", "str-c");
list.add(demo4);
testDistinct(list); // [{"key":"a","value":"str-a"},{"key":"b","value":"str-b"},{"key":"c","value":"str-c"},{"key":"c","value":"str-c"}]
convertToList(list); // ["str-a","str-b","str-c","str-c"]
}
private static List<DemoVo> initList() {
List<DemoVo> list = new ArrayList<>();
DemoVo demoVo1 = new DemoVo("a", "str-a");
DemoVo demoVo2 = new DemoVo("b", "str-b");
DemoVo demoVo3 = new DemoVo("c", "str-c");
list.add(demoVo1);
list.add(demoVo2);
list.add(demoVo3);
return list;
}
// filter过滤
private static void testFilter(List<DemoVo> list) {
List<DemoVo> listNew = list.stream().filter(item -> item.getKey().equals("a")).collect(Collectors.toList());
System.out.println(JSON.toJSONString(listNew));
}
// Map映射
private static void listToMap(List<DemoVo> list) {
Map<String, DemoVo> demoMap1 = list.stream().collect(Collectors.toMap(DemoVo::getKey, Function.identity()));
// 当出现同名key时,用后者替换前者,toMap跟进源码可以看到使用了binaryOperator类作为参数,BinaryOperator接收两个同样类型的实参,返回和参数同样类型的结果类型
Map<String, DemoVo> demoMap2 = list.stream().collect(Collectors.toMap(DemoVo::getKey, Function.identity(), (key1, key2) -> key2));
System.out.println(JSON.toJSONString(demoMap1));
}
// sorted排序
private static void testSorted(List<DemoVo> list) {
// 自然排序,先对key进行排序,再对value进行排序
List<DemoVo> collect = list.stream().sorted(Comparator.comparing(DemoVo::getKey).thenComparing(DemoVo::getValue)).collect(Collectors.toList());
System.out.println(JSON.toJSONString(collect));
// 先对value排序,再对key排序
collect = list.stream().sorted(Comparator.comparing(DemoVo::getValue).thenComparing(DemoVo::getKey)).collect(Collectors.toList());
System.out.println(JSON.toJSONString(collect));
}
// distinct 去重
private static void testDistinct(List<DemoVo> list) {
List<DemoVo> collect = list.stream().distinct().collect(Collectors.toList());
System.out.println(JSON.toJSONString(collect));
}
// List转List 只取value
private static void convertToList(List<DemoVo> list) {
List<String> collect = list.stream().map(DemoVo::getValue).collect(Collectors.toList());
System.out.println(JSON.toJSONString(collect));
}
}
class DemoVo {
String key;
String value;
public DemoVo(String key, String value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return "DemoVo{" +
"key='" + key + '\'' +
", value='" + value + '\'' +
'}';
}
}
4. 结语
通过本文,我们深入了解了Java中IO流操作的基础知识,并且展示了如何巧妙地将Lambda表达式应用于各种IO操作中,最后通过流式操作的讲解也让我们学会了如何在业务中引用流式操作。这种组合不仅使代码更加清晰、简洁,还能提升代码的可读性和可维护性。在今后的编程实践中,将这些技巧运用到自己的项目中,定能事半功倍。