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

文章详细介绍了Java中对文件的操作,包括文件的概念、路径表示、文件类型识别、Java的File类及其方法,如创建、删除、重命名文件。重点讲解了流对象,包括字节流和字符流的读写操作,以及如何确保close方法执行以释放资源。此外,还涉及Scanner类的使用以及文件的复制操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

文件

文件概念

文件的路径

路径有俩种表示风格 

文件类型

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

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这个类

parent表示当前文件所在目录,child表示自身文件名,比如d:/cat.jpg parent就是d:/ child是 cat.jpg 

在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,而不能使用nextLine
        String 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);
            }
        }
    }
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值