文件和IO

文件和IO

文件概述

文件系统是系统内核的一部分,文件分为普通文件,目录文件和其他等等文件,普通文件就是我们常见的xxx.jpg,xxx.txt。目录文件就是文件夹

文件是存储在硬盘上的,标识文件在电脑上的位置用的是文件路径(相对路径和绝对路径),绝对路径是从盘符开始的,相对路径有一个基准,相对于基准的位置

1️⃣绝对路径:D:\program Files\qq\qq.exe(从盘符开始,经过目录(目录就是文件夹)到文件名结束),中间用\分隔,也可以用/分隔

2️⃣相对路径:以.或…开头,从一个基准路径(工作目录)开始,经过目录,到文件名。

文件也是操作系统管理的,在系统内核中有一个专门用来管理文件的部分,叫文件系统,而java针对文件系统进行了封装。

操作文件

定义文件和查看路径

在java中操作文件用的是File类,下面来看具体代码

package file;

import java.io.File;
import java.io.IOException;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        //通过路径定义一个文件
        File file = new File("d:/test.txt");
        System.out.println(file.getParent()); //获取上一级路径
        System.out.println(file.getName());//获取文件名
        System.out.println(file.getPath());//构造方法用的获取路径
        System.out.println(file.getAbsolutePath());//获取绝对路径
        System.out.println(file.getCanonicalPath());//获取整理过的路径
    }
}

image-20220827204635605

✅注意:这里File没有真正在硬盘上创建一个文件,只是先定义了一个文件,下面使用相对路径定义一个文件

package file;

import java.io.File;
import java.io.IOException;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        //通过路径定义一个文件
        //File file = new File("d:/test.txt");
        File file = new File("./test.txt");
        System.out.println(file.getParent()); //获取上一级路径
        System.out.println(file.getName());//获取文件名
        System.out.println(file.getPath());//构造方法用的获取路径
        System.out.println(file.getAbsolutePath());//获取绝对路径
        System.out.println(file.getCanonicalPath());//获取整理过的路径
    }
}

image-20220827205229319

相对路径的基准路径就是下面这个路径(工作目录):

image-20220827205520120

✅使用相对路径创建文件时,三种路径就有区别了

是否存在,是否是目录或文件

package file;

import java.io.File;
import java.io.IOException;

public class Demo2 {
    public static void main(String[] args) throws IOException {
        File file = new File("test.txt");
        System.out.println(file.exists()); //文件是否存在
        System.out.println(file.isDirectory()); //是不是目录
        System.out.println(file.isFile()); //是不是文件
        System.out.println("=====================");
        file.createNewFile(); //真正创建文件
        System.out.println(file.exists());
        System.out.println(file.isDirectory());
        System.out.println(file.isFile());
    }
}

image-20220827210723801

✅这次我们用file.createNewFile()真正的创建了文件,之前的File只是定义了一个文件,当真正创建了一个文件后,可见左侧目录里就有了一个test.txt

文件创建和删除

package file;

import java.io.File;
import java.io.IOException;

public class Demo4 {
    public static void main(String[] args) throws IOException {
        File file = new File("test.txt");
        file.createNewFile();

        file.delete();//删除文件

        file.deleteOnExit();//程序退出时删除

    }
}

✅file.delete()是立即删除,file.deleteOnExit()是等到程序退出时才删除。

文件夹的创建

package file;

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;

import java.io.File;

public class Demo3 {
    public static void main(String[] args) {
        File file = new File("test");
        System.out.println(file.exists());
        System.out.println(file.isDirectory());
        System.out.println("===========");
        file.mkdir();
        System.out.println(file.exists());
        System.out.println(file.isDirectory());
    }
}

image-20220827233558202

创建多级目录

package file;

import java.io.File;

public class Demo3 {
    public static void main(String[] args) {
        File file = new File("test/a/b");
        System.out.println(file.exists());
        System.out.println(file.isDirectory());
        System.out.println("===========");
        file.mkdirs();
        System.out.println(file.exists());
        System.out.println(file.isDirectory());
    }

}

image-20220827233939163

文件重命名

package file;

import java.io.File;
import java.io.IOException;

public class Demo6 {
    public static void main(String[] args) throws IOException {
        File file1 = new File("./test1.txt");
        File file2 = new File("./test2.txt");
        file1.createNewFile();
        file1.renameTo(file2);
    }
}

✅把test1.txt重命名为test2.txt

读写文件

读写文件步骤

读写文件前需要先打开文件,结束后还得关闭文件

  1. 打开文件 ,==打开文件其实就是打开文件流
  2. 读/写文件
  3. 关闭文件

java中关于文件读写,提供了两组类:

第一组:InputStream OutputStream (基于字节流读写数据) (字节流用来操作二进制文件)

第二组:Reader Writer (基于字符流读写数据)(字符流用来操作文本文件)

==字节流:==以字节为单位读写数据,把二进制数据不经转化直接写出或读入,

字符流:以字符为单位读写数据,把二进制数据转化为字符对应的编码再写出或读入

文件打开不关闭的影响

文件的关闭会释放资源,而打开不关闭就会浪费资源,这个资源主要是什么呢?

这个资源主要是文件描述符表,之前在学进程的时候,我们知道一个线程对应一个PCB,PCB中就有文件描述符表,描述了当前线程打开的文件信息,而文件描述符表其实就是一个数组,也就是用数组来组织了每个文件的信息,数组的下标就是文件描述符。

==每打开一个文件,就会占用一个数组空间,而每关闭一个文件,就释放一个数组空间,==但是要注意,这个数组是有固定容量的,而且系统中也没有设计数组满了要扩容,所以如果代码中频繁打开文件,但是不关闭,数组就会满,再次尝试打开文件就会报错。

读文件

使用InputStream读文本文件
package file;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Demo7 {
    public static void main(String[] args) throws IOException {
        File file = new File("test.txt");
        //创建文件
        file.createNewFile();
        //打开文件
        InputStream inputStream = new FileInputStream("test.txt");
        //读文件
        int ret = inputStream.read();
        //关闭文件
        inputStream.close();
    }
}

✅使用InputStream创建一个字节流即是打开了文件,InputStream(代表输入流)是一个抽象类,使用其子类FileInputStream(代表文件输入流)创建一个实例,构造方法传入文件路径,代表打开哪个文件

✅使用read方法读取数据,read方法有三个版本:

image-20220828161020666

1️⃣不带参数版本:读取一个字节的数据,通过返回值接收,返回类型是int,代表用int类型变量接收读取到的一个字节的数据。如果读到文件尾(EOF),返回-1

2️⃣带一个参数版本:参数是一个字节数组,把读到的数据放到这个字节数组中,所以这个参数是一个输出型参数,返回值代表读到了多少个字节的数据

3️⃣带三个参数的版本:和带一个参数的版本类似,不同点是从b[off]这个位置放数据,最多放len个字节的数据

下面写代码来看一下具体使用:

package file;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Demo7 {
    public static void main(String[] args) throws IOException {
        //File file = new File("test.txt");
        //创建文件
        //file.createNewFile();
        //打开文件
        InputStream inputStream = new FileInputStream("test.txt");
        //读文件
        byte[] b = new byte[1024];
        int len = inputStream.read(b);
        for (int i = 0; i < len; i++) {
            System.out.println(b[i]);
        }
        System.out.println(len);
        //关闭文件
        inputStream.close();
    }
}

test.txt文件中的内容:这是一个文本文件,使用的编码格式是utf-8编码

image-20220828183659913

  • 把test.txt中的数据一个字节一个字节的读取放到byte数组中,然后打印取出的字节

image-20220828184048329

104对应h,101对应e,108对应l等等,以utf8编码存储字符,一个英文字符占用一个字节,所以每取出一个字节正好对应了一个英文字符,也就是byte数组每个下标正好对应上了一个字符,但要是存的是汉字就不一样了:


image-20220828184526073

比如存储的是“你好”,中文汉字以utf8存储的时候,占用三个字节的空间,然后用read一个字节一个字节取出来,每个汉字就会占用三个字节的空间:

image-20220828184751223

-28,-67,-96这三个字节组合起来才能对应上一个汉字,所以可以明显看到以字节流读取文本文件并不直观,所以可以使用字符流读取文本文件,因为文本文件本身就是按字符编码存储数据的,一个字符一个字符读取,然后存储到字符数组中,再打印,就直接可以打印出字符了

使用Reader读取文本文件
package file;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class Demo8 {
    public static void main(String[] args) throws IOException {
        Reader reader = new FileReader("test.txt");
        char[] b = new char[1024];
        int len = reader.read(b);
        for (int i = 0; i < len; i++) {
            System.out.println(b[i]);
        }
        reader.close();
    }
}

image-20220828191120724

一个字符一个字符读出来,放到字符数组中,再打印就能直观的显示出来了,读取汉字也是一样:

image-20220828191433515

使用Scanner读取文本文件
package file;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class Demo9 {
    public static void main(String[] args) throws IOException {
        //打开文件流
        InputStream inputStream = new FileInputStream("test.txt");
        Scanner scanner = new Scanner(inputStream);
        String str = scanner.next();
        System.out.println(str);
        inputStream.close();
    }
}

我们之前使用Scanner都是从键盘(标准输入流)读取数据,构造方法传入的是System.in,代表从标准输入流读取数据,而传入InputStream就代表从文件流读入数据,这种写法更简单。这里打开文件流得自己手动打开,而打开标准输入流是程序启动默认打开的。

出现异常导致文件未关闭

package file;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Demo7 {
    public static void main(String[] args) throws IOException {
        //File file = new File("test.txt");
        //创建文件
        //file.createNewFile();
        //打开文件
        InputStream inputStream = new FileInputStream("test.txt");
        //读文件
        byte[] b = new byte[1024];
        int len = inputStream.read(b);
        for (int i = 0; i < len; i++) {
            System.out.println(b[i]);
        }
        System.out.println(len);
        //关闭文件
        inputStream.close();
    }
}

上面代码中打开文件或者读取数据都会抛异常,一旦出现异常就有可能导致close()执行不到,出现文件打开了但没有关闭这种情况。

❤️解决办法就是使用try,catch处理异常,然后把close()方法放到finally代码块中,这样就算出异常了,close()也是肯定能执行到的

package file;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Demo7 {
    public static void main(String[] args) {
        InputStream inputStream = null;
        try {
            //打开文件
            inputStream = new FileInputStream("test.txt");
            //读文件
            byte[] b = new byte[1024];
            int len = inputStream.read(b);
            for (int i = 0; i < len; i++) {
                System.out.println(b[i]);
            }
            System.out.println(len);
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            //关闭文件
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

这样的写法就能解决可能关闭不了文件的问题了 ,然后在finally代码块中还得处理一下异常,这样上面一个try catch语句,下面一个try catch语句,比较繁琐,改进办法就是使用try with resources

package file;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Demo7 {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("test.txt")){
            //打开文件
            //读文件
            byte[] b = new byte[1024];
            int len = inputStream.read(b);
            for (int i = 0; i < len; i++) {
                System.out.println(b[i]);
            }
            System.out.println(len);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

上面是具体写法,这种写法在try执行结束后就会自动执行inputStream.close()语句,前提是InputStream实现了Closeable接口,才能这么做

写文件

使用OutputStream写文件
package file;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Demo10 {
    public static void main(String[] args) {
        try(OutputStream outputStream = new FileOutputStream("test.txt")){
            outputStream.write('a');
        }catch (IOException e){
            e.printStackTrace();
        }

    }
}

✅写文件用的是write()方法,用法和读文件时的read()方法相似

image-20220828224956434

使用Writer写文件
package file;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class Demo11 {
    public static void main(String[] args) {
        try (Writer writer = new FileWriter("test.txt")){
            writer.write("hello_world");
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

write()的几个版本:

image-20220828225545890

可以传入一个字符,或者一个字符串,或者一个字符数组

✅注意:每次写文件前都会把文件中原本的内容清除再写文件。

借助PrintStream写文件
package file;

import java.io.*;

public class Demo11 {
    public static void main(String[] args) {
        try (OutputStream outputStream = new FileOutputStream("test.txt")){
            PrintStream printStream = new PrintStream(outputStream);
            printStream.println("hello");
            printStream.println("world");
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

写文件除了使用OutputStream,Writer也可以使用PrintStream,构造方法里传入outputStream,也可以写文件。

文件操作案例

案例1:查找并删除文件

案例1是遍历删除文件,先让用户输入一个路径,遍历这个路径下的所有文件,让用户决定是否删除

package file;

import java.io.File;
import java.io.IOException;
import java.util.Scanner;

public class Demo12 {
    public static void main(String[] args) throws IOException {
        System.out.println("请输入一个路径");
        Scanner scanner = new Scanner(System.in);
        String path = scanner.next();
        File file = new File(path);
        if (!file.exists()){
            System.out.println("不存在:"+file.getCanonicalPath());
            return;
        }
        System.out.println("请输入该路径下的待删除文件名或文件名的一部分");
        String toDelete = scanner.next();
        scanDelete(file,toDelete);
    }
    //把该路径下的所有文件尝试删除
    public static void scanDelete(File file,String toDelete){
        File[] files = file.listFiles();
        if (files==null){
            return;
        }
        for (File x:files) {
            if(x.isDirectory()){
                scanDelete(x,toDelete);
            }
            if (x.isFile()){
                tryDelete(x,toDelete);
            }
        }
    }
    public static void tryDelete(File file,String toDelete){
        String fileName = file.getName();
        if (fileName.contains(toDelete)){
            try {
                System.out.println("是否要删除该文件?(Y/N)"+file.getCanonicalPath());
                Scanner scanner = new Scanner(System.in);
                String ret = scanner.next();
                if(ret.equals("Y")){
                    file.delete();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

✅获取一个存在的路径之后,用递归的方式遍历该路径下的所有文件,找到文件然后再确定是否删除

案例2:复制文件

package file;

import java.io.*;
import java.util.Scanner;

//拷贝普通文件
public class Demo13 {
    //复制普通文件
    public static void main(String[] args) {
        System.out.println("请输入待拷贝文件路径");
        Scanner scanner = new Scanner(System.in);
        String srcPath = scanner.next();
        File srcFile = new File(srcPath);
        if (!srcFile.exists()){
            System.out.println("待拷贝文件不存在");
            return;
        }
        if (!srcFile.isFile()){
            System.out.println("待拷贝文件不是普通文件");
            return;
        }
        System.out.println("请输入文件拷贝路径");
        String destPath = scanner.next();
        File destFile = new File(destPath);
        if (destFile.exists()){
            System.out.println("文件拷贝路径已经存在,不可拷贝");
            return;
        }
        try {
            destFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //拷贝文件
        copyFile(srcFile,destFile);
    }
    public static void copyFile(File srcFile,File destFile){
        try (InputStream inputStream = new FileInputStream(srcFile)){
            try (OutputStream outputStream = new FileOutputStream(destFile)){
                //缓冲区
                byte[] buf = new byte[1024];
                while(true){
                    //从文件读数据到缓冲区
                    int len = inputStream.read(buf);
                    //返回值为-1代表读到了EOF
                    if(len==-1){
                        break;
                    }
                    //从缓冲区取数据到文件
                    outputStream.write(buf,0,len);
                }
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

✅先把读取到的数据放到缓冲区里,然后再把缓冲区中的数据写到文件中,循环进行,直到遇到文件尾

❤️解释一下缓冲区:缓冲区其实就是内存中的一块空间,比如一个程序要写数据到文件,如果直接把CPU中的数据写到硬盘上,这是很慢的,因为硬盘的读写速度比CPU慢好几个数量级,就会造成CPU长时间等待IO,无法继续执行其他程序,而如果先把数据写到内存中,后续内存中的数据在存入硬盘上这就比较快了,因为内存读写速度比磁盘快好几个数量级,所以写到内存中比直接写到硬盘上要快的多,写到内存上CPU就可以接着运行其他程序了,大大提高了程序的执行效率。

案例3:根据文件名或内容查找文件

package file;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class Demo14 {
    public static void main(String[] args) {
        System.out.println("请输入待搜索的目录");
        Scanner scanner = new Scanner(System.in);
        String path = scanner.next();
        File file = new File(path);
        if (!file.exists()){
            System.out.println("该目录不存在");
            return;
        }
        System.out.println("请输入文件名或内容关键字");
        String find = scanner.next();
        scanFile(file,find);
    }

    private static void scanFile(File file, String find) {
        File[] files = file.listFiles();
        if(files == null){
            return;
        }
        for (File x:files) {
            if(x.isDirectory()){
                scanFile(x,find);
            }
            if (x.isFile()){
                tryShow(x,find);
            }
        }
    }

    private static void tryShow(File x, String find)  {
        if (x.getName().contains(find)){
            try {
                System.out.println("文件名匹配,路径:"+x.getCanonicalPath());
            } catch (IOException e) {
                e.printStackTrace();
            }
            return;
        }
        StringBuilder stringBuilder = new StringBuilder();
        try (InputStream inputStream = new FileInputStream(x)){
            Scanner scanner = new Scanner(inputStream);
            while (scanner.hasNextLine()){
                stringBuilder.append(scanner.nextLine());
            }
            if (stringBuilder.indexOf(find) >=0){
                System.out.println("文件内容匹配,路径:"+x.getCanonicalPath());
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

✅递归遍历目录,找到文件,先判断文件名是否匹配,再判断文件内容是否匹配

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值