文章目录
在上一篇文章主要讲述了 Java 针对文件系统进行操作(创建文件,删除文件,创建目录,重命名文件)。本文章是上一篇文章的续集,主要讲述 Java 针对文件内容进行操作(读文件,写文件)。
读写操作就涉及到流的概念,Java 中通过 ‘流’(stream)这样的一组类,进行上述的文件内容操作。
主要分为输入流(InputStream)和输出流(OutputStream)这两类。
一、输入流
说明:InputStream 只是一个抽象类,要使用还需要具体的实现类。关于 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从文件中读取,所以使用 FileInputStream。
1.1 InputStream 概述:
- 方法:
修饰符及返回值类型方法签名说明intread()读取一个字节的数据,返回
```
-1
代表已经完全读完了。intread(byte[] b)最多读取
b.length
字节的数据到 b 中,返回实际读到的数量。-1 代表已经读完了。intread(byte[] b, int off, int len)最多读取
len - off
字节的数据到 b 中,放在从 off 开始,返回实际读到的数量。-1 代表已经读完了。voidclose()关闭字节流。
- 构造方法:
签名说明FileInputStream(File file)利用 File 构造文件输入流FileInputStream(String name)利用文件路径构造文件输入流
### 1.2 read 方法详解:
> 不难看到 read 方法有 3 种使用的版本,接下来我会分别给大家演示前两种(第 3 种就是第 2 种加上起点),并进行讲解。
> 
- 版本一:无参数,一次读取一个字节。读取到的内容,就通过返回值来进行表示。
注意:这里的返回值是 int 类型,这是为什么呢?既然一次读取一个字节,返回值不是 byte 就行了?
答:注意有以下三个原因:
1. 为了能够有额外的空间表示 “到达末尾” -1 这样的情况。
2. 确保读取到的数据都是正数:从原则上来说,“字节” 这样的概念,本身就是 “无符号”,但是 byte 是带有符号的,使用 int 就可以确保读取出来的字节都是正数。按照 “无符号”的方式来处理了。
3. 为什么不使用 ```short```,而是使用 ```int```?答:虽然使用 int 较 short 是浪费了 2 个字节。但是还是不建议使用 short 类型,这个类型随着硬件的升级,正在逐渐被取代。对于 32 位 CPU,一次处理 4 个字节的数据,使用 short,操作系统内部还是要把 short 转化成 int 来处理,32 位 CPU 如此,对于 64 位 CPU,short 就更加没有意义了。
案例演示:
关于为什么 inputStream 的创建为什么能和为什么要写在try()里面,在后面会有详细解释,这里主要看 read 的使用。
importjava.io.*;publicclass demo2 {publicstaticvoidmain(String[] args)throwsIOException{try(InputStream inputStream =newFileInputStream("./text.txt")){//推荐这样的写法。while(true){int n = inputStream.read();if(n ==-1)break;System.out.printf("0x%x ",n);//按照十六进制打印}}}}
案例演示结果:

- 版本二:带有一个参数 byte[]。
**版本二和版本一的返回值的含义不太一样,具体差别如下:版本二的返回值代表读入 byte 数组的长度,如果返回 -1 表示读取完毕。** 这个写法在 Java 中不太常见,这是一种属于
C++
式的写法。用参数来作为函数的返回结果(输出型参数)。
一次读取多少,取决于数组的长度和文件内容多少,read 会尽可能把数组填满。
案例演示:
importjava.io.*;publicclass demo3 {publicstaticvoidmain(String[] args)throwsIOException{try(InputStream inputStream =newFileInputStream("./text.txt")){byte[] bytes =newbyte[1024];int n =0;while(true){
n = inputStream.read(bytes);if(n ==-1)break;for(int i =0;i < n;i++){System.out.printf("0x%x ",bytes[i]);}}}}}
案例演示结果:
将文件完全读完的两种方式。相比较而言,后一种的 IO 次数更少,性能更好。

### 1.3 close 方法:
关闭文件是一个非常关键的操作。
为什么要关闭文件?
答:没关闭文件大部分情况下我们是感知不到的,但是一旦出现问题,就是大事(能击碎年终奖😭)。在打开文件的时候,会在操作系统内核,PCB 结构体(进程)中给“文件描述符表(长度存在上限,并且不能自动扩容)”添加一个元素,这个元素就是表示当前打开文件的相关信息。一旦占满了之后,再尝试打开,就会打开文件失败。(其他的操作,网络通信相关的操作就可能收到影响)。
inputStream.close();
上面的这种写法是一般的关闭文件的方式。但是这样写不太好,因为如果代码前面出现异常或者 return,此时 close 就执行不到了。虽然使用 try finally 可以解决问题,但是写出来的代码不太优化。
try(){........}finally{
inputStream.close();}
对于老程序猿来说代码是语文,要追求优雅。于是 Java 实现了把流对象的创建写到 try()里,代码执行出了 try{}时,就会自动调用
inputStream
的
close
了。
> 写法如下:
>
> ```
> try(InputStream inputStream =newFileInputStream("./text.txt"))
> ```
>
> 要抛异常(这里没写)。
> 注意:务必要实现 Closeable 接口的类,才能够放到 try() 里。
> 
### 1.4 利用 Scanner 进行读操作:
上述栗子中,我们看到了对字符类型直接使用 InputStream 进行读取是非常麻烦且困难的,所以,我们使用一种我们之前比较熟悉的类来完成该共作,就是
Scanner
类。
构造方法说明Scanner(InputStream is)构造一个新的 Scanner ,产生从指定输入流扫描的值。Scanner(InputStream is, String charset)构造一个新的 Scanner ,产生从指定输入流扫描的值。使用 charset 字符集进行 is 的扫描读取。
示例如下:
publicstaticvoidmain(String[] args)throwsIOException{try(InputStream inputStream =newFileInputStream("./text.txt")){try(Scanner in =newScanner(inputStream)){}}}
### 1.5 Reader:
以字符为单位读取数据,在 InputStream 中,虽然可以通过 Scanner 传入字符集来达到读取字符的效果,但是这种办法并不是通用的。我们更推荐使用 Reader 来实现读取字符。
由于 Reader 的使用方法和 InputStream 类的使用方式基本一致,所以这里只叙述 read 方法。

注意:这里传入的是
char
类型的数组,这样就有足够的内存来保存字符了。
> 由于写法基本一样,这里就演示版本二(带字符数组)。
importjava.io.*;publicclass demo2 {publicstaticvoidmain(String[] args)throwsIOException{try(Reader reader =newFileReader("./text.txt")){char[] chars =newchar[1024];while(true){int n = reader.read(chars);if(n ==-1)break;for(int i =0;i < n;i++){System.out.print(chars[i]);}}}}}
演示结果如下:

## 二、输出流
说明:OutputStream 只是一个抽象类,要使用还需要具体的实现类。关于 OutputStream 的实现类有很多,基本可以认为不同的输出设备都可以对应一个 OutputStream 类,我们现在只关心写文件,所以使用 FileOutputStream。
### 2.1 OutputStream 概述:
- 方法:
修饰符及返回值类型方法签名说明voidwrite(int b)将指定的字节写入此输出流。voidwrite(byte[] b)将
b.length
字节从指定的字节数组写入此输出流。intwrite(byte[] b,int off,int len)从指定的字节数组写入
len
个字节,从偏移
off
开始输出到此输出流。voidclose()关闭此输出流并释放与此流相关联的任何系统资源。voidflush()刷新此输出流并强制任何缓冲的输出字节被写出。
解释一下
flush
:我们知道 I/O 的速度是很慢的,所以,大多的
OutputStream
为了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写入设备中,这个区域一般称为
缓冲区
。但造成一个结果,就是我们写的数据,很可能会遗留一部分在缓冲区中。需要在
最后
或者
合适
的位置,调用
flush
(刷新)操作,将数据刷到设备中。
- 构造方法:
构造方法说明FileOutputStream(String name)创建文件输出流,以指定的名称写入文件FileOutputStream(String name,boolean append)创建文件输出流,以指定的名称是否追加写入文件。
注意:在使用 FileOutputStream 创建指定文件对象的时候,如果不在文件名后面加上
true
,原有的文件内容会被清空,如果加上
true
就是在原文件内容上追加写。
> 案例如下:
> 
> 执行完
> ```
> try(OutputStream outputStream = new FileOutputStream("./text.txt"))
> ```
> 后变成:
> 
### 2.2 write 方法详解:

和 read 一样有三种版本,并且没有返回值,这里就只演示版本二(带byte 数组)。
- 版本二:一次 write 若干字节。会把参数数组里所有的字节都写入文件中。
代码案例如下:
publicclass demo5 {publicstaticvoidmain(String[] args)throwsIOException{try(OutputStream outputStream =newFileOutputStream("./text.txt")){byte[] bytes =newbyte[4];for(int i =0;i <4;i++){
bytes[i]=(byte)(97+ i);//对应是字符 abcd}
outputStream.write(bytes);
outputStream.flush();//不要忘记,flush 刷新缓存}}}
> 案例演示结果:
> 
### 2.3 利用 PrintWriter 进行写操作:
注意:
OutputStream
也有
close
操作,这里没有单独写,是因为使用和注意点都一样。
上述,我们其实已经完成输出工作,但总是有所不方便,我们接来下将 OutputStream 处理下,使用 PrintWriter 类来完成输出,因为 PrintWriter 类中提供了我们熟悉的 print/println/printf 方法。
案例演示如下:
publicclass demo6 {publicstaticvoidmain(String[] args)throwsIOException{try(OutputStream outputStream =newFileOutputStream("./text.txt")){try(PrintWriter printWriter =newPrintWriter(outputStream)){
printWriter.print("abcd");
printWriter.flush();}}}}
> 案例演示结果:
> 
还有一个对应的字符写类 Writer 使用的方法是一样的,就不再赘述了。
## 三、小程序练习
我们学会了文件的基本操作 + 文件内容读写操作,接下来,我们实现一些小工具程序,来锻炼我们的能力。
### 3.1 程序1:
- 程序要求:扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件。
步骤如下:
1. 提示用户输入
2. 找到名称包含指定字符的所有普通文件
3. 是否删除
源码如下(有详细注释):
importjava.io.;importjava.util.;//扫描指定⽬录,并找到名称中包含指定字符的所有普通⽂件(不包含⽬录),并且后续询问⽤⼾是否//要删除该⽂件publicclass demo1 {staticScanner in =newScanner(System.in);publicstaticvoidmain(String[] args){//1提示用户输入System.out.println("请输入要查找的文件目录:");String dirName = in.next();List<File> result =newArrayList<>();//2找到名称包含指定字符的所有普通文件File file =newFile(dirName);//判断输入是否合法if(!file.isDirectory()){System.out.println("输入的目录不存在");return;}System.out.println("请输入要查找的指定字符:");String searchWord = in.next();searchFile(file,searchWord,result);System.out.println("一共找到了"+ result.size()+"种结果");for(int i =0;i < result.size();i++){System.out.println((i +1)+"号:"+ result.get(i).getAbsolutePath());}List<Integer> list =newArrayList<>();//存储用来删除的下标System.out.println("请输入要删除第几号文件(输入0停止删除):");int y = in.nextInt();while(y !=0){
y--;if(!result.get(y).exists()){System.out.println("已经被删除过来");}else{
result.get(y).delete();System.out.println("删除成功");}
y = in.nextInt();}}//查找目录底下的所有文件publicstaticvoidsearchFile(File file,String searchWord,List<File> result){if(file ==null)return;//递归出口File[] files = file.listFiles();//返回目录底下的文件for(int i =0;i < files.length;i++){if(files[i].isDirectory()){//目录的情况searchFile(files[i],searchWord,result);}else{//走到这里说明是普通文件//找文件名字是否包含关键字if(searchWord(files[i],searchWord)){//包含关键字的情况
result.add(files[i]);}}}}//查找文件名是否包含关键字privatestaticbooleansearchWord(File file,String searchWord){String fileName = file.getName();return fileName.contains(searchWord);}}
> 程序运行结果如下:可以在电脑上单独创建一个文件夹来测试。
> 
### 3.2 程序2:
- 程序要求:进行普通文件的复制。
步骤如下:
1. 提示用户输入
2. 检查输入是否合法
3. 进行复制
源码如下:
importjava.util.;importjava.io.;//进⾏普通⽂件的复制publicclass demo1 {publicstaticvoidmain(String[] args)throwsIOException{Scanner in =newScanner(System.in);//1.提示读入System.out.println("请输入要复制的文件");String srcFileRoot = in.next();//检查输入是否合法File srcFile =newFile(srcFileRoot);if(!srcFile.isFile()){System.out.println("输入的不是普通文件路径");return;}//已经是文件了System.out.println("请输入要复制到的文件路径");String destFileRoot = in.next();File destFile =newFile(destFileRoot);//检查输入是否合法if(destFile.isDirectory()){System.out.println("输入的不是普通文件(路径)");return;}//进行复制copyFile(srcFile, destFile);System.out.println("复制完毕");}//复制文件publicstaticvoidcopyFile(File srcFile,File destFile)throwsIOException{try(InputStream inputStream =newFileInputStream(srcFile)){try(OutputStream outputStream =newFileOutputStream(destFile)){byte[] bytes =newbyte[1024];while(true){int n = inputStream.read(bytes);if(n ==-1)break;
outputStream.write(bytes,0, n);//起点和要复制的长度}
outputStream.flush();}}}}
> 程序运行结果如下:
> 
### 3.3 程序3:
- 程序要求:扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)。
步骤如下:
1. 提示读入数据
2. 检查输入是否合法
3. 查找指定文件
源码如下:
//案例三//目的:扫描指定⽬录,并找到内容中包含指定字符的所有普通⽂件(不包含⽬录)importjava.util.;importjava.io.;publicclass demo2 {publicstaticvoidmain(String[] args){Scanner in =newScanner(System.in);//提示读入数据System.out.println("请输入要搜索的目录:");String dirRoot = in.next();File dirFile =newFile(dirRoot);if(!dirFile.isDirectory()){System.out.println("目录输入错误");return;}System.out.println("请输入要查找的指定内容");String serWord = in.next();//查找指定文件searchFile(dirFile,serWord);}privatestaticvoidsearchFile(File dirFile,String serWord){File[] files = dirFile.listFiles();//这里不用判断为空for(int i =0;i < files.length;i++){if(files[i].isFile()){//查找指定内容searchWord(files[i],serWord);}elseif(files[i].isDirectory()){//递归进行查找searchFile(files[i],serWord);}}}privatestaticvoidsearchWord(File file,String serWord){//查找文件内容StringBuilder sd =newStringBuilder();try(InputStream inputStream =newFileInputStream(file)){//读取数据while(true){int n = inputStream.read();if(n ==-1)break;
sd.append((char)n);}if(sd.indexOf(serWord)>=0){//找到了System.out.println("文件路径为:"+ file.getAbsolutePath());}}catch(IOException e){thrownewRuntimeException(e);}}}
```
程序运行结果如下:
结语:
其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。
版权归原作者 gobeyye 所有, 如有侵权,请联系我们删除。

