> 文章列表 > IO详解(文件,流对象,一些练习)

IO详解(文件,流对象,一些练习)

IO详解(文件,流对象,一些练习)

目录

文件

文件概念

文件的路径

路径有俩种表示风格 

文件类型

如何区分文本文件还是二进制文件?

java对文件的操作

File类中的一些方法

流对象

流对象的简单概念

java标准库的流对象

1.字节流,(操作二进制数据的)

2.字符流 (操作文本数据的)

流对象最核心的四个操作

注意,intput 和 output的方向

读文件(字节流)

read无参数版本

 read一个参数版本

 理解read的行为和返回值

写文件

关于close

如何确保close被执行到

字符流

读操作

 写操作

 关于Scanner

练习

用户输入一个查询的词,看看当前目录下(以及子目录里)是否有匹配的结果,如果有匹配结果,就询问用户是否删除

把一个文件拷贝成另一个文件(把第一个文件按照字节读取,把结果写入到另一个文件中) 


文件

文件概念

狭义的文件:指的是硬盘上的 文件目录(文件夹)

广义的文件:泛指计算机中的很多 软硬件资源
操作系统中,把很多的硬件设备和软件资源抽象成了文件,按照文件的方式来统一管理.
例如:在网络编程中的网卡,操作系统是把网卡当作一个文件来进行操作的

文件的路径

之前学习的代码中,存储数据,主要靠变量,变量是在内存中的
现在学习的文件,则是在硬盘上

每个文件,在硬盘上都有一个具体的路径

路径有俩种表示风格 

1.绝对路径:以c:d:盘符开头的路径

2.相对路径:以当前所在目录为基准,以.或..开头(.有时可以省略),找到指定路径

当前所在目录:称为工作目录,每个程序运行的时候,都有一个工作目录(在控制台里通过命令操作较为明显)

假定,当前的工作目录是,d:/tmp

tmp目录下有111这个目录,定位到111这个目录,就可以表示成
./111(./就表示的是当前的目录 d:/tmp)

../表示当前目录的上级目录,如果工作目录是d:/tmp/222 想要定位到111这个目录,相对路径写作.
../111(..表示当前目录d:/tmp/222 的上级目录 d:/tmp)

文件类型

如何区分文本文件还是二进制文件?

直接使用 记事本 打开,如果乱码了,说明是二进制文件,如果没乱,就是文本文件

java对文件的操作

1.针对文件系统操作(文件的创建,删除,重命名)

2.针对文件内容操作(文件读和写)

java标准库提供了File这个类

在new File 对象的时候,构造方法参数中,可以指定一个路径.此时File对象代表这个路径对应的文件

File类中的一些方法

方法

 

 代码

  public static void main(String[] args) throws IOException {File file = new File("./test.txt");System.out.println(file.getName());System.out.println(file.getParent());System.out.println(file.getAbsoluteFile());System.out.println(file.getPath());System.out.println(file.getCanonicalFile());}

运行结果

方法 

 

 代码

public static void main(String[] args) throws IOException {File file = new File("D:/test.txt");file.createNewFile();System.out.println(file.exists());System.out.println(file.isFile());System.out.println(file.isDirectory());}

运行结果

 方法

代码

public static void main(String[] args) throws IOException {File file = new File("D:/test.txt");System.out.println(file.exists());file.createNewFile();System.out.println(file.exists());file.delete();System.out.println(file.exists());}

 运行结果

 方法

代码

 public static void main(String[] args) {File file = new File("./test");file.mkdir();}

 

public static void main(String[] args) {File file = new File("./test/aaa/bbb");file.mkdirs();}

运行结果

 

流对象

流对象的简单概念

针对文件内容,使用"流对象" 进行操作,从文件中读100个字节,我们可以一次读100个字节,一次读完
也可以一次读20个字节,5次读完,我们可以随心所欲的读

java标准库的流对象

从类型上分为俩大类

1.字节流,(操作二进制数据的)

InputStream FileInputStream

OutputStream FileOutputStream

2.字符流 (操作文本数据的)

Reader FileReader

Writer FileWriter

流对象最核心的四个操作

这些类的使用方式是固定的,核心就是四个操作

1.打开文件(构造对象)
2.读写文件(read) ==>针对的是  InputStream/Reader
3.写文件(writer) ==> 针对OutputStream/Writer
4.关闭文件(close)

注意,intput 和 output的方向

我们是以CPU为中心,来看待这个方向的
数据朝着CPU的方向流向,就是输入,所以把从硬盘中读取数据到内存 这个过程称为读 input
数据远离CPU的方向流向,就是输入,所以把从内存中写数据到硬盘 这个过程称为写 output

读文件(字节流)

read无参数版本

代码

public static void main(String[] args) throws IOException {
//创建 InputStream 对象的时候,使用绝对路径或者相对路径,都可以,也可以使用File对象InputStream inputStream = new FileInputStream("D:/test.txt");
//进行读操作while (true){int b = inputStream.read();if(b == -1){break;}System.out.println(""+(byte)b);}inputStream.close();
}

 

 

运行结果

D盘中test的文件内容

 

 

 read一个参数版本

   public static void main(String[] args) throws IOException {//创建 InputStream 对象的时候,使用绝对路径或者相对路径,都可以,也可以使用File对象InputStream inputStream = new FileInputStream("D:/test.txt");while(true){byte[] buffer = new byte[1024];int len = inputStream.read(buffer);System.out.println("len "+len);if(len == -1){break;}for (int i = 0; i < len; i++) {System.out.println(""+buffer[i]);}}inputStream.close();}

运行结果

 

 理解read的行为和返回值

上面这里给的数组长度是1024,read就会尽可能读取1024个字节,填到数组里.但实际上,文件剩余长度是有限的,如果剩余长度超过1024,此时1024个字节就会填满,返回值是1024,如果当前剩余的长度不足1024,此时有多少就填多少,read方法就会返回当前实际读取的长度

第二个版本的代码有什么好处?

buffer存在的意义,是为了提高IO效率,单次IO操作,是要访问硬盘IO设备,单次操作是比较消耗时间的
如果频繁进行这样的IO操作,耗时比较大

单次IO时间是一定的,如果能缩短IO次数,此时就可以提高程序的整体效率了
第一个版本的代码,是一次读取一个字节,循环次数比较高,read次数很高,读取IO次数也很高
第二个版本的代码,是一次读取1024个字节,循环次数降低了很多,read次数变少了

写文件

代码

   public static void main(String[] args) throws IOException {OutputStream outputStream = new FileOutputStream("D:/test.txt")outputStream.write(97);outputStream.write(98);outputStream.write(99);outputStream.write(100);outputStream.close();}

运行结果

对于OutputStream 来说,默认情况下,打开一个文件,会先清空文件原有的内容(这样,之前的"hello"就被清空了)
如果不想清空,流对象还提供了一个"追加写"对象,通过这个就可以实现不清空文件,把新内容追加到后面.

关于close

对于上述代码中的outputStream.close();这里的close操作,含义是关闭文件
一个线程对于一个PCB,一个进程对应1个或多个PCB
PCB有一个重要的属性,文件描述符表(相当于一个数组),记录了该进程打开了哪些文件.(即使一个进程有多个线程多个PCB也没关系,这些PCB共用一个文件描述符表)

如果没有close,对应的表项,没有及时释放.虽然 java有GC,GC操作会在回收这个outputStream对象的时候完成释放操作,但是这个GC不一定及时...
所以,如果不能手动释放,意味着文件描述符表可能很快就被占满了(文件描述符表这个数组,不能自动扩容,有上限)
如果占满了后,再次打开文件,就会打开失败

 close 一般来说是要执行的,但是如果一个程序,有一些文件对象自始至终都要使用,也可以不用关闭
随着进程结束,PCB销毁了,文件描述符表也就销毁了,对应的资源操作系统就自动回收
了,因此,如果一个文件close之后,程序就立即结束了,此时也可以省略close

有时我们可能会发现,写文件的内容没有真正在文件中出现,很大可能是因为缓存区,
写操作其实是,先写到缓冲区里(缓冲区有很多种形态,自己写的代码里可以有缓冲区,标准库里也可以有缓冲区,操作系统内核里也可以有缓冲区)
写操作执行完了,内容可能在缓冲区,还没有真正进入硬盘,close操作 就会触发缓冲区的刷新(刷新操作就是把缓冲区的内容写到硬盘里)
除了close之外,还可以通过flush方法刷新缓冲区(此时文件不会立即关闭)

如何确保close被执行到

 刚刚的代码可以改成这样

 public static void main(String[] args) throws IOException {try(OutputStream outputStream = new FileOutputStream("D:/test.txt")) {outputStream.write(97);outputStream.write(98);outputStream.write(99);outputStream.write(100);}}

这是更推荐的写法,这个写法虽然没有显示的写 close,实际上会执行的,只要try语句块执行完毕,就可以自动执行到close
这个语法,在java中被称为try with resources ,当然不是随便拿一个对象放在try()里就能自动释放,必须要这个对象实现了Closeable接口

实现了这个Closeable接口的类才可以放到try()中被自动关闭,这个接口提供的方法就是close方法

字符流

读操作

 代码

public static void main(String[] args) throws IOException {try(Reader reader = new FileReader("D:/test.txt")){while(true){int ch = reader.read();if(ch == -1){break;}System.out.println((char)ch+"");}}}

运行结果 

 写操作

 public static void main(String[] args) throws IOException {try(Writer writer = new FileWriter("D:/test.txt")){writer.write("hello world");}}

运行结果

 关于Scanner

Scanner是搭配流对象来使用的

代码

public static void main(String[] args) throws IOException {try(InputStream inputStream = new FileInputStream("D:test.txt")) {//此时读取的内容就是从 文件 中读取了Scanner scanner = new Scanner(inputStream);scanner.next();}}

练习

用户输入一个查询的词,看看当前目录下(以及子目录里)是否有匹配的结果,如果有匹配结果,就询问用户是否删除

代码

import java.io.File;
import java.util.Scanner;public class IODemo10 {static Scanner scanner = new Scanner(System.in);public static void main(String[] args) {//让用户搜索一个指定搜索的目录System.out.println("请输入要搜索的路径: ");String basePath = scanner.next();//针对用户输入进行简单判定File root = new File(basePath);if(!root.isDirectory()){//路径不存在,或者只是一个普通的文件,此时无法进行搜索System.out.println("输入的目录有误!");return;}//再让用户输入一个要删除的文件名System.out.println("请输入要删除的文件");//此处要使用next,而不能使用nextLineString nameToDelete = scanner.next();//针对指定的路径进行扫描,递归操作//先从根目录出发//判定一下,当前的这个目录里,是否包含我们所需要删除的目录.如果是则删除,否则跳过下一个//如果当前目录里包含一些目录,再针对子目录进行递归scanDir(root,nameToDelete);}private static void scanDir(File root, String nameToDelete) {//1.先列出 root 下的文件和目录File[] files = root.listFiles();if(files == null){//当前root 目录下没有东西,是一个空目录//结束继续递归return;}//2.遍历当前列出的结果for(File x : files){if(x.isDirectory()){//如果是目录,就进一步递归scanDir(x,nameToDelete);}else{//如果是普通文件,则判定是否要删除if(x.getName().contains(nameToDelete)){System.out.println("确实是否要删除: "+x.getAbsolutePath()+"嘛");String choice = scanner.next();if(choice.equals("y")||choice.equals("Y")){x.delete();System.out.println("删除成功");}else{System.out.println("删除取消");}}}}}
}

 运行结果

把一个文件拷贝成另一个文件(把第一个文件按照字节读取,把结果写入到另一个文件中) 

代码

import java.io.*;
import java.util.Scanner;public class IODemo11 {public static void main(String[] args) throws IOException {//输入俩个路径//源 和 目标(从哪里,拷贝到哪里)Scanner scanner = new Scanner(System.in);System.out.println("请输入要拷贝哪个文件: ");String srcPath = scanner.next();System.out.println("请输入要拷贝到哪个地方: ");String destPath = scanner.next();File srcFile = new File(srcPath);if(!srcFile.isFile()){//如果源路径不是一个文件(是一个目录,或者不存在)//此时不做任何操作System.out.println("输入的源路径有误");return;}File destFile = new File(destPath);if(destFile.isFile()){//如果目标路径已经存在,认为不能拷贝System.out.println("当前输入的目标路径有误");return;}//进行拷贝操作try(InputStream inputStream = new FileInputStream(srcFile);OutputStream outputStream = new FileOutputStream(destFile)){//进行读文件操作while(true){int b = inputStream.read();if(b == -1){break;}//进行写操作outputStream.write(b);}}}
}