Java EE 文件和IO


提示:以下是本篇文章正文内容,下面案例可供参考

一、认识文件

平时说的文件一般都是指存储在硬盘上的普通文件
比如txt,jpg,mp4,rar等,这些文件都可以认为是普通文件,它们都是在硬盘上存储的

在计算机中,文件可能是一个广义的概念,就不只是包含普通文件,还可以包含目录(把目录称为目录文件)

在操作系统中,还会使用文件来描述一些其他硬件设备或软件资源。
比如网卡(硬件),但是操作系统中就把网卡这样的硬件也给抽象成了一个文件——简化开发
比如显示器/硬盘,操作系统也是认为是文件

ps:当前讨论的文件主要还是针对普通文件,后续学习一些其他的硬件设备对应的文件,仍然是通过类似的代码操作的。

关于硬盘的一些小知识:
在这里插入图片描述

1.1树型结构组织和目录

计算机中,保存管理文件,是通过操作系统中“文件系统”这样的模块来负责的。

在文件系统中,一般是通过“树型”结构来组织磁盘上的目录和文件的
注意:这里的树不是二叉的,是N叉的
在这里插入图片描述
整体的文件系统,就是这种树形结构,
如果是一个普通文件,就是树的叶子结点
如果是一个目录文件,目录中就可以包含子树,这个目录就是非叶子结点
这个树上的每个节点上的子树都可以有N个,这就是一个N叉树了

1.2文件路径(重要)

操作系统中,就通过“路径”这样的概念,来描述某个具体文件/目录的位置。
路径这里有两种描述风格:
1.绝对路径以盘符开头的
比如E:\java-ee\text.txt 这种
2.相对路径以“.”或“. .”开头的,其中“.”表示当前路径,“. .”表示当前路径的父目录(上级路径)

而谈到相对路径,必须有一个基准目录,相对路径就是从基准目录出发,按照一个啥样的路径找到对应的文件。
在这里插入图片描述

举例说明:
如下是我家到一个餐馆的路线图
在这里插入图片描述

绝对路径:我从我家到这个餐馆路线——先向西走,再向北走,再向西走,再向北走

相对路径:比如我现在正在路上的某个点,不在我家,如图:
在这里插入图片描述
那么我现在去餐馆,只要向西,再向北就可以了。

总结:
绝对路径——无论我现在在哪个位置,绝对路径都是从起点到目标
相对路径——从我当前所处位置,到目标位置

1.3文本文件和二进制文件

站在我们程序员角度,文件主要有两类:

1.文本文件
里面存储的是字符,文本文件本质上也是存字节的,但是文本文件中,相邻的字节在一起恰好能构成一个个字符

2.二进制文件
里面存储的是字节,字节与字节之间没有联系

快速判断一个文件是文本文件还是二进制文件方法:
用记事本打开,如果打开后是乱码,就是二进制文件;不是乱码,就是文本文件。
在这里插入图片描述
类似的,像日常中使用的,
.txt, .c, .java都属于文本文件
.doc,.ppt,.exe,.zip,.class等等都属于二进制文件

ps:word、excel这种office系列的文件都是二进制的
因为这种软件保存的不单单是文本,而是“富文本”,文本中有各种格式化信息(字体大小,颜色等等)

二、java中操作文件

java中操作文件,主要是包含两类操作
1.文件系统相关操作:
指的是通过“文件资源管理器”,能够完成的一些功能
1)列出目录中有哪些文件
2)创建文件
3)创建目录
4)删除文件
5)重命名文件

2.文件内容相关操作:

2.1File概述

我们在java中提供了一个File类,通过这个类来描述上述操作。首先,这个File类就描述了一个文件/目录,基于这个对象就可以实现上面的功能。
在这里插入图片描述

2.1.1构造方法

File的构造方法,能够传入一个路径,来指定一个文件,这个路径可以是绝对路径,也可以是相对路径。

构造好方法后,就可以通过这个方法,来完成一些具体的功能了。

在这里插入图片描述

2.1.2方法

在这里插入图片描述
(图片来自比特就业课)

路径和绝对路径的相关方法:
在这里插入图片描述

代码示例如下:

public static void main(String[] args) throws IOException {
        File f=new File("d:/input.txt");//绝对路径
        System.out.println(f.getParent());//获取文件父目录
        System.out.println(f.getName());//获取文件名
        System.out.println(f.getPath());//获取文件路径
        System.out.println(f.getAbsoluteFile());//获取绝对路径
        System.out.println(f.getCanonicalPath());//这也是获取绝对路径,
        //但是这里要抛一个异常,IOException表示输入输出可能会有问题

        System.out.println("========分割线========");

        File f2=new File("./input.txt");//相对路径

        //谈到相对路径,一定得先明确基准路径
        //上述代码中是看不出基准路径是哪个的

        //——基准路径是由运行java程序的方式来确定的
        //1.如果通过idea方式,那么基准路径就是当前java项目的路径
        //2.如果通过命令行的方式,此时执行命令所在的目录,就是基准路径(实际中几乎用不掉2这种情况)
        //3.后面会学到,把一个java代码打成war包,放到tomcat上运行,这种基准路径就是tomcat的bin目录
        System.out.println(f2.getParent());//获取文件父目录
        System.out.println(f2.getName());//获取文件名
        System.out.println(f2.getPath());//获取文件路径
        System.out.println(f2.getAbsoluteFile());//获取绝对路径
        System.out.println(f2.getCanonicalPath());//这也是获取绝对路径

    }

运行结果如下:
在这里插入图片描述
分析:
在这里插入图片描述
判断文件的相关方法:
在这里插入图片描述

public static void main(String[] args) {
        File f=new File("d:/input.txt");//d盘中有这样一个文件
        System.out.println(f.exists());//判断文件是否存在——打印true
        System.out.println(f.isDirectory());//是不是目录——打印false
        System.out.println(f.isFile());//是不是文件——打印true

        System.out.println("======分割线======");

        File f2=new File("./input.txt");//当前java项目目录下没有这样一个文件
        System.out.println(f2.exists());//判断文件是否存在——打印false
        System.out.println(f2.isDirectory());//是不是目录——打印false
        System.out.println(f2.isFile());//是不是文件——打印false
    }

运行结果如下:
在这里插入图片描述
解释如下:
在这里插入图片描述
文件的创建与删除的方法:
在这里插入图片描述

创建

public static void main(String[] args) throws IOException {
        //文件的创建与删除
        File f=new File("./test.txt");
        System.out.println(f.exists());

        System.out.println("创建文件开始");
        f.createNewFile();
        System.out.println("创建文件结束");

        System.out.println(f.exists());
    }

一开始没有这个文件,所以你exist判断的时候,会打印false。后面创建了这个文件,你exist再判断就是true了
在这里插入图片描述
删除

public static void main(String[] args) {
        File f=new File("./test.txt");
        System.out.println(f.exists());
        f.delete();//删除文件
        System.out.println(f.exists());
    }

一开始存在f这个文件,然后你删了,再去exist判断就没有了
在这里插入图片描述

创建目录相关方法:
在这里插入图片描述

public static void main(String[] args) {
        //创建一级目录
        File f=new File("./a");
        f.mkdir();//创建目录
        System.out.println(f.isDirectory());//判断是不是目录——打印true

        //创建多级目录
        File f2=new File("./b/c");
        f2.mkdir();
        System.out.println(f2.isDirectory());//打印false——mkdir不能创建多级目录

        File f3=new File("./b/c");
        f3.mkdirs();
        System.out.println(f3.isDirectory());//打印true——创建多级目录要用mkdirs
    }

运行结果如下:
在这里插入图片描述
list和listFiles:

在这里插入图片描述

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

        File f2=new File("./aaa");
        System.out.println(f2.list());
        //直接打印是[Ljava.lang.String;@1b6d3586——表示字符串的数组
        //正确打印方式:
        System.out.println(Arrays.toString(f2.list()));//打印[bbb]

        System.out.println(Arrays.toString(f2.listFiles()));//打印[.\aaa\bbb]

    }

运行结果如下:
在这里插入图片描述
文件改名方法:
在这里插入图片描述

public static void main(String[] args) {
        File f=new File("./aaa");
        File f2=new File("./zzz");
        f.renameTo(f2);//把aaa的名字改成zzz
    }

2.2文件内容的读写——数据流

我们下面要说的都是流(Stream)对象。此处我们说的流,就像水龙头里的水流,打开开关就源源不断。比如我们这里想通过这个水龙头,接100ml的水:我们可以1次接10ml,分10次接;也可以1次接20ml,分5次接…

而我们想通过这样的流对象来读取100字节也是同样道理,比如我们想读100字节:可以1次读10字节,读10次;也可以1次读20字节,读5次…
ps:读是和取水一样,写也是同理
在这里插入图片描述

针对文件内容的读写,java标准库提供了一组类,首先按照文件的内容,分成了两个系列:
(1)字节流对象,针对二进制文件,以字节为单位进行读写的
读:inputStream
写:OutputStream

我们现在用FileInputStream演示:
比如我们现在D盘里有个文件test.txt
在这里插入图片描述

一次读一个字节:

//1次读1个字节
public static void main(String[] args) throws IOException {
        //构造方法中需要指定打开文件的路径
        //此处的路径可以是绝对路径,也可以是相对路径,还可以是File对象
        //1.创建对象
        InputStream inputStream=new FileInputStream("D:/test.txt") ;
        try {
            //2.尝试一个一个字节的读取,把整个文件读完
            while(true){
                //read有三个版本的重载
                //1.无参:1次读1个字节,返回值是读到的这个字节
                //2.一个参数版本:1次读n个字节,把读的结果放到参数中指定的数组中,返回值就是读到的字节数
                //3.三个参数版本:1次读n个字节,把读的结果放到参数中指定的数组中,返回值就是读到的字节数
                //不是从数组的起始位置放,而是从中间位置放(off这个下标),len表示最多能放多少元素(字节)

                int b=inputStream.read();
                //为什么是一个字节一个字节的读取,返回值是int?
                //1个字节的范围是:0~255或-128~127
                //而这个方法里规定:-1"是已经读完文件"的标记,
                //如果你正常用byte,就没办法知道-1是正好有一个-1的数,还是说已经读完了
                //所以我们这里会用更大范围的类型

                //针对字符流,也有类似的设定:1次读一个char
                if(b==-1){
                    break;
                }
                System.out.print(b+" ");
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {//放到finally里面close是防止前面报异常导致无法执行到close
            //读完之后,要关闭文件,释放资源
            inputStream.close();
        }
    }

运行结果如下,结果正好对应test.txt里面的字母ASCII码:
在这里插入图片描述
如果你嫌上面的代码麻烦,还有改进的方法:

//改进写法
    public static void main(String[] args) throws FileNotFoundException {
        try(InputStream inputStream=new FileInputStream("D:/test.txt")){
            while(true){
                int b=inputStream.read();
                if(b==-1){
                    break;
                }
                System.out.println(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } //这个代码中,没有显示的调用close,但是try会自动帮我们调用
    }

在这里插入图片描述
一次读若干字节:

//1次读若干字节
    public static void main(String[] args) {
        try(InputStream inputStream=new FileInputStream("D:/test.txt")){
            while(true){
                byte[] buffer=new byte[1024];
                int len=inputStream.read(buffer);
                if(len==-1){
                    break;//返回-1说明读取完毕
                }
                for(int i=0;i<len;i++){
                    System.out.print(buffer[i]+" ");
                }
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }

按字节流写文件:

//使用字节流,写文件
    public static void main(String[] args) {
        try(OutputStream outputStream=new FileOutputStream("d:/test.txt")){
            byte[] buffer=new byte[]{97,98,99};//写入abc
            //注意!每次按照写方式打开文件,都会清空文件的内容,然后从头开始写

            //ps:也有一种不清空文件,而是在原有内容上追加写的流对象

            outputStream.write(buffer);
        }catch (IOException e){
            e.printStackTrace();
        }
    }

(2)字符流对象,针对文本文件,是以字符为单位进行读写的
读:Reader
写:Writer

按字符流读文件:

public static void main(String[] args) {
        try(Reader reader=new FileReader("d:/test.txt")){
            //按字符来读
            while(true){
                char[] buffer=new char[1024];
                int len=reader.read(buffer);//不一定能读满1024
                if(len==-1){
                    break;
                }
                //法一
                for(int i=0;i<len;i++){
                    System.out.print(buffer[i]);//打印abc
                }

                System.out.println("========分割线==========");

                //法二
                String str=new String(buffer,0,len);//从下标0读len个
                //如果这里传入的数组是byte数组,还可以手动指定utf-8,字符集来避免乱码
                System.out.println(str);//打印abc
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }

按字符流写文件:

public static void main(String[] args) {
        try(Writer writer=new FileWriter("d:/test.txt")){
            writer.write("i love china");
        }catch (IOException e){
            e.printStackTrace();
        }
    }

两个系列的读和写都是抽象类,实际使用往往是这些类的子类,比如:
FileInputStream
FileOutputStream
FileReader
FileWriter

上面8个类中,
黑色字体的抽象类既可以针对普通文件的读写,也可以针对特殊文件(网卡、socket文件)进行读写。
红色字体的都是特指针对普通文件进行读写的。

ps:虽然这里类很多,但是实际操作这些类的方法都是一样的

三、一些案例

3.1实现查找文件并删除

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


//案例1:实现查找文件并删除
public class Demo12 {
   public static void deleteFile(File f){//删除文件
        try {
            System.out.println("你确认要删除文件"+f.getCanonicalPath()+"吗?");
            System.out.println("请输入yes/no来进一步确认:");
            Scanner scanner=new Scanner(System.in);
            String choice=scanner.next();
            if(choice.contains("Y")||choice.contains("y")){
                f.delete();
                System.out.println("文件已删除");
            }else{
                System.out.println("删除操作已取消");
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void scanDir(File rootDir,String toDeleteName){//递归遍历并删除
        //1.先列出rootDir中有哪些内容
        File[] files=rootDir.listFiles();
        if(files==null){
            //rootDir是一个空目录
            return;
        }

        //不为空目录

        //2.遍历当前列出的这些内容,如果是普通文件,就检查文件名是否是要删除的文件
        //如果还是目录,就继续递归
        for(File f:files){
            if(f.isFile()){
                //普通文件的情况
                if(f.getName().contains(toDeleteName)){
                    //不要求名字完全一样,只要文件名中包含了关键字即可删除
                    deleteFile(f);
                }else if(f.isDirectory()){
                    //目录就继续递归遍历
                    scanDir(f,toDeleteName);
                }
            }
        }

    }

    public static void main(String[] args) {
        //1.先输入要扫描的目录,以及要删除的文件
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入要扫描的路径:");
        String rootDirPath=scanner.next();
        System.out.println("请输入要删除的文件名:");
        String toDeleteName=scanner.next();

        File rootDir=new File(rootDirPath);
        if(!rootDir.isDirectory()){
            System.out.println("输入的扫描路径有误,请重新输入:");
        }
        //2.遍历目录,目录是一个树形结构,我们用递归方式
        scanDir(rootDir,toDeleteName);

    }
}

3.2文件的复制

需要让用户指定两个文件路径,一个是原路径(被复制的文件),一个是目标路径(复制之后生成的文件)

import java.io.*;
import java.util.Scanner;
public class Demo13 {
    public static void main(String[] args) {
        //1.输入两个路径
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入要拷贝的路径:");
        String src=scanner.nextLine();
        System.out.println("请输入要拷贝的路径:");
        String dest=scanner.nextLine();
        File srcFile=new File(src);
        if(!srcFile.isFile()){
            System.out.println("输入的源路径不正确");
            return;
        }
        //此处不太需要检查目标文件是否存在,OutputStream写文件时可以自动创建不存在的文件
        //2.读取源文件内容,拷贝到目标文件中
        try(InputStream inputStream=new FileInputStream(src)){
            try(OutputStream outputStream=new FileOutputStream(dest)){
                //把inputStream的内容读出来,再写入到outputstream
                byte[] buffer=new byte[1024];
                while(true){
                    int len=inputStream.read(buffer);
                    if(len==-1){
                        //读取完
                        break;
                    }
                    //写入的时候,不能把整个buffer都写进去,buffer可能是只有一部分有效
                    //(比如一个文件只有24字节,但是buffer是1024,你只需要读24个字节即可)
                    outputStream.write(buffer,0,len);
                }
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

3.3找到指定名称或内容包含指定字符的所有普通文件

这个案例的核心就是进行文件内容的查找

先输入一个路径,再输入一个要查找的文件内容的“关键词”

递归的遍历文件,看哪个文件的内容包含关键词,就把对应文件的路径打印出来

先递归遍历这个文件,针对每个文件都打开,并读取内容,再进行字符串查找即可。

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Scanner;
public class Demo14 {
    private static boolean containsWord(File f, String word) {//判断文件内容是否包含word
        StringBuilder stringBuilder=new StringBuilder();
        //把f中的内容都读出来,放到一个StringBuilder中
        try(Reader reader=new FileReader(f)){
            char[] buffer=new char[1024];
            while(true){
                int len=reader.read(buffer);
                if(len==-1){
                    break;
                }
                //把这段读到的结果,放到StringBuilder中
                stringBuilder.append(buffer,0,len);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
        return stringBuilder.indexOf(word)!=-1;
        //indexOf返回的是字符串下标,如果word在stringBuilder中不存在,返回-1
    }
    private static void scanDir(File rootDir,String word) throws IOException {
        //1.先列出rootDir中有哪些内容
        File[] files=rootDir.listFiles();
        if(files==null){
            return;
        }

        //2.遍历每个元素,针对普通文件和目录分别进行处理
        for(File f:files){
            if(f.isFile()){
                //文件进行内容查找
                if(containsWord(f,word)){
                    System.out.println(f.getCanonicalPath());
                }
            }else{
                //目录就继续往下递归
                scanDir(f,word);
            }
        }
    }



    public static void main(String[] args) throws IOException {
        //1.输入要扫描的文件路径
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入要扫描的路径");
        String rootDirPath=scanner.next();
        System.out.println("请输入要查找的关键词");
        String word=scanner.next();
        File rootDir=new File(rootDirPath);
        if(!rootDir.isDirectory()){
            System.out.println("输入的路径非法");
            return;
        }
        //2.递归的进行遍历
        scanDir(rootDir,word);
    }

}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

劲夫学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值