本篇博客是上一篇文章的续写,重点介绍数据流,还包括三道练习题.
🐎文章专栏: JavaEE初阶
🚀若有问题 评论区见
❤ 欢迎大家点赞 评论 收藏 分享
如果你不知道分享给谁,那就分享给薯条.
你们的支持是我不断创作的动力 .
王子,公主请阅🚀
要开心
要快乐
顺便进步
1.数据流
什么是输入?什么是输出呢?
文件操作中输入和输出是站在CPU的角度来说的.
1.1 字节流
1.1.1 InputStream概述
InputStream只是一个抽象类, 要使用还需要具体的实现类. 关于 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从件中读取,所以使用 FileInputStream.
注意抽象类不能new实例,只能new子类.
FileInputStream 概述
代码示例1:
public class InputStream示例一 {
public static void main(String[] args) {
//需要先在D盘上准备一个test.txt文件.此处文件内容为 你好世界
try(InputStream inputStream = new FileInputStream("d:/test.txt")) {
while(true) {
int n = inputStream.read();//无参数read方法,一次读取一个字节.
if(n == -1) {
//表示已经读完
break;
}
char ret = (char)n;
System.out.println(n);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
问题1: 不是字节流吗?为什么数据类型不用byte,要用int?
我们知道当read()方法返回-1时,说明已经读取完毕,如果用byte就接收不了-1了.
同样地,在字符流中也是用int类型作为返回值类型.
一个字节,8个比特位,2^8 - 1= 255
所以范围是[-1,255]
问题2: try(InputStream inputStream = new FileInputStream(“d:/test.txt”)){}是什么意思?所有的对象都适用吗?
try with resources这个语法的目的就是
()定义的变量会在 try 代码块结束的时候(无论是正常结束还是抛出异常),自动调用其中的 close 方法.
同时要求写到()里的对象必须要实现Closeable接口.
代码示例2:
public class InputStream示例一 {
public static void main(String[] args) {
//需要先在D盘上准备一个test.txt文件.此处文件内容为 你好世界
//此处使用try(){},是为了防止忘记close().try(){}能够帮助我们自动close().
try(InputStream inputStream = new FileInputStream("d:/test.txt")) {
while(true) {
int n = inputStream.read();//无参数read方法,一次读取一个字节.
if(n == -1) {
//表示已经读完
break;
}
char ret = (char)n;
System.out.println(n);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
两个示例相比之下,后一种的IO次数更少,性能更好.
代码示例3: 这里我们把文件内容中填充中文看看,注意,写中文的时候使用 UTF-8 编码。test.txt 中填写 “你好世界”
注意:这里我利用了这几个中文的 UTF-8 编码后长度不超过 1024 字节的现状,但这种方式并不是通用的
public class InputStream示例二 {
//需要先在D盘上准备一个test.txt文件.此处文件内容为 你好世界
//此处使用try(){},是为了防止忘记close().try(){}能够帮助我们自动close().
public static void main(String[] args) {
try(InputStream inputStream = new FileInputStream("d:/test.txt")) {
byte[] buf = new byte[1024];
while(true) {
int len = inputStream.read(buf);
if(len == -1) {
//文件全部读完
break;
}
//test文件内容是字符串类型的,buf是字节类型的,要想打印出原内容,得先转成字符串类型.
String s = new String(buf);
System.out.println(s);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
利用Scanner进行字符读取
上述例子中,我们看到了对字符类型直接使用 InputStream 进行读取是非常麻烦且困难的,所以,我们使用一种我们之前比较熟悉的类来完成该工作,就是 Scanner 类。
public class InputStream示例三 {
//需要现在D盘上创建一个test.txt文件,并填充"你好世界"
public static void main(String[] args) throws IOException{
try(InputStream inputStream = new FileInputStream("d:/test.txt")) {
Scanner sc = new Scanner(inputStream);
String s = sc.next();
System.out.println(s);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
1.1. 2 OutputStream概述
OutputStream 同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中,所以使用 FileOutputStream.
利用OutputStream的writer方法 进行字符写入
代码示例一:
public class OutputStream示例一 {
public static void main(String[] args) {
//在test文件中写入hello,会将原有内容覆盖.
try(OutputStream outputStream = new FileOutputStream("d:/test.txt")) {
String s = "HelloWorld";
outputStream.write(s.getBytes());
//不要忘记flush
outputStream.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
若是不想将原有内容覆盖,该怎么操作?
public class OutputStream示例一 {
public static void main(String[] args) {
//在test文件中写入hello,会将原有内容覆盖.
try(OutputStream outputStream = new FileOutputStream("d:/test.txt",true)) {
//构造时,加上true.表示不将原有内容覆盖.
String s = "HelloWorld";
outputStream.write(s.getBytes());
//不要忘记flush
outputStream.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
代码示例二:
public class OutputStream示例二 {
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("d:/test.txt",true)) {
byte[] b = new byte[] {
(byte)'G',(byte)'o',(byte)'o',(byte)'d'
};
outputStream.write(b);
String s = new String(b);
System.out.println(s);
//不要忘记flush
outputStream.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
1.2 字符流
字符流的输入输出和字节流的输入输出使用方法类似. 这里只给代码示例,不做过多说明.
1.2.1 Reader
代码示例:
public class Demo7 {
public static void main(String[] args) throws IOException {
//无参数read方法,一次读一个字符.
// Reader reader = new FileReader("d:/test.txt");
//
// while(true) {
// int c = reader.read();
// if(c == -1) {
// break;
// }
// char ch = (char)c;
// System.out.print(ch);
// }
//手动close
//reader.close();
//带有字符数组参数的read方法,将没有元素read()方法填满
// Reader reader = new FileReader("d:/test.txt");
// try{
// while(true) {//循环多次,把文件中的内容全部读完.
// char[] cubf = new char[2];
// //n表示读到的字符的个数
// int n = reader.read(cubf);
// if(n == -1) {
// //读取完毕
// break;
// }
// System.out.println("n = "+n);
// for (int i = 0; i < n; i++) {
// System.out.println(cubf[i]);
// }
// }
// }finally {
// //一个文件使用完了之后要记得close.
// reader.close();
// }
try(Reader reader = new FileReader("d:/test.txt");) {
while(true) {
char[] cbuf = new char[3];
int n = reader.read(cbuf);
if(n == -1) {
break;
}
System.out.println("n = "+ n);
for (int i = 0; i < n; i++) {
System.out.println(cbuf[i]);
}
}
}
}
}
Java中一个字符由UTF-8编码的是三个字节.24个比特位. 计算2^24发现 >> 65535. 最大范围不应该是2^24-1吗?
在 Java 标准库内部, 对于字符编码是进行了很多的处理工作的.
如果是只使用 char,此时使用的字符集,固定就是 unicode.
如果是使用 String,此时就会自动的把每个字符的 unicode 转换成 utf8.
同样表示一个汉字,utf8是3个字节,而unicode是2个字节.
也就是说char类型的数组中每一个字符都是unicode编码的.一旦使用这个字符数组构造成String,每一个字符就会被转换成utf8.
相应地,如果字符串s 调用 s.charAt(i),也会转换成unicode.
1.2.2 Writer
代码示例:
public class Demo8 {
public static void main(String[] args) {
try(Writer writer = new FileWriter("d:/test.txt",true)) {//不想删除原有数据,就得加true.
//直接使用writer方法就可以写入数据
writer.write("我在学习文件IO");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
利用 PrintWriter 找到我们熟悉的方法
上述,我们其实已经完成输出工作,但总是有所不方便,我们接来下将 OutputStream 处理下,使用
PrintWriter 类来完成输出,因为
PrintWriter 类中提供了我们熟悉的 print/println/printf 方法
代码示例:
public class Demo12 {
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("d:/test.txt")) {
PrintWriter writer = new PrintWriter(outputStream);
writer.printf("hello");
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
PrintWriter 这样的类, 在进行写入的时候,不一定是直接写硬盘, 而是先把数据写入到一个 内存构成的"缓冲区"中(buffer).
引入缓冲区,目的是为了提高效率!!!
当我们写入缓冲区之后,如果还没来得及把缓冲区的数据写入硬盘,进程就结束了,此时数据就丢了!!
为了能够确保数据确实被写入硬盘,就应该在合适的时机,使用 flush 方法手动刷新缓冲区.
2.小程序练习
运用文件地基本操作和文件内容读写操作实现一些小工具程序,锻炼我们地能力.
练习一
扫描指定目录,并找到名称中包含指定字符地所有普通文件(不包含目录),并且后序询问用户是否要删除文件.
import java.io.File;
import java.util.Scanner;
/**
* Date: 2025-04-2025/4/16
* User: 11
* Time: 23:13
* 扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且询问用户是否要删除该文件.
*/
public class 实现练习1 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入你要扫描的目录: ");
String path = sc.next();
File rootpath = new File(path);
//判断目录是否合法.
if(!rootpath.isDirectory()) {
System.out.println("输入的目录不合法!");
}
//输入要查询的关键字
System.out.println("请输入要删除文件的关键词: ");
String word = sc.next();
//递归扫描目录
scanDir(rootpath,word);
}
//递归扫描目录
public static void scanDir(File rootpath,String word) {
//1.先列出rootpath中所有文件和目录
File[] files = rootpath.listFiles();
if(files == null) {
return;
}
for(File f : files) {
//加个日志,方便观察当前递归的执行过程
System.out.println("当前扫描文件: "+f.getAbsoluteFile());
if(f.isDirectory()) {
//目录继续递归
scanDir(f,word);
}else {
//普通文件,判断是否删除.
checkDelete(f,word);
}
}
}
public static void checkDelete(File f,String word) {
if(!f.getName().contains(word)) {
//不是word文件,不必删除.
return;
}
//需要删除
Scanner sc = new Scanner(System.in);
System.out.println("当前文件为: "+f.getAbsoluteFile() +"请确认是否删除,请输入(Y/n): ");
String choice = sc.next();
if(choice.equals("Y") || choice.equals("y")) {
f.delete();
System.out.println("删除成功!");
}else {
System.out.println("取消删除!");
}
}
}
练习二
进行普通文件的复制
public class test2 {
public static void main(String[] args) {
//输入要被复制文件的路径
Scanner sc = new Scanner(System.in);
System.out.println("请输入文件路径: ");
String s = sc.next();
File sourceFile = new File(s);
//判断文件是否存在.
if(!sourceFile.exists()) {
System.out.println("文件不存在,请确实路径是否正确!");
}
//判断文件是否为普通文件.
if(!sourceFile.isFile()) {
System.out.println("文件不是普通文件,请确认路径是否正确!");
}
System.out.println("请输入要复制到的目标路径: ");
String destPath = sc.next();
File destFile = new File(destPath);
if(destFile.exists()) {
if(destFile.isDirectory()) {
System.out.println("目标路径已存在,并且是一个目录,请确认路径是否正确!");
}
if(destFile.isFile()) {
System.out.println("目标路径已存在,并且是一个文件,请确认是否覆盖(y/n): ");
String choice = sc.next();
if(!choice.toLowerCase().contains("y")) {
System.out.println("停止覆盖!");
return;
}
}
}
//将文件从sourceFile处复制到destFile处
try(InputStream is = new FileInputStream(sourceFile)) {
try(OutputStream os = new FileOutputStream(destFile)) {
byte[] buf = new byte[1024];
int len;
while(true) {
len = is.read(buf);
if(len == -1) {
break;
}
os.write(buf,0,len);//将buf这个数组从下标0开始,写了len个.
}
os.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println("覆盖成功!");
}
}
练习三
扫描指定目录,并找到内容中包含指定字符的所有普通文件(不包含目录)
注意:给出的代码性能较差,所以尽量不要在太复杂的目录下或者大文件下实验
public class test3 {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
System.out.println("请输入你要扫描的路径: ");
String rootPath = sc.next();
File rootFile = new File(rootPath);
//判断路径是否合法
if(!rootFile.isDirectory()) {
System.out.println("输入路径不合法!");
}
System.out.println("请输入你要找出的文件名中的字符: ");
String word = sc.next();
List<File> result = new ArrayList<>();
//深度优先遍历(递归)
dfs(rootFile,word,result);
System.out.println("共找到了符合条件的文件: " + result.size() + "个, 它们分别是: ");
for(File file : result) {
System.out.println(file.getCanonicalPath());
}
}
public static void dfs(File rootFile,String word,List<File> result) throws IOException {
File[] files = rootFile.listFiles();
if(files == null || files.length == 0) {
return;
}
for(File file : files) {
if(file.isDirectory()) {
dfs(file,word,result);
}else {
if(checkContent(file,word) || checkName(file,word)) {
result.add(file.getAbsoluteFile());
}
}
}
}
public static boolean checkContent(File file,String word) throws IOException {
StringBuilder sb = new StringBuilder();//用于高效地拼接文件内容
try(InputStream is = new FileInputStream(file)) {
//Scanner 用于逐行读取输入流中的内容。
//这里指定了编码格式为 "UTF-8",表示文件内容是以 UTF-8 编码格式进行读取
try(Scanner scanner = new Scanner(is,"UTF-8")) {
while(scanner.hasNextLine()) {//判断文件中是否还有下一行内容。如果有,则进入循环,逐行读取文件。
sb.append(scanner.nextLine());//scanner.nextLine()表示读取当前行的内容。
sb.append("\r\n");//在每行内容后面追加一个换行符(\r\n),这是为了确保在拼接多行内容时每行之间能够保持换行。
}
}
}
// 检查 StringBuilder 对象 sb 中是否包含指定的 word 字符串。
// indexOf(word) 方法会返回 word 在 sb 中首次出现的位置。如果 word 不在 sb 中,则返回 -1。
return sb.indexOf(word) != -1;
}
public static boolean checkName(File file,String word) {
//怎么判断文件名包含word关键字?
if(file.getName().contains(word)) {
return true;
}
return false;
}
}
本篇博客到这里就结束啦, 感谢观看 ❤❤❤
🐎期待与你的下一次相遇😊😊😊