JDK8新特性:Stream流入门、Stream流的创建
什么是Stream?
- 也叫Stream流,是Jdk8开始新增的一套API (java.util.stream.*),可以用于操作集合或者数组的数据。
- 优势: Stream流大量的结合了Lambda的语法风格来编程,提供了一种更加强大,更加简单的方式操作集合或者数组中的数据,代码更简洁,可读性更好。
案例:体验Stream流
需求:
- 把集合中所有以“张”开头,且是3个字的元素存储到一个新的集合。
package d8_stream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
目标:初步体验Stream流的方便与快捷
*/
public class StreamTest1 {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
Collections.addAll(names, "张三丰","张无忌","周芷若","赵敏","张强");
System.out.println(names); //[张三丰, 张无忌, 周芷若, 赵敏, 张强]
// names = [张三丰, 张无忌, 周芷若, 赵敏, 张强]
// name
// 找出姓张,且是3个字的名字,存入到一个新集合中去。
//方式一:
//准备一个新的ArrayList集合,存姓张的数据
List<String> list = new ArrayList<>();
for (String name : names) {
//startsWith 以什么开头
//length 且三个字
if(name.startsWith("张") && name.length() == 3){
list.add(name);
}
}
System.out.println(list); //[张三丰, 张无忌]
//方式二:
// 开始使用Stream流来解决这个需求。
//filter startsWith 过滤出姓张的
//filter length 长度字数为 3
//collect(Collectors.toList()) 收集到 list2 里
List<String> list2 = names.stream().filter(s -> s.startsWith("张"))
.filter(a -> a.length()==3).collect(Collectors.toList());
System.out.println(list2); //[张三丰, 张无忌]
}
}
Stream流的使用步骤
1、Stream是什么?有什么作用? 结合了什么技术?
- 简化集合、数组操作的API。结合了Lambda表达式。
2、说说Stream流处理数据的步骤是什么?
- 先得到集合或者数组的Stream流。
- 然后调用Stream流的方法对数据进行处理。
- 获取处理的结果。
Stream流的常用方法
1、获取Stream流?
- 获取 集合 的Stream流
- 获取 数组 的Stream流
package d8_stream;
import java.util.*;
import java.util.stream.Stream;
/**
* 目标:掌握Stream流的创建。
*/
public class StreamTest2 {
public static void main(String[] args) {
// 1、如何获取List集合的Stream流?
List<String> names = new ArrayList<> ();
Collections.addAll(names, "张三丰","张无忌","周芷若","赵敏","张强");
Stream<String> stream = names.stream();
// 2、如何获取Set集合的Stream流?
Set<String> set = new HashSet<>();
Collections.addAll(set, "刘德华","张曼玉","蜘蛛精","马德","德玛西亚");
Stream<String> stream1 = set.stream();
//forEach 对数据中的结果进行遍历
stream1.filter(s -> s.contains("德")).forEach(s -> System.out.println(s));
//马德
//德玛西亚
//刘德华
// 3、如何获取Map集合的Stream流?
//Map 不可以直接调用Stream方法获得Stream流
Map<String, Double> map = new HashMap<> ();
map.put("古力娜扎", 172.3);
map.put("迪丽热巴", 168.3);
map.put("马尔扎哈", 166.3);
map.put("卡尔扎巴", 168.3);
Set<String> keys = map.keySet();
//得到Set方法Stream流
Stream<String> ks = keys.stream();
Collection<Double> values = map.values();
Stream<Double> vs = values.stream();
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()));
//迪丽热巴-->168.3
//卡尔扎巴-->168.3
// 4、如何获取数组的Stream流?
String[] names2 = {"张翠山", "东方不败", "唐大山", "独孤求败"};
Stream<String> s1 = Arrays.stream(names2);
Stream<String> s2 = Stream.of(names2);
}
}
JDK8新特性:Stream的中间方法、终结方法
2、Stream流常见的中间方法
- 中间方法指的是调用完成后会返回新的Stream流,可以继续使用(支持链式编程)。
package d8_stream;
import java.util.Objects;
public class Student {
private String name;
private int age;
private double height;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Double.compare(student.height, height) == 0 && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, height);
}
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
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 double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
package d8_stream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
/**
* 目标:掌握Stream流提供的常见中间方法。
*/
public class StreamTest3 {
public static void main(String[] args) {
List<Double> scores = new ArrayList<>();
Collections.addAll(scores, 88.5, 100.0, 60.0, 99.0, 9.5, 99.6, 25.0);
// 需求1:找出成绩大于等于60分的数据,并升序后,再输出。
//filter用来过滤数据,进行筛选 过滤60 60分以上数据 sorted()对剩下的数据默认升序排序 forEach对剩下的数据进行遍历
scores.stream().filter(s -> s >= 60).sorted().forEach(s -> System.out.println(s));
List<Student> students = new ArrayList<>();
Student s1 = new Student("蜘蛛精", 26, 172.5);
Student s2 = new Student("蜘蛛精", 26, 172.5);
Student s3 = new Student("紫霞", 23, 167.6);
Student s4 = new Student("白晶晶", 25, 169.0);
Student s5 = new Student("牛魔王", 35, 183.3);
Student s6 = new Student("牛夫人", 34, 168.5);
Collections.addAll(students, s1, s2, s3, s4, s5, s6);
// 需求2:找出年龄大于等于23,且年龄小于等于30岁的学生,并按照年龄降序输出.
//filter过滤 s代表学生对象 学生年龄23到30
//(o1, o2) -> o2.getAge() - o1.getAge() 按照年龄降序排序
// forEach 遍历数据
students.stream().filter(s -> s.getAge() >= 23 && s.getAge() <= 30)
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
.forEach(s -> System.out.println(s));
// 需求3:取出身高最高的前3名学生,并输出。
//身高降序排序 选取前三个即可
//身高为double不能直接做差 用Double.compare
//limit(3)取出前三个
//System.out::println 简化代码
students.stream().sorted((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()))
.limit(3).forEach(System.out::println);
System.out.println("----------------------------------------------------------------");
// 需求4:取出身高倒数的2名学生,并输出。 s1 s2 s3 s4 s5 s6
//身高降序 最后的两个即倒数2名
//skip 跳过前几个 students.size() - 2 只留下剩下的两个
students.stream().sorted((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()))
.skip(students.size() - 2).forEach(System.out::println);
// 需求5:找出身高超过168的学生叫什么名字,要求去除重复的名字,再输出。
//先找出大于168的学生
// map 映射
//Student::getName 把学生对象变成名字 都放进流
//distinct()用来去重复的
students.stream().filter(s -> s.getHeight() > 168).map(Student::getName)
.distinct().forEach(System.out::println);
// distinct去重复,自定义类型的对象(希望内容一样就认为重复,重写hashCode,equals)
//重写equals 依赖hashCode
students.stream().filter(s -> s.getHeight() > 168)
.distinct().forEach(System.out::println);
//concat
Stream<String> st1 = Stream.of("张三", "李四");
Stream<String> st2 = Stream.of("张三2", "李四2", "王五");
//concat合并 将st1合并st2到一起
Stream<String> allSt = Stream.concat(st1, st2);
allSt.forEach(System.out::println);
}
}
3、Stream流常见的终结方法
- 终结方法指的是调用完成后,不会返回新Stream了,没法继续使用流了。
- 收集Stream流:就是把Stream流操作后的结果转回到集合或者数组中去返回。
- Stream流:方便操作集合/数组的手段; 集合/数组:才是开发中的目的。
package d8_stream;
import java.util.*;
import java.util.stream.Collectors;
/**
* 目标:Stream流的终结方法
*/
public class StreamTest4 {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
Student s1 = new Student("蜘蛛精", 26, 172.5);
Student s2 = new Student("蜘蛛精", 26, 172.5);
Student s3 = new Student("紫霞", 23, 167.6);
Student s4 = new Student("白晶晶", 25, 169.0);
Student s5 = new Student("牛魔王", 35, 183.3);
Student s6 = new Student("牛夫人", 34, 168.5);
Collections.addAll(students, s1, s2, s3, s4, s5, s6);
// 需求1:请计算出身高超过168的学生有几人
//s.getHeight() > 168 身高超过168的学生
// count() 统计数量
long size = students.stream().filter(s -> s.getHeight() > 168).count();
System.out.println(size);
// 需求2:请找出身高最高的学生对象,并输出。
// 声明max比较规则 (o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())
//get() 获取身高最高的学生对象
Student s = students.stream().max((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())).get();
System.out.println(s);
// 需求3:请找出身高最矮的学生对象,并输出。
Student ss = students.stream().min((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())).get();
System.out.println(ss);
// 需求4:请找出身高超过170的学生对象,并放到一个新集合中去返回。
// collect 终结对象
// 流只能收集一次。
List<Student> students1 = students.stream().filter(a -> a.getHeight() > 170).collect(Collectors.toList());
System.out.println(students1);
//身高超170 放入set集合中
// 流只能收集一次。
Set<Student> students2 = students.stream().filter(a -> a.getHeight() > 170).collect(Collectors.toSet());
System.out.println(students2);
// 需求5:请找出身高超过170的学生对象,并把学生对象的名字和身高,存入到一个Map集合返回。
Map<String, Double> map =
students.stream().filter(a -> a.getHeight() > 170)
.distinct().collect(Collectors.toMap(a -> a.getName(), a -> a.getHeight()));
System.out.println(map);
// Object[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray();
Student[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray(len -> new Student[len]);
System.out.println(Arrays.toString(arr));
}
}
IO流(一):File、IO流概述、File文件对象的创建
存储数据的方案
有些数据想长久保存起来,咋整?
- 文件是非常重要的存储方式,在计算机硬盘中。
- 即便断电,或者程序终止了,存储在硬盘文件中的数据也不会丢失。
File类
- File是java.io.包下的类, File类的对象,用于代表当前操作系统的文件(可以是文件、或文件夹)。
- 注意:File类只能对文件本身进行操作,不能读写文件里面存储的数据。
IO流
- 用于读写数据的(可以读写文件,或网络中的数据…)
- File:代表文本
- IO流:读写数据
创建File类的对象
package com.liu.d1_file;
import java.io.File;
/**
* 目标:掌握File创建对象,代表具体文件的方案。
*/
public class FileTest1 {
public static void main(String[] args) {
// 1、创建一个File对象,指代某个具体的文件。
// 路径分隔符 \\ 和 / 都可以
// File f1 = new File("D:/resource/ab.txt");
// File f1 = new File("D:\\resource\\ab.txt");//有参构造器,声明文件路径 代表指代某个具体的文件
//File.separator 是什么系统用什么分隔符
File f1 = new File("D:" + File.separator +"resource" + File.separator + "ab.txt");
//打印
System.out.println(f1.length()); // 文件大小 //f1是一个对象 表示返回文件对象
//File 可以指向文件夹
File f2 = new File("D:/resource");
System.out.println(f2.length()); //取文件夹本身大小
// 注意:File对象可以指代一个不存在的文件路径
File f3 = new File("D:/resource/aaaa.txt"); //随便定位一个文件
System.out.println(f3.length()); //因为文件不存在,则返回大小为0
//exists() 判断文件路径是否存在
System.out.println(f3.exists());// false
// 我现在要定位的文件是在idea的模块中,应该怎么定位呢?
// 绝对路径:带盘符的
// File f4 = new File("D:\\code\\javasepromax\\file-io-app\\src\\itheima.txt"); //从idea拿到文件的路径,copy过来(绝对路径)
// 相对路径(重点)(建议使用):不带盘符,默认是直接去工程下寻找文件的。
File f4 = new File("file-io-app\\src\\itheima.txt"); //相对路径
System.out.println(f4.length());
}
}
注意
- File对象既可以代表文件、也可以代表文件夹。
- File封装的对象仅仅是一个路径名,这个路径可以是存在的,也允许是不存在的。
绝对路径、相对路径
- 绝对路径:从盘符开始
- 相对路径:不带盘符,默认直接到当前工程下的目录寻找文件。
1、File类构建对象的方式是什么样的?File的对象可以代表哪些东西?
- File file = new File(“文件/文件夹/绝对路径/相对路径”);
2、绝对路径和相对路径是什么意思?
- 绝对路径是带盘符的。
- 相对路径是不带盘符的,默认到当前工程下寻找文件。
IO流(一):File类的常用方法、案例
File提供的判断文件类型、获取文件信息功能
package com.liu.d1_file;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
/**
目标:掌握File提供的判断文件类型、获取文件信息功能
*/
public class FileTest2 {
public static void main(String[] args) throws UnsupportedEncodingException {
// 1.创建文件对象,指代某个文件
File f1 = new File("D:/resource/ab.txt");
//File f1 = new File("D:/resource/");
// 2、public boolean exists():判断当前文件对象,对应的文件路径是否存在,存在返回true.
//判断ab.txt文件是否存在
System.out.println(f1.exists());//存在返回true 不存在返回false
// 3、public boolean isFile() : 判断当前文件对象指代的是否是文件,是文件返回true,反之。
//判断ab.txt是否是文件
System.out.println(f1.isFile());
// 4、public boolean isDirectory() : 判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之。
// ab.txt 是否为文件夹
System.out.println(f1.isDirectory()); //不是文件夹 false
// 5.public String getName():获取文件的名称(包含后缀)
System.out.println(f1.getName()); //ab.txt //若问文件夹 返回结果还会返回文件夹里面的路径
// 6.public long length():获取文件的大小,返回字节个数
System.out.println(f1.length());
// 7.public long lastModified():获取文件的最后修改时间。
long time = f1.lastModified();
//时间毫秒值对应的具体时间
//yyyy/MM/dd HH:mm:ss 指定格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
//格式化
System.out.println(sdf.format(time));
// 8.public String getPath():获取创建文件对象时,使用的路径
File f2 = new File("D:\\resource\\ab.txt"); //创建一个文件对象(绝对路径)
File f3 = new File("file-io-app\\src\\itheima.txt"); //创建一个文件路径 (相对路径)
System.out.println(f2.getPath()); //创建的是什么路径,返回的就是什么路径
System.out.println(f3.getPath()); //创建的是什么路径,返回的就是什么路径
// 9.public String getAbsolutePath():获取绝对路径
System.out.println(f2.getAbsolutePath()); //本来就是绝对路径,返回就是绝对路径
System.out.println(f3.getAbsolutePath()); //相对路径转为相对路径返回
}
}
File类创建文件的功能
File类删除文件的功能
- 注意:delete方法默认只能删除文件和空文件夹,删除后的文件不会进入回收站。
package com.liu.d1_file;
import java.io.File;
/**
* 目标:掌握File创建和删除文件相关的方法。
*/
public class FileTest3 {
public static void main(String[] args) throws Exception {
// 1、public boolean createNewFile():创建一个新文件(文件内容为空),创建成功返回true,反之。
File f1 = new File("D:/resource/itheima2.txt");
System.out.println(f1.createNewFile()); //创建成功 返回true
// 2、public boolean mkdir():用于创建文件夹,注意:只能创建一级文件夹
//错误写法 D:/resource/bbb/ccc/ddd/eee/fff/ggg
//只能创建一级文件夹 D:/resource/aaa
File f2 = new File("D:/resource/aaa");
System.out.println(f2.mkdir());
// 3、public boolean mkdirs():用于创建文件夹,注意:可以创建多级文件夹
File f3 = new File("D:/resource/bbb/ccc/ddd/eee/fff/ggg");
System.out.println(f3.mkdirs());
// 3、public boolean delete():删除文件,或者空文件,注意:不能删除非空文件夹。
System.out.println(f1.delete());
System.out.println(f2.delete());
File f4 = new File("D:/resource");
System.out.println(f4.delete());
}
}
1、创建多级目录使用哪个方法?
- public boolean mkdirs()
2、删除文件需要注意什么?
- 可以删除文件、空文件夹。
- 默认不能删除非空文件夹。
File类提供的遍历文件夹的功能
package com.liu.d1_file;
import java.io.File;
import java.util.Arrays;
/**
* 目标:掌握File提供的遍历文件夹的方法。
*/
public class FileTest4 {
public static void main(String[] args) {
// 1、public String[] list():获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。
File f1 = new File("D:\\course\\待研发内容");
String[] names = f1.list();//定义字符串数组,接收文件名称
//遍历
for (String name : names) {
System.out.println(name);
}
// 2、public File[] listFiles():(重点)获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
File[] files = f1.listFiles();
//遍历文件对象
for (File file : files) {
System.out.println(file.getAbsolutePath()); //取绝对路径
}
// 当主调是空文件夹时,返回一个长度为0的数组
File f = new File("D:/resource/aaa");
File[] files1 = f.listFiles();
System.out.println(Arrays.toString(files1));
}
}
使用listFiles方法时的注意事项:
- 当主调是文件,或者路径不存在时,返回null
- 当主调是空文件夹时,返回一个长度为0的数组
- 当主调是一个有内容的文件夹时,将里面所有一级文件和文件夹的路径放在File数组中返回
- 当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏文件
- 当主调是一个文件夹,但是没有权限访问该文件夹时,返回null
如何遍历文件夹下的文件对象,使用哪个API,有什么特点?
- File[] listFiles()(常用)。
- 只能遍历当前文件夹对象下的一级文件对象。
案例:改变某个文件夹下视频的序号,要求从19开始
package com.liu.d1_file;
import java.io.File;
public class Test {
public static void main(String[] args) {
//目标:改变某个文件夹下视频的序号,要求从19开始
File dir = new File("E:\\w\\视频\\day02视频");
//1、拿到下面全部的视频,一级文件对象
File[] videos = dir.listFiles();
//2、一个一个的找
for (File video:videos){
//3、拿到它的名字
String name = video.getName(); //name = "10、多态、继承..."
String index = name.substring(0, name.indexOf("、"));
String lastName = name.substring(name.indexOf("、"));
String newName = (Integer.valueOf(index) + 18) + lastName;
//4、正式改名
video.renameTo(new File(dir,newName));
}
}
}
IO流(一):前置知识:方法递归、递归的算法和执行流程
什么是方法递归?
- 递归是一种算法,在程序设计语言中广泛应用。
- 从形式上说:方法调用自身的形式称为方法递归( recursion)。
递归的形式
- 直接递归:方法自己调用自己。
- 间接递归:方法调用其他方法,其他方法又回调方法自己。
package com.liu.d2_recursion;
/**
* 目标:认识一下递归的形式。
*/
public class RecursionTest1 {
public static void main(String[] args) {
//主程序调用方法,报错
test1(); //出错,栈内存溢出
}
// 直接方法递归
//定义 test1方法
public static void test1(){
System.out.println("----test1---");
//方法自己调用自己
test1(); // 直接方法递归
}
// 间接方法递归
public static void test2(){
System.out.println("---test2---");
test3();
}
public static void test3(){
test2(); // 间接递归
}
}
使用方法递归时需要注意的问题:
- 递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出错误。
什么是递归死循环?
- 方法无限调用自己,无法终止,最终引起栈内存溢出。
案例:计算n的阶乘
需求:
计算n的阶乘,5的阶乘=1*2*3*4*5; 6的阶乘=1*2*3*4*5*6;
分析
1、假如我们认为存在一个公式是 f(n) = 1*2*3*4*5*6*7*…(n-1)*n;
2、那么公式等价形式就是: f(n) = f(n-1) *n
3、如果求的是 1-5 的阶乘 的结果,我们手工应该如何应用上述公式计算。
f(5) = f(4) * 5
f(4) = f(3) * 4
f(3) = f(2) * 3
f(2) = f(1) * 2
f(1) = 1
package com.liu.d2_recursion;
public class RecursionTest2 {
//目标:掌握递归的应用,执行流程和算法思想
public static void main(String[] args) {
System.out.println("5的阶乘是:" + f(5));
}
public static int f(int n){
//终结点
if (n == 1){
return 1;
}else {
return f(n-1) * n ;
}
}
}
案例:递归求阶乘的执行流程
递归算法三要素:
- 递归的公式: f(n) = f(n-1) * n;
- 递归的终结点:f(1)
- 递归的方向必须走向终结点:f(5) = f(4) * 5;f(4) = f(3) * 4;f(3) = f(2) * 3;f(2) = f(1) * 2 f(1) = 1
IO流(一):File文件搜索-啤酒问题-删除非空文件夹
案例:文件搜索
需求:
从D:盘中,搜索“QQ.exe” 这个文件,找到后直接输出其位置。
分析:
1、先找出D:盘下的所有一级文件对象
2、遍历全部一级文件对象,判断是否是文件
3、如果是文件,判断是否是自己想要的
4、如果是文件夹,需要继续进入到该文件夹,重复上述过程
package com.liu.d2_recursion;
import java.io.File;
/**
* 目标:掌握文件搜索的实现。
*/
public class RecursionTest3 {
public static void main(String[] args) throws Exception {
//传数据 路径和文件名
searchFile(new File("D:/") , "QQ.exe");
}
/**
* 去目录下搜索某个文件
* @param dir 目录
* @param fileName 要搜索的文件名称
*/
//设置方法 需要接收参数:file类型参数,取名dir;String类型,要搜索文件的名称 fileName
// throws Exception 启动文件
public static void searchFile(File dir, String fileName) throws Exception {
// 1、把非法的情况都拦截住
if(dir == null || !dir.exists() || dir.isFile()){
return; // 代表无法搜索
}
// 2、dir不是null,存在,一定是目录对象。
// 获取当前目录下的全部一级文件对象。
File[] files = dir.listFiles(); // 返回数组,存放一级文件对象,dir.listFiles()
// 3、判断当前目录下是否存在一级文件对象,以及是否可以拿到一级文件对象。
if(files != null && files.length > 0){
// 4、遍历全部一级文件对象。
for (File f : files) {
// 5、判断文件是否是文件,还是文件夹
if(f.isFile()){
// 是文件,判断这个文件名是否是我们要找的
if(f.getName().contains(fileName)){ //判断是否包含要查的文件名
System.out.println("找到了:" + f.getAbsolutePath());
//找到文件之后,启动文件
Runtime runtime = Runtime.getRuntime();
runtime.exec(f.getAbsolutePath()); //绝对路径
}
}else {
// 遍历的是文件夹,调用方法,继续重复这个过程(递归)
searchFile(f, fileName);
}
}
}
}
}
案例:删除非空文件夹
package com.liu.d2_recursion;
import java.io.File;
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
//目标:删除非空文件夹,独立功能独立成方法
File dir = new File("E:\\resource\\秘密");
deleteDir(dir);
}
public static void deleteDir(File dir) {
if (dir == null || !dir.exists()) {
return;
}
if (dir.isFile()) {
dir.delete();
return;
}
//1、dir存在且是文件夹,拿里面的一级文件对象
File[] files = dir.listFiles();
if (files == null) {
return;
}
if (files.length == 0) {
dir.delete();
return;
}
//2、这是一个有内容的文件夹,干掉里面的内容,再干掉自己
for (File file : files) {
if (file.isFile()) {
file.delete();
} else {
deleteDir(file);
}
}
dir.delete();
}
}
案例:啤酒
package com.liu.d2_recursion;
public class Test1 {
public static int totalNumber; //总酒数
public static int lastBattleNumber;
public static int lastCoverNumber; //总酒数
public static void main(String[] args) {
//啤酒问题:啤酒2元一瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶
// 请问10元可以喝多少瓶
buy(10);
System.out.println("总数:" + totalNumber);
System.out.println("剩余盖子数" + lastCoverNumber);
System.out.println("剩余瓶子数:" + lastBattleNumber);
}
public static void buy(int money){
//1、先买了再说
int buyNumber = money / 2;
totalNumber += buyNumber;
//2、把盖子和瓶子换算成钱继续买
//计算本轮总的盖子和瓶子数
int allBottleNumber = buyNumber + lastBattleNumber;
int allCoverNumber = buyNumber + lastCoverNumber;
int allMoney = 0 ;
if (allBottleNumber >= 2){
allMoney += (allBottleNumber / 2) * 2;
}
lastBattleNumber = allBottleNumber % 2;
if (allCoverNumber >= 4){
allMoney += (allCoverNumber / 4) * 2;
}
lastCoverNumber = allCoverNumber % 4;
if (allMoney >= 2){
buy(allMoney);
}
}
}
文件搜索用到了什么技术?
- 递归,listFile只是搜索到了一级文件对象。
IO流(一):前置知识-字符集、UTF-8、GBK、ASCII、乱码问题、编码和解码等
标准ASCII字符集
- ASCII(American Standard Code for Information Interchange): 美国信息交换标准代码,包括了英文、符号等。
- 标准ASCII使用1个字节存储一个字符,首尾是0,总共可表示128个字符,对美国佬来说完全够用。
GBK(汉字内码扩展规范,国标)
- 汉字编码字符集,包含了2万多个汉字等字符,GBK中一个中文字符编码成两个字节的形式存储。
- 注意:GBK兼容了ASCII字符集。
Unicode字符集(统一码,也叫万国码)
- Unicode是国际组织制定的,可以容纳世界上所有文字、符号的字符集。
UTF-8
- 是Unicode字符集的一种编码方案,采取可变长编码方案,共分四个长度区:1个字节,2个字节,3个字节,4个字节
- 英文字符、数字等只占1个字节(兼容标准ASCII编码),汉字字符占用3个字节。
- 注意:技术人员在开发时都应该使用UTF-8编码!
本节要点
- ASCII字符集:只有英文、数字、符号等,占1个字节。
- GBK字符集:汉字占2个字节,英文、数字占1个字节。
- UTF-8字符集:汉字占3个字节,英文、数字占1个字节。
注意:
- 注意1:字符编码时使用的字符集,和解码时使用的字符集必须一致,否则会出现乱码
- 注意2:英文,数字一般不会乱码,因为很多字符集都兼容了ASCII编码。
Java代码完成对字符的编码
编码:把字符按照指定字符集编码成字节
Java代码完成对字符的解码
解码:把字节按照指定字符集解码成字符
package com.liu.d3_charset;
import java.util.Arrays;
/**
* 目标:掌握如何使用Java代码完成对字符的编码和解码。
*/
public class Test {
public static void main(String[] args) throws Exception {
// 1、编码
String data = "a我b";
byte[] bytes = data.getBytes(); // 默认是按照平台字符集(UTF-8)进行编码的。
//总共5个字节 :--97代表a-- --[97, -26, -120, -111, 98] 代表我-- --98代表b--
System.out.println(Arrays.toString(bytes)); //[97, -26, -120, -111, 98]
// 按照指定字符集进行编码。
byte[] bytes1 = data.getBytes("GBK"); //使用GBK对字符串进行编码
System.out.println(Arrays.toString(bytes1)); //[97, -50, -46, 98]
// 2、解码
String s1 = new String(bytes); // 按照平台默认编码(UTF-8)解码
System.out.println(s1); //a我b
String s2 = new String(bytes1, "GBK");
System.out.println(s2); //a我b
}
}
如何使用Java程序对字符进行编码?
- String类下的方法:
- byte[] getBytes():默认编码
- byte[] getBytes(String charsetName):指定编码
如何使用Java程序对字节进行解码?
- String类的构造器:
- String(byte[] bytes):使用默认编码解码
- String(byte[] bytes, String charsetName)):指定编码解码
IO(一):IO流概述、字节流-FileInputStream每次读取一个字节
IO流概述
- 输入输出流 读写数据的
IO流的应用场景
怎么学IO流?
- 1、先搞清楚IO流的分类、体系。
- 2、再挨个学习每个IO流的作用、用法。
IO流的分类
- 按流的方向分为:
- 按流中数据的最小单位,分为:
IO流总体来看就有四大流
总结流的四大类:
- 字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流
- 字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流。
- 字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流。
- 字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流。
IO流的体系
1、IO流的作用?
- 读写文件数据的
2、IO流是怎么划分的,大体分为几类,各自的作用?
- 字节输入流 InputStream(读字节数据的)
- 字节输出流 OutoutStream(写字节数据出去的)
- 字符输入流 Reader(读字符数据的)
- 字符输出流 Writer(写字符数据出去的)
IO流的体系
FileInputStream(文件字节输入流)
- 作用:以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去。
注意事项:
- 使用FileInputStream每次读取一个字节,读取性能较差,并且读取汉字输出会乱码。
FileInputStream(文件字节输入流)(每次读取一个字节)
- 作用:以内存为基准,把文件中的数据以字节的形式读入到内存中去。
package com.liu.d4_byte_stream;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 目标:掌握文件字节输入流,每次读取一个字节。
*/
public class FileInputStreamTest1 {
public static void main(String[] args) throws Exception { //throws Exception 抛出异常
// 1、创建文件字节输入流管道,与源文件接通。
// file-io-app\\src\\itheima01.txt 该路径文件 就在次此项目工程里的src下新建的文件
// InputStream is = new FileInputStream(new File("file-io-app\\src\\itheima01.txt"));
// 简化写法:推荐使用
InputStream is = new FileInputStream(("file-io-app\\src\\itheima01.txt"));
// 2、开始读取文件的字节数据。
// public int read()方法:每次读取一个字节返回,如果没有数据了,返回-1.
// int b1 = is.read();// 每次从管道读取文件一个字节
// System.out.println((char)b1); //转字符型
//
// int b2 = is.read(); //b2代表第二个字节
// System.out.println((char) b2);
//
// int b3 = is.read();
// System.out.println(b3);
// 3、使用循环改造上述代码
int b; // 用于记住读取的字节。
while ((b = is.read()) != -1){
System.out.print((char) b);
}
//方案存在的问题:
// 读取数据的性能很差!
// 读取汉字输出会乱码!!无法避免的!!
// 流使用完毕之后,必须关闭!释放系统资源!
is.close();
}
}
FileInputStream(文件字节输入流)(每次读取多个字节)
- 作用:以内存为基准,把文件中的数据以字节的形式读入到内存中去。
package com.liu.d4_byte_stream;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 目标:掌握使用FileInputStream每次读取多个字节。
*/
public class FileInputStreamTest2 {
public static void main(String[] args) throws Exception { //throws Exception 抛出异常
// 1、创建一个字节输入流对象代表字节输入流管道与源文件接通。
InputStream is = new FileInputStream("file-io-app\\src\\itheima02.txt");
// public int read(byte b[]) throws IOException 方法
// 每次读取多个字节到字节数组中去,返回读取的字节数量,读取完毕会返回-1.
// 2、开始读取文件中的字节数据:每次读取多个字节。
//定义字节数组,装多个字节
// byte[] buffer = new byte[3]; //每次读取3个字节
// int len = is.read(buffer); //读取字节
// String rs = new String(buffer); //转字节数组
// System.out.println(rs); //abc
// System.out.println("当次读取的字节数量:" + len); //3
//
// // buffer = [abc]
// // buffer = [66c]
// int len2 = is.read(buffer); //第二次读出
// // 注意:读取多少,倒出多少。
// String rs2 = new String(buffer, 0, len2); //0代表从第一个字节开始 也就是从6开始
// System.out.println(rs2); // 66c
// System.out.println("当次读取的字节数量:" + len2); //2
//
// int len3 = is.read(buffer); //第三次读出
// System.out.println(len3); // -1 //无数据,输出-1
//优化代码
// 3、使用循环 进行 改造
byte[] buffer = new byte[3];
int len; // 记住每次读取了多少个字节。 abc 66
while ((len = is.read(buffer)) != -1){ //不等于-1 说明读到字节
// 注意:读取多少,倒出多少。
String rs = new String(buffer, 0 , len);
System.out.print(rs); //打印不用换行
}
//简化后的代码:
// 性能得到了明显的提升!!
// 这种方案也不能避免读取汉字输出乱码的问题!!
is.close(); // 关闭流
}
}
1、使用字节流读取中文,如何保证输出不乱码,怎么解决?
- 定义一个与文件一样大的字节数组,一次性读取完文件的全部字节。
2、每次读取一个字节数组有什么好处? 存在什么问题?
- 读取的性能得到了提升
- 读取中文字符输出无法避免乱码问题。
文件字节输入流:一次读取完全部字节
- 方式一:自己定义一个字节数组与被读取的文件大小一样大,然后使用该字节数组,一次读完文件的全部字节。
- 方式二:Java官方为InputStream提供了如下方法,可以直接把文件的全部字节读取到一个字节数组中返回。
package com.liu.d4_byte_stream;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 目标:使用文件字节输入流一次读取完文件的全部字节。
*/
public class FileInputStreamTest3 {
public static void main(String[] args) throws Exception { //throws Exception 抛出异常
// 1、一次性读取完文件的全部字节到一个字节数组中去。
// 创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("file-io-app\\src\\itheima03.txt");
//方法一
// 2、准备一个字节数组,大小与文件的大小正好一样大。
//用文件对象 指代文件大小
// File f = new File("file-io-app\\src\\itheima03.txt");
// long size = f.length(); //返回文件大小
//定义一共数组 (int) size 此方案适合读取文件大小较小的文件
// byte[] buffer = new byte[(int) size]; // 从long 强转为int型
//
// int len = is.read(buffer); //读取字节
// System.out.println(new String(buffer)); //转为字符串
//
// System.out.println(size);
// System.out.println(len);
//方法二
byte[] buffer = is.readAllBytes();
System.out.println(new String(buffer));
is.close(); // 关闭流
}
}
直接把文件数据全部读取到一个字节数组可以避免乱码,是否存在问题?
- 如果文件过大,创建的字节数组也会过大,可能引起内存溢出。
IO流(一):字节流-FileOutputStream、字节流完成文件拷贝
IO流的体系
FileOutputStream(文件字节输出流)
- 作用:以内存为基准,把内存中的数据以字节的形式写出到文件中去。
package com.liu.d4_byte_stream;
import java.io.FileOutputStream;
import java.io.OutputStream;
/**
* 目标:掌握文件字节输出流FileOutputStream的使用。
*/
public class FileOutputStreamTest4 {
public static void main(String[] args) throws Exception { //throws Exception 抛出异常
// 1、创建一个字节输出流管道与目标文件接通。
// 覆盖管道:覆盖之前的数据
// OutputStream os =
// new FileOutputStream("file-io-app/src/itheima04out.txt");
// 追加数据的管道
//true 不会覆盖之前的数据 继续往后追加
OutputStream os = new FileOutputStream("file-io-app/src/itheima04out.txt", true); //文件可以自动生成
// 2、开始写字节数据出去了
os.write(97); // 97就是一个字节,代表a
os.write('b'); // 'b'也是一个字节
// os.write('磊'); // [ooo] 默认只能写出去一个字节
// getBytes() 把字符串转换成字节数组
byte[] bytes = "我爱你中国abc".getBytes();
os.write(bytes);
os.write(bytes, 0, 15); // 我爱你中国 写出去
// 换行符 \r\n
//字节写出去 可以实现换行
//getBytes() 转成字节数组
os.write("\r\n".getBytes());
os.close(); // 关闭流
}
}
文件复制
字节流非常适合做一切文件的复制操作
- 任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题!
package com.liu.d4_byte_stream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* 目标:使用字节流完成对文件的复制操作。
*/
public class CopyTest5 {
public static void main(String[] args) throws Exception { //throws Exceptio 抛出异常
//此文件复制 可用于 所有文件复制,只要目标文件是个文本文件 没问题就能复制
// 需求:复制
// 1、创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("file-io-app\\src\\itheima03.txt");
// 2、创建一个字节输出流管道与目标文件接通。
OutputStream os = new FileOutputStream("file-io-app\\src\\itheima03copy.txt");
// 3、创建一个字节数组,负责转移字节数据。
byte[] buffer = new byte[1024]; // 1KB. //字节数组越大 性能越好
// 4、从字节输入流中读取字节数据,写出去到字节输出流中。读多少写出去多少。
int len; // 记住每次读取了多少个字节。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0, len); //存多少个就倒出多少个 buffer决定多少个
}
//关流
//先关后创建的
os.close();
is.close();
System.out.println("复制完成!!");
}
}
IO流(一):释放资源-try-catch-finally、try-catch-resource
try-catch-finally
- finally代码区的特点:无论try中的程序是正常执行了,还是出现了异常,最后都一定会执行finally区,除非JVM终止。
package com.liu.d5_resource;
/**
* 目标:认识try-catch-finally。
*/
public class Test1 {
public static void main(String[] args) {
try {
System.out.println(10 / 2); //5
// return; // 跳出方法的执行
// System.exit(0); // 虚拟机
}catch (Exception e){
e.printStackTrace();
} finally { //加了finally 即使有return 最后还是会执行一次
System.out.println("===finally执行了一次==="); //===finally执行了一次===
}
System.out.println(chu(10, 2));
}
//写方法 除法
public static int chu(int a, int b){
try {
return a / b; //结果return
}catch (Exception e){
e.printStackTrace();
return -1; // 代表的是出现异常 出现异常的时候不会报错
}finally {
// 千万不要在finally中返回数据!
return 111;
}
}
}
- 作用:一般用于在程序执行完成后进行资源的释放操作(专业级做法)。
package com.liu.d5_resource;
import java.io.*;
/**
* 目标:掌握释放资源的方式。
*/
public class Test2 {
public static void main(String[] args) {
InputStream is = null;
OutputStream os = null;
//ctrl + alt + T 选择 try catch
try {
System.out.println(10 / 0);
// 1、创建一个字节输入流管道与源文件接通
is = new FileInputStream("file-io-app\\src\\itheima03.txt");
// 2、创建一个字节输出流管道与目标文件接通。
os = new FileOutputStream("file-io-app\\src\\itheima03copy.txt");
System.out.println(10 / 0);
// 3、创建一个字节数组,负责转移字节数据。
byte[] buffer = new byte[1024]; // 1KB.
// 4、从字节输入流中读取字节数据,写出去到字节输出流中。读多少写出去多少。
int len; // 记住每次读取了多少个字节。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
System.out.println("复制完成!!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 释放资源的操作
try {
//可避免空指针异常
if(os != null) os.close(); //关流
} catch (IOException e) {
e.printStackTrace();
}
try {
//可避免空指针异常
if(is != null) is.close(); //关流
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
JDK 7开始提供了更简单的资源释放方案:try-with-resource
try-with-resource:
- 该资源使用完毕后,会自动调用其close()方法,完成对资源的释放!
- () 中只能放置资源,否则报错
- 什么是资源呢?
- 资源一般指的是最终实现了AutoCloseable接口。
package com.liu.d4_byte_stream;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 目标:使用文件字节输入流一次读取完文件的全部字节。
*/
public class FileInputStreamTest3 {
public static void main(String[] args) throws Exception { //throws Exception 抛出异常
// 1、一次性读取完文件的全部字节到一个字节数组中去。
// 创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("file-io-app\\src\\itheima03.txt");
//方法一
// 2、准备一个字节数组,大小与文件的大小正好一样大。
//用文件对象 指代文件大小
// File f = new File("file-io-app\\src\\itheima03.txt");
// long size = f.length(); //返回文件大小
//定义一共数组 (int) size 此方案适合读取文件大小较小的文件
// byte[] buffer = new byte[(int) size]; // 从long 强转为int型
//
// int len = is.read(buffer); //读取字节
// System.out.println(new String(buffer)); //转为字符串
//
// System.out.println(size);
// System.out.println(len);
//方法二
byte[] buffer = is.readAllBytes();
System.out.println(new String(buffer));
is.close(); // 关闭流
}
}
IO 流(二):字符流——FileReader、FileWriter、字符输出流的注意事项
IO流的体系
FileReader(文件字符输入流)
- 作用:以内存为基准,可以把文件中的数据以字符的形式读入到内存中去。
FileWriter(文件字符输出流)
- 作用:以内存为基准,把内存中的数据以字符的形式写出到文件中去。
package com.liu.d1_char_stream;
import java.io.FileReader;
import java.io.Reader;
/**
* 目标:掌握文件字符输入流。
*/
public class FileReaderTest1 {
public static void main(String[] args) {
try (
// 1、创建一个文件字符输入流管道与源文件接通
Reader fr = new FileReader("io-app2\\src\\itheima01.txt"); //相对路径
){
// 2、读取文本文件的内容了。
//方法一
//每次读取一个字符
// int c; // 记住每次读取的字符编号。
// while ((c = fr.read()) != -1){
// System.out.print((char) c); //强转char 不需要换行
// }
// 每次读取一个字符的形式,性能肯定是比较差的。
//方法二
// 3、每次读取多个字符。
char[] buffer = new char[3]; //定义一个数组 一次读取三个字符
int len; // 记住每次读取了多少个字符。
while ((len = fr.read(buffer)) != -1){
// 读取多少倒出多少
System.out.print(new String(buffer, 0, len));
}
// 每次读取多个字符 性能是比较不错的!
} catch (Exception e) {
e.printStackTrace();
}
}
}
IO流的体系
FileWriter(文件字符输出流)
- 作用:以内存为基准,把内存中的数据以字符的形式写出到文件中去。
package com.liu.d1_char_stream;
import java.io.FileWriter;
import java.io.Writer;
/**
* 目标:掌握文件字符输出流:写字符数据出去
*/
public class FileWriterTest2 {
public static void main(String[] args) {
//释放异常
try (
// 0、创建一个文件字符输出流管道与目标文件接通。
// 覆盖管道 之前的数据会丢失
// Writer fw = new FileWriter("io-app2/src/itheima02out.txt");
// 追加数据的管道 true 之前的数据不会丢失
Writer fw = new FileWriter("io-app2/src/itheima02out.txt", true);
){
// 1、public void write(int c):写一个字符出去
fw.write('a');
fw.write(97);
//fw.write('磊'); // 写一个字符出去
fw.write("\r\n"); // 换行符
// 2、public void write(String c)写一个字符串出去
fw.write("我爱你中国abc");
fw.write("\r\n"); //换行符
// 3、public void write(String c ,int pos ,int len):写字符串的一部分出去
fw.write("我爱你中国abc", 0, 5); //0表示从我开始 ,5代表输出我爱你中国
fw.write("\r\n"); //换行符
// 4、public void write(char[] buffer):写一个字符数组出去
char[] buffer = {'黑', '马', 'a', 'b', 'c'};
fw.write(buffer);
fw.write("\r\n"); //换行符
// 5、public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去
fw.write(buffer, 0, 2); // 黑马
fw.write("\r\n"); //换行符
} catch (Exception e) {
e.printStackTrace();
}
}
}
字符输出流使用时的注意事项
- 字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效
package com.liu.d1_char_stream;
import java.io.FileWriter;
import java.io.Writer;
/**
* 目标:搞清楚字符输出流使用时的注意事项。
*/
public class FileWriterTest3 {
public static void main(String[] args) throws Exception {
Writer fw = new FileWriter("io-app2/src/itheima03out.txt");
// 写字符数据出去
fw.write('a');
fw.write('b');
fw.write('c');
fw.write('d');
fw.write("\r\n");
fw.write("我爱你中国");
fw.write("\r\n");
fw.write("我爱你中国");
// fw.flush(); // 刷新流。 //把缓冲区里的内容同步到文件中去
// fw.write("张三");
// fw.flush();
fw.close(); // 关闭流,关闭流包含刷新操作! 自动把缓冲区的数据同步
// fw.write("张三"); //关闭流后 就不能再写数据
}
}
字节流、字符流的使用场景小结:
- 字节流适合做一切文件数据的拷贝(音视频,文本);字节流不适合读取中文内容输出。
- 字符流适合做文本文件的操作(读,写)。
IO流(二):缓冲流——BufferedReader、BufferedWriter、案例
IO流的体系
字节缓冲流的作用
- 提高字节流读写数据的性能
原理:字节缓冲输入流自带了8KB缓冲池;字节缓冲输出流也自带了8KB缓冲池。
package com.liu.d2_buffered_stream;
import java.io.*;
/**
* 目标:掌握字节缓冲流的作用。
*/
public class BufferedInputStreamTest1 {
public static void main(String[] args) {
try (
InputStream is = new FileInputStream("io-app2/src/itheima01.txt");
// 1、定义一个字节缓冲输入流包装原始的字节输入流
InputStream bis = new BufferedInputStream(is);
OutputStream os = new FileOutputStream("io-app2/src/itheima01_bak.txt");
// 2、定义一个字节缓冲输出流包装原始的字节输出流
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("复制完成!!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
1、缓冲流有几种?
- 字节缓冲输入流: BufferedInputStream
- 字节缓冲输出流:BufferedOutputStream
- 字符缓冲输入流:BufferedReader
- 字符缓冲输出流:BufferedWriter
2、字节缓冲流为什么提高了字节流读写数据的性能?
- 字节缓冲流自带8KB缓冲区
- 可以提高原始字节流、字符流读写数据的性能
3、字节缓冲流如何使用?
- public BufferedOutputStream(OutputStream os)
- public BufferedInputStream(InputStream is)
- 功能上并无很大变化,性能提升了。
IO流的体系
BufferedReader(字符缓冲输入流)
- 作用:自带8K(8192)的字符缓冲池,可以提高字符输入流读取字符数据的性能。
字符缓冲输入流新增的功能:按照行读取字符
package com.liu.d2_buffered_stream;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.Reader;
/**
* 目标:掌握字符缓冲输入流的用法。
*/
public class BufferedReaderTest2 {
public static void main(String[] args) {
try (
Reader fr = new FileReader("io-app2\\src\\itheima04.txt");
// 创建一个字符缓冲输入流 包装原始的字符输入流
//不用多态写 也不用原始得字符输入流
BufferedReader br = new BufferedReader(fr);
){
// char[] buffer = new char[3];
// int len;
// while ((len = br.read(buffer)) != -1){ //br.read(buffer) 读取数据
// System.out.print(new String(buffer, 0, len));
// }
//读取四行
//如果下面没用内容 却还在读 就返回null
// System.out.println(br.readLine());
// System.out.println(br.readLine());
// System.out.println(br.readLine());
// System.out.println(br.readLine());
//读取文本内容并输出
String line; // 记住每次读取的一行数据
while ((line = br.readLine()) != null){
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
BufferedWriter(字符缓冲输出流)
- 作用:自带8K的字符缓冲池,可以提高字符输出流写字符数据的性能。
字符缓冲输出流新增的功能:换行
package com.liu.d2_buffered_stream;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.Writer;
/**
* 目标:掌握字符缓冲输出流的用法。
*/
public class BufferedWriterTest3 {
public static void main(String[] args) {
try (
Writer fw = new FileWriter("io-app2/src/itheima05out.txt", true);
// 创建一个字符缓冲输出流管道包装原始的字符输出流
//不用多态写
BufferedWriter bw = new BufferedWriter(fw);
//BufferedWriter 默认字符缓冲输出流
){
//用bw 来写
bw.write('a');
bw.write(97);
bw.write('磊');
bw.newLine();
bw.write("我爱你中国abc");
//增加换行功能
bw.newLine();
} catch (Exception e) {
e.printStackTrace();
}
}
}
1、字符缓冲流有几种,作用是什么?
字符缓冲流自带8K缓冲区
可以提高原始字符流读写数据的性能
2、两种字符缓冲流如何使用?各自新增了什么功能?
- public BufferedReader(Reader r) 性能提升了,多了readLine()按照行读取的功能
- public BufferedWriter(Writer w) 性能提升了,多了newLine()换行的功能
案例:拷贝出师表到另一个文件,恢复顺序
需求:
- 把《出师表》的文章顺序进行恢复到一个新文件中。
分析:
- 1、定义一个缓存字符输入流管道与源文件接通。
- 2、定义一个List集合存储读取的每行数据。
- 3、定义一个循环按照行读取数据,存入到List集合中去。
- 4、对List集合中的每行数据按照首字符编号升序排序。
- 5、定义一个缓存字符输出管道与目标文件接通。
- 6、遍历List集合中的每个元素,用缓冲输出管道写出并换行。
package com.liu.d2_buffered_stream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test3 {
public static void main(String[] args) {
try(
//2、创建缓冲字符输入流管道与源文件接通、
BufferedReader br = new BufferedReader(new FileReader("day09-io2\\src\\csd.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("day09-io2\\src\\newcsd.txt"));
) {
//目标:恢复出师表得顺序到新文件中
//1、定义一个ArrayList集合存储每段内容
List<String> data = new ArrayList<>();
//3、按照行读取每段
String line;
while ((line = br.readLine()) != null){
data.add(line);
}
//4、对List集合中的每段文章进行排序
Collections.sort(data);
//5、遍历List集合的每段内容,依次写出去到新文件中
for (String ln : data){
bw.write(ln);
bw.newLine();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
IO流(二):原始流、缓冲流的性能分析
步骤:原始流、缓冲流的性能分析[重点]
测试用例:
- 分别使用原始的字节流,以及字节缓冲流复制一个很大视频。
测试步骤:
1、使用低级的字节流按照一个一个字节的形式复制文件。、
2、使用低级的字节流按照字节数组的形式复制文件。
3、使用高级的缓冲字节流按照一个一个字节的形式复制文件。
4、使用高级的缓冲字节流按照字节数组的形式复制文件。
package com.liu.d2_buffered_stream;
import java.io.*;
/**
目标:观察原始流和缓冲流的性能。
*/
public class TimeTest4 {
// 复制的视频路径
private static final String SRC_FILE = "D:\\resource\\线程池.avi"; //视频内存889MB
// 复制到哪个目的地
private static final String DEST_FILE = "D:\\";
public static void main(String[] args) {
//分别调用方法
// copy01(); // 低级字节流一个一个字节的赋值,慢的简直让人无法忍受,直接淘汰!
copy02();// 低级的字节流流按照一个一个字节数组的形式复制,速度较慢!
// copy03(); // 缓冲流按照一个一个字节的形式复制,速度较慢,直接淘汰!
copy04(); // 缓冲流按照一个一个字节数组的形式复制,速度极快,推荐使用!
}
//低级字节流 一个一个字节的赋值
private static void copy01() {
//拿到系统的当前时间
long startTime = System.currentTimeMillis();
try (
//原始字节输入流;SRC_FILE复制到1.avi文件里 ; DEST_FILE表示路径重写
InputStream is = new FileInputStream(SRC_FILE);
//原始字节输出流
OutputStream os = new FileOutputStream(DEST_FILE + "1.avi");
){
int b; //记住一个字节
while ((b = is.read()) != -1){
os.write(b);
}
}catch (Exception e){
e.printStackTrace();
}
//截止时间
long endTime = System.currentTimeMillis();
System.out.println("低级字节流一个一个字节复制耗时:" + (endTime - startTime) / 1000.0 + "s");
}
// 低级字节流 一个一个字节的赋值
private static void copy02() {
long startTime = System.currentTimeMillis();
try (
InputStream is = new FileInputStream(SRC_FILE);
OutputStream os = new FileOutputStream(DEST_FILE + "2.avi");
){
byte[] buffer = new byte[1024*64];
int len;
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
}catch (Exception e){
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("低级字节流使用字节数组复制耗时:" + (endTime - startTime) / 1000.0 + "s");
}
//缓冲流 按照一个一个字节的形式
private static void copy03() {
long startTime = System.currentTimeMillis();
try (
InputStream is = new FileInputStream(SRC_FILE);
BufferedInputStream bis = new BufferedInputStream(is);
OutputStream os = new FileOutputStream(DEST_FILE + "3.avi"); //拷贝到第三个视频文件中去
BufferedOutputStream bos = new BufferedOutputStream(os); //缓冲字节输出流
){
int b;
while ((b = bis.read()) != -1){
bos.write(b);
}
}catch (Exception e){
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("缓冲流一个一个字节复制耗时:" + (endTime - startTime) / 1000.0 + "s");
}
//缓冲流 按照一个一个字节数组的形式
private static void copy04() {
long startTime = System.currentTimeMillis();
try (
InputStream is = new FileInputStream(SRC_FILE);
BufferedInputStream bis = new BufferedInputStream(is, 64 * 1024);
OutputStream os = new FileOutputStream(DEST_FILE + "4.avi");
BufferedOutputStream bos = new BufferedOutputStream(os, 64 * 1024);
){
byte[] buffer = new byte[1024 * 64]; // 32KB //字节数组越大越好 读写性能提升 但大到一定程度,性能就不影响
int len;
while ((len = bis.read(buffer)) != -1){
bos.write(buffer, 0, len);
}
}catch (Exception e){
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("缓冲流使用字节数组复制耗时:" + (endTime - startTime) / 1000.0 + "s");
}
}
推荐使用哪种方式提高字节流读写数据的性能?
建议使用字节缓冲输入流、字节缓冲输出流,结合字节数组的方式,目前来看是性能最优的组合。
IO流(二):引出问题:不同编码读取会出现乱码
不同编码读取出现出现乱码的问题
- 如果代码编码和被读取的文本文件的编码是一致的,使用字符流读取文本文件时不会出现乱码!
- 如果代码编码和被读取的文本文件的编码是不一致的,使用字符流读取文本文件时就会出现乱码!
package com.liu.d3_transform;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.Reader;
/**
* 目标:掌握不同编码读取乱码的问题。
*/
public class Test1 {
public static void main(String[] args) {
try (
// 1、创建一个文件字符输入流与源文件接通
// 代码编码:UTF-8 文件的编码:UTF-8
// Reader fr = new FileReader("io-app2/src/itheima04.txt");
// 1 床 前 明 月光c
// GBK o [oo] [oo] [oo] ...
// UTF-8 1 ?????
// 代码编码:UTF-8 文件的编码:GBK
Reader fr = new FileReader("io-app2/src/itheima06.txt");
// 2、把文件字符输入流包装成缓冲字符输入流
BufferedReader br = new BufferedReader(fr);
){
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
IO流(二):转换流——InputStreamReader、OutputStreamWriter
IO流的体系
InputStreamReader(字符输入转换流)
- 解决不同编码时,字符流读取文本内容乱码的问题。
- 解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了。
package com.liu.d3_transform;
import java.io.*;
/**
* 目标:掌握字符输入转换流的作用。
*/
public class InputStreamReaderTest2 {
public static void main(String[] args) {
try (
// 1、得到文件的原始字节流(GBK的字节流形式)
InputStream is = new FileInputStream("io-app2/src/itheima06.txt");
// 2、把原始的字节输入流按照指定的字符集编码转换成字符输入流
Reader isr = new InputStreamReader(is, "GBK");
// 3、把 字符输入流 包装成 缓冲字符输入流
BufferedReader br = new BufferedReader(isr);
){
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
字符输入转换流InputStreamReader的作用是啥?
- 可以解决字符流读取不同编码乱码的问题
- public InputStreamReader(InputStream is,String charset): 可以指定编码把原始字节流转换成字符流,如此字符流中的字符不乱码。
需要控制写出去的字符 使用什么字符集编码,该咋整?
1、调用String提供的getBytes方法解决?
2、使用”字符输出转换流”实现。
IO流的体系
OutputStreamWriter字符输出转换流
- 作用:可以控制写出去的字符使用什么字符集编码。
- 解决思路:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了。
package com.liu.d3_transform;
import java.io.*;
/**
* 目标:掌握字符输出转换流的使用。
*/
public class OutputStreamWriterTest3 {
public static void main(String[] args) {
// 指定写出去的字符编码。
try (
// 1、创建一个文件字节输出流
OutputStream os = new FileOutputStream("io-app2/src/itheima07out.txt");
// 2、把原始的字节输出流,按照指定的字符集编码转换成字符输出转换流。
Writer osw = new OutputStreamWriter(os, "GBK");
// 3、把字符输出流包装成缓冲字符输出流
BufferedWriter bw = new BufferedWriter(osw);
){
bw.write("我是中国人abc");
bw.write("我爱你中国123");
} catch (Exception e) {
e.printStackTrace();
}
}
}
字符输出转换流Output StreamWriter的作用?
public Output StreamWriter(Output Stream os,String charset)
可以指定编码把字节输出流转换成字符输出流,从而可以指定写出去的字符编码!
IO流(二):其他流:打印流、打印流应用
IO流的体系
PrintStream/PrintWriter(打印流)
- 作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去。
PrintStream提供的打印数据的方案

PrintWriter 提供的打印数据的方案
package com.liu.d4_printstream;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
/**
* 目标:掌握打印流:PrintStream/PrintWriter的用法。
*/
public class PrintTest1 {
public static void main(String[] args) {
try (
// 1、创建一个打印流管道
// PrintStream 打印数据
//Charset.forName("GBK") 指定字符集对象为GBK
// PrintStream ps = new PrintStream("io-app2/src/itheima08.txt", Charset.forName("GBK"));
//
// PrintStream ps = new PrintStream("io-app2/src/itheima08.txt");
// PrintWriter 打印数据
//new FileOutputStream true 追加数据
PrintWriter ps =
new PrintWriter(new FileOutputStream("io-app2/src/itheima08.txt", true));
){
//支持 几乎打印任意类型的数据
ps.println(97);
ps.println('a');
ps.println("我爱你中国abc");
ps.println(true);
ps.println(99.5);
// ps.write(97); // 'a'
} catch (Exception e) {
e.printStackTrace();
}
}
}
PrintStream和PrintWriter的区别
- 打印数据的功能上是一模一样的:都是使用方便,性能高效(核心优势)
- PrintStream继承自字节输出流Output Stream,因此支持写字节数据的方法。
- PrintWriter继承自字符输出流Writer,因此支持写字符数据出去。
打印功能2者是一样的使用方式
- PrintStream继承自字节输出流Output Stream,支持写字节
- PrintWrite继承自字符输出流Writer,支持写字符
打印流的优势是什么?
- 两者在打印功能上都是使用方便,性能高效(核心优势)
打印流的一种应用:输出语句的重定向
package com.liu.d4_printstream;
import java.io.PrintStream;
/**
* 目标:了解下输出语句的重定向。
*/
public class PrintTest2 {
public static void main(String[] args) {
System.out.println("老骥伏枥"); //内容输入到控制台里
System.out.println("志在千里");
try (
//打印流
PrintStream ps = new PrintStream("io-app2/src/itheima09.txt"); ){
// 把系统默认的打印流对象改成自己设置的打印流
System.setOut(ps);
//打印的东西 到 itheima09.txt文件 里
System.out.println("烈士暮年");
System.out.println("壮心不已");
} catch (Exception e) {
e.printStackTrace();
}
}
}
IO流(二):其他流:数据流、序列化流
IO流的体系
DataOutputStream(数据输出流)
- 允许把数据和其类型一并写出去。
package com.liu.d5_data_stream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
/**
* 目标:数据输出流。
*/
public class DataOutputStreamTest1 {
public static void main(String[] args) {
try (
// 1、创建一个数据输出流包装低级的字节输出流
DataOutputStream dos =
new DataOutputStream(new FileOutputStream("io-app2/src/itheima10out.txt"));
){
dos.writeInt(97);
dos.writeDouble(99.5);
dos.writeBoolean(true);
dos.writeUTF("黑马程序员666!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
DataInputStream(数据输入流)
- 用于读取数据输出流写出去的数据。
package com.liu.d5_data_stream;
import java.io.DataInputStream;
import java.io.FileInputStream;
/**
* 目标:使用数据输入流读取特定类型的数据。
*/
public class DataInputStreamTest2 {
public static void main(String[] args) {
try (
DataInputStream dis =
new DataInputStream(new FileInputStream("io-app2/src/itheima10out.txt"));
){
int i = dis.readInt();
System.out.println(i);
double d = dis.readDouble();
System.out.println(d);
boolean b = dis.readBoolean();
System.out.println(b);
String rs = dis.readUTF();
System.out.println(rs);
} catch (Exception e) {
e.printStackTrace();
}
}
}
IO流的体系
ObjectOutputStream(对象字节输出流)
- 可以把Java对象进行序列化:把Java对象存入到文件中去。
注意:
对象如果要参与序列化,必须实现序列化接口(java.io.Serializable)
package com.liu.d6_object_stream;
import java.io.Serializable; // 注意:对象如果需要序列化,必须实现序列化接口。
public class User implements Serializable {
private String loginName; //登录名
private String userName; //用户名
private int age; //年龄
// transient 这个成员变量将不参与序列化。
private transient String passWord;
public User() {
}
public User(String loginName, String userName, int age, String passWord) {
this.loginName = loginName;
this.userName = userName;
this.age = age;
this.passWord = passWord;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
@Override
public String toString() {
return "User{" +
"loginName='" + loginName + '\'' +
", userName='" + userName + '\'' +
", age=" + age +
", passWord='" + passWord + '\'' +
'}';
}
}
package com.liu.d6_object_stream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
/**
* 目标:掌握对象字节输出流的使用:序列化对象。
*/
public class Test1ObjectOutputStream {
public static void main(String[] args) {
try (
// 2、创建一个对象字节输出流包装原始的字节 输出流。
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("io-app2/src/itheima11out.txt"));
){
// 1、创建一个Java对象。
//如果需要 某个对象去进行序列化 必须让对象类去实现一个接口,否则会报错+
User u = new User("admin", "张三", 32, "666888xyz");
// 3、序列化对象到文件中去
oos.writeObject(u);
System.out.println("序列化对象成功!!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
ObjectInputStream(对象字节输入流)
- 可以把Java对象进行反序列化:把存储在文件中的Java对象读入到内存中来。
package com.liu.d6_object_stream;
import java.io.Serializable; // 注意:对象如果需要序列化,必须实现序列化接口。
public class User implements Serializable {
private String loginName; //登录名
private String userName; //用户名
private int age; //年龄
// transient 这个成员变量将不参与序列化。
private transient String passWord;
public User() {
}
public User(String loginName, String userName, int age, String passWord) {
this.loginName = loginName;
this.userName = userName;
this.age = age;
this.passWord = passWord;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
@Override
public String toString() {
return "User{" +
"loginName='" + loginName + '\'' +
", userName='" + userName + '\'' +
", age=" + age +
", passWord='" + passWord + '\'' +
'}';
}
}
package com.liu.d6_object_stream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
/**
* 目标:掌握对象字节输入流的使用:反序列化对象。
*/
public class Test2ObjectInputStream {
public static void main(String[] args) {
try (
// 1、创建一个对象字节输入流管道,包装 低级的字节输入流与源文件接通
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("io-app2/src/itheima11out.txt"));
){
User u = (User) ois.readObject(); //强转成用户对象
System.out.println(u);
} catch (Exception e) {
e.printStackTrace();
}
}
}
如果要一次序列化多个对象,咋整?
- 用一个ArrayList集合存储多个对象,然后直接对集合进行序列化即可
- 注意:ArrayList集合已经实现了序列化接口!
对象序列化的含义是什么?怎么实现对象序列化?需要注意什么?
- 把对象数据存入到文件中去。
- 对象字节输出流ObjectOutputStram
- public void writeObject(Object obj)
- 对象必须实现序列化接口
对象反序列化的含义是什么?怎么实现对象反序列化?
- 把对象数据存入到文件中去。
- 对象字节输入流ObjectInputStram
- public Object readObject()
IO流(二):补充知识:IO框架
什么是框架
- 解决某类问题,编写的一套类、接口等,可以理解成一个半成品,大多框架都是第三方研发的。
- 好处:在框架的基础上开发,可以得到优秀的软件架构,并能提高开发效率
- 框架的形式:一般是把类、接口等编译成class形式,再压缩成一个.jar结尾的文件发行出去。
什么是IO框架?
- 封装了Java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等。
Commons—io
- Commons-io是apache开源基金组织提供的一组有关IO操作的小框架,目的是提高IO流的开发效率。
package com.liu;
import java.nio.file.Files;
/**
* 目标:使用CommonsIO框架进行IO相关的操作。
*/
public class CommonsIOTest1 {
public static void main(String[] args) throws Exception {
// FileUtils.copyFile(new File("io-app2\\src\\itheima01.txt"), new File("io-app2/src/a.txt"));
// FileUtils.copyDirectory(new File("D:\\resource\\私人珍藏"), new File("D:\\resource\\私人珍藏3"));
// FileUtils.deleteDirectory(new File("D:\\resource\\私人珍藏3"));
// Java提供的原生的一行代码搞定很多事情
// Files.copy(Path.of("io-app2\\src\\itheima01.txt"), Path.of("io-app2\\src\\b.txt"));
System.out.println(Files.readString(Path.of("io-app2\\src\\itheima01.txt")));
}
}