目录

JavaEE文件操作和IO

【JavaEE】文件操作和IO

博客结尾附有此篇博客的全部代码!!!

一、认识文件

1.1 狭义和广义的文件概念

  • 狭义的文件:通常指存储在磁盘中的普通文件,是由字节(或字符)序列构成的数据集合,以一定的结构保存在存储介质中(如 FAT、ext4 等文件系统)。

  • 广义的文件:概念则更为广泛,不仅包括磁盘上的普通文件,还涵盖了其他多种资源。例如:

    1.设备文件:包括字符设备和块设备,代表硬件设备(如键盘、显示器、硬盘等)

    2.目录:存储文件名和相关元数据的特殊文件

    3.虚拟文件:如 Linux 系统中 /proc 下的伪文件,用于显示内核和进程状态。

    4.其他资源:在某些情况下,网卡、声卡等外设也可以被视为文件

1.2 文件路径

绝对路径 :绝对路径是从文件系统的根目录(通常是 / 或 C:\)开始的完整路径,它唯一地标识了文件或目录的位置。

相对路径 :相对路径是从当前工作目录(Current Working Directory,CWD)开始的路径,它描述了从当前目录到目标文件或目录的相对位置。

1.3 文件的分类

文本文件(以记事本打开可以看懂的):是由可打印字符(如字母、数字、标点符号等)组成的文件,通常以人类可读的形式存储数据。

内容表示:文件内容以字符编码(如ASCII、UTF-8、GBK 等)存储,每个字符占用一个或多个字节。

二进制文件(以记事本打开时乱码):二进制文件是以二进制形式存储数据的文件,文件内容可以是任意字节序列,不一定对应可打印字符。

内容表示:文件内容以字节序列存储,可以包含任何数值(0-255),不局限于字符编码。

二、Java 中操作⽂件

2.1 File类

Java中将文件的操作封装在File类中。(创建目录、删除文件、重命名文件等…)

属性:

https://i-blog.csdnimg.cn/direct/7afb7adefda749bab3caf1efc0c8a8fd.png#pic_center

构造方法:

File 的构造方法,能够传入一个路径,来指定一个文件,这个路径可以是绝对路径也可以是相对路径,构造好对象之后,就可以通过这些方法,来完成一些具体的功能了

https://i-blog.csdnimg.cn/direct/4c1c7977b3bb4c7593f6eef5b94ae066.png#pic_center

方法:

https://i-blog.csdnimg.cn/direct/0739c678cb2c40e8be294f341eaf0de0.png#pic_center

2.2 代码演示

在Java代码编写的时候不能用windows默认的分隔符:"/“斜杠

idea中用斜杠可能会被JVM识别成转义字符

我们可以用反斜杠”“或者”//“来表示。

https://i-blog.csdnimg.cn/direct/0804784635a54073b9697f6e08322b2f.png#pic_center

https://i-blog.csdnimg.cn/direct/d5b4c3447ba24146a7e665713b2a661f.png#pic_center

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

public class Demo1 {
    public static void main(String[] args) throws IOException {
//        File file=new File("D:Project/java/java-biog" +
//                "/Day20250312/IO测试文件.txt");
        File file = new File("./IO测试文件.txt");
        System.out.println("File 对象的⽗⽬录⽂件路径:"+file.getParent());
        System.out.println("FIle 对象的纯⽂件名称:"+file.getName());
        System.out.println("File 对象的⽂件路径:"+file.getPath());
        System.out.println("File 对象的绝对路径:"+file.getAbsolutePath());
        System.out.println("File 对象的修饰过的绝对路径:"+file.getCanonicalPath());
        System.out.println("File 对象描述的⽂件是否真实存在:"+file.exists());
        System.out.println("File 对象代表的⽂件是否是⼀个⽬录:"+file.isDirectory());
        System.out.println("File 对象代表的⽂件是否是⼀个普通⽂件:"+file.isFile());
    }
}

运行结果:

https://i-blog.csdnimg.cn/direct/e068bbc1f84b42ffa357f37565a5ef0c.png#pic_center

createNewFile()和delete():

public class Demo2 {
    public static void main(String[] args) throws IOException {
        File file=new File("D:/Project/java/java-biog/" +
                "Day20250312/hello.txt");

        if(!file.exists()){
            boolean file1=file.createNewFile();
            System.out.println("file文件创建成功:"+file1);
        }
        System.out.println(file.delete());
    }
}

https://i-blog.csdnimg.cn/direct/c0b9954ad1d644a992fd75aa652b60d1.png#pic_center

https://i-blog.csdnimg.cn/direct/b13fad4faee94b87a30b61dbe436c8b7.png#pic_center

public class Demo3 {
    public static void main(String[] args) throws IOException, InterruptedException {
        File file=new File("D:/Project/java/java-biog/" +
                "Day20250312/hello.txt");
        if(!file.exists()){
            boolean file1=file.createNewFile();
            System.out.println("file文件创建成功:"+file1);
        }

        Thread.sleep(6000);
        file.deleteOnExit();
    }
}

https://i-blog.csdnimg.cn/direct/04ad0c8e4ce94fb4bc6bb548ed8d80c4.png#pic_center

https://i-blog.csdnimg.cn/direct/5d0a54f5317f40d2bf8e41fb69b27881.png#pic_center

https://i-blog.csdnimg.cn/direct/fb08410b444c4cac82b22581986ec0fb.png#pic_center

list()和listFiles():

public class Demo4 {
    public static void main(String[] args) {
        File file=new File("D:/Project/java/java-biog/" +
                "Day20250312");
        System.out.println(Arrays.toString(file.list()));
        System.out.println(Arrays.toString(file.listFiles()));
    }
}

https://i-blog.csdnimg.cn/direct/c90e1cd99a404f33ace80345dae9933f.png#pic_center

mkdir()和mkdirs():

public class Demo5 {
    public static void main(String[] args) {
        File file1=new File("D:/Project/java/java-biog/" +
                "Day20250312/hello1");
        boolean f1=file1.mkdir();
        System.out.println(f1);
        File file2=new File("D:/Project/java/java-biog/" +
                "Day20250312/hello2/aaa/bbb/ccc");
        boolean f2=file2.mkdirs();
        System.out.println(f2);
    }
}

https://i-blog.csdnimg.cn/direct/d8896182570d47a58ad978c6e1aa35af.png#pic_center

mkdirs():

https://i-blog.csdnimg.cn/direct/59b603153b644eb8be6fc613dc65d91e.png#pic_center

mkdir():

https://i-blog.csdnimg.cn/direct/1ee3ed08f31f487aa8c38169d41b2720.png#pic_center

renameTo(File dest)、canRead()和canWrite():

public class Demo6 {
    public static void main(String[] args) {
        File file1=new File("D:/Project/java/java-biog/" +
                "Day20250312/IO测试文件.txt");
        System.out.println(file1.renameTo(new File("D:/Project/java/java-biog/" +
                "Day20250312/IO.txt")));
                
        System.out.println(file1.canRead()); // 判断用户是否对文件有可读权限 false
        System.out.println(file1.canWrite()); // 判断用户是否对文件有可写权限 false
    }
}

三、文件内容的读写 —— 数据流

文件内容的读写涉及的操作:

打开文件 –》读文件(写文件) –》关闭文件

输入:内存 –》硬盘

输出:硬盘 –》内存

3.1 字节流和字符流

字节流:主要针对二进制文本,是以字节为单位进行读写的

  • InputStream:字节输入流的超类,用于从源读取字节。
  • OutputStream:字节输出流的超类,用于向目标写入字节。

字符流:主要针对二进制文本,是以字节为单位进行读写的

  • Reader:针对文本文件,是以字符为单位进行读写的

  • Writer:字符输出流的超类,用于向目标写入字符。

    https://i-blog.csdnimg.cn/direct/c3d63447333f43cda408ba9f98550bbe.png#pic_center

以上面两个文件给大家演示:

字节流

InputStream

InputStream是一个抽象类,要使用还需要实现具体的类(FileInputStream)。

https://i-blog.csdnimg.cn/direct/e70d3748166b46488a6277e2b6a7fb39.png#pic_center

https://i-blog.csdnimg.cn/direct/4a341d589f3543b49ed884e2aa19ed16.png#pic_center

public class Demo8 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis1=null;
        try{
             fis1 = new FileInputStream("D:/Project/java/java-biog/" +
                    "Day20250312/IO测试文件目录/测试图片.png");
            while(true){
                int ch = fis1.read();
                if(ch == -1){
                    break;
                }
                System.out.println(ch);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally{
            fis1.close();
        }
    }
}

改进之后,代码显得不美观!

Java 中提供了一个语法,try with resourcestry ( )

public class Demo9 {
    public static void main(String[] args) throws FileNotFoundException {
        try(InputStream inputStream=new FileInputStream("D:/Project/java/java-biog/"  +
                                    "Day20250312/IO测试文件目录/测试图片.png")){
            while(true){
                int n=inputStream.read();
                if(n==-1) break;
                System.out.println(n);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
public class Demo10 {
    public static void main(String[] args) throws FileNotFoundException {
        try(InputStream i=new FileInputStream("D:/Project/java/java-biog/"  +
                "Day20250312/IO测试文件目录/测试图片.png")){
            while(true){
                byte[] b=new byte[1024];
                int n=i.read(b);
                if(n==-1) break;
                for (int j = 0; j < n; j++) {
                    System.out.println(n);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

FileInputStream是InputStream子类,InputStream实现Closeable接口。所以,当进入 try 块时,inputStream 被初始化。一旦退出 try 块(无论是因为正常结束还是由于异常),JVM 会自动调用 inputStream 的 close() 方法来关闭文件流。

OutputStream

OutputStream 同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中,所以使用 FileOutputStream https://i-blog.csdnimg.cn/direct/eee49eeb5ed84c7eb1d9d5d1050607fe.png#pic_center

public class Demo11 {
    public static void main(String[] args) throws FileNotFoundException {
        String content = "Hello, World!";
        try( OutputStream outputStream=new FileOutputStream("D:/Project/java/java-biog/"  +
                "Day20250312/IO测试文件目录/测试1.txt",true)){
            outputStream.write(content.getBytes());
                outputStream.flush();//刷新输出流,确保所有数据都被写出
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

https://i-blog.csdnimg.cn/direct/650e15082fc54b35a829a9521d4472eb.png#pic_center

红框里面的true,可以让本次写的内容不覆盖上次的内容,在上次内容后面追加本次的内容!

public class Demo12 {
    public static void main(String[] args) {
        try (OutputStream outputStream = new FileOutputStream("D:/Project/java/java-biog/" +
                "Day20250312/IO测试文件目录/测试1.txt", true)) {
            byte[] b = new byte[]{99, 98, 97};
            outputStream.write(b);
            outputStream.flush();//刷新输出流,确保所有数据都被写出
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
字符流

Reader:

https://i-blog.csdnimg.cn/direct/632671deeeee45b5a85dbb3834263bc1.png#pic_center

public class Demo13 {
    public static void main(String[] args) throws FileNotFoundException {
        try(Reader reader=new FileReader("D:/Project/java/java-biog/" +
                "Day20250312/IO测试文件目录/测试1.txt")){
            while(true){
                int ch=reader.read();
                if(ch==-1) break;
                for (int i = 0; i < ch; i++) {
                    System.out.println(ch);
                }
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
public class Demo14 {
    public static void main(String[] args) throws FileNotFoundException {
        try(Reader reader=new FileReader("D:/Project/java/java-biog/" +
                "Day20250312/IO测试文件目录/测试1.txt")){
            while(true){
                char[] chars=new char[1024];
                int ch=reader.read(chars);
                if(ch==-1) break;
                for (int i = 0; i < ch; i++) {
                    System.out.println(chars[i]);
                }
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

Writer :

https://i-blog.csdnimg.cn/direct/bfee51d2f01b4e439aeaceee3faa8d28.png#pic_center

public class Demo15 {
    public static void main(String[] args) throws FileNotFoundException {
        try(Writer writer=new FileWriter("D:/Project/java/java-biog/"  +
                               "Day20250312/IO测试文件目录/测试1.txt")){
            BufferedWriter bufferedWriter=new BufferedWriter(writer);
            writer.write("hello world");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

BufferedWriter 可以配置为在特定条件下自动刷新缓冲区(例如,当缓冲区满或遇到换行符时)。这确保了数据及时写入,而不需要手动调用 flush() 方法。

3.2 特别注意

  1. 不管字节流或字符流读写文件,结束都需要关闭文件?

每次程序打开一个文件,就会在文件的描述表中(固定长度的顺序表),每次打开文件,就相当申请一个表项。如果光打开,不关闭,后续表项申请完,后续再打开文件,就会打开失败。

  1. 字符流和字节流读相同的字符,输出的字节是不同的?

“你好”这两个字,字节流读取是三个字节,而字符流读取是两个字节 。这个是不矛盾的。

  • 在字节流中,对于中文字符“你”和“好”,在 UTF-8 编码中,每个字符通常占用3个字节。
  • 对于 UTF-8 编码的文本,Java 会根据编码规则正确地将3个字节的字节序列转换为一个字符。因此,对于“你好”这两个字,字符流会正确地读取两个字(每个中文字符在 UTF-8 中通常占用3个字节,但字符流按字符读取,所以是两个字)。

通常是因为在字节流读取时没有正确处理 UTF-8 的多字节特性,而字符流读取则自动处理了编码,正确地按字符读取。

四、实战演示

4.1 查找删除文件

扫描指定⽬录,并找到名称中包含指定字符的所有普通⽂件(不包含⽬录),并且后续询问⽤⼾是否要删除该⽂件。

思路:

  1. 先输入扫描文件目录,并判断是否是目录,不是则退出
  2. 输入要删除文件的关键字
  3. traversefile()这个方法是遍历目录下的每个文件
  4. 判断是否是目录,是目录进行递归,不是目录,则判断是否是空文件;如果不是空文件,则进行deletefile()操作是否删除此文件
public class Demo16 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入要扫描的目录:");
        String filename = sc.next();
        File file = new File(filename);
        if (!file.isDirectory()) {
            System.out.println("您输⼊的不是目录,退出");
            return;
        }
        System.out.println("请输入要删除的文件:");
        String deleteName = sc.next();
        traversefile(file, deleteName);
    }

    private static void traversefile(File file, String deleteName) {
        //1.遍历该目录下的文件
        File[] files = file.listFiles();
        //2.判断是否目录是否为空
        if (files == null || files.length == 0) {
            return;
        }
        for (File f : files) {
            //判断是否是目录还是普通文件
            if (f.isDirectory()) {
                traversefile(f, deleteName);
            } else {
                deletefile(f, deleteName);
            }
        }

    }

    private static void deletefile(File f, String deleteName) {
        if (f.getName().contains(deleteName)) {
            System.out.println("找到包含关键字的文件,是否删除"+f.getName()+"文件,(y/n)");
            Scanner sc = new Scanner(System.in);
            String answer = sc.next();
            if (answer.equals("y")) {
                f.delete();
                System.out.println("文件删除!!!");
            } else {
                return;
            }
        }
    }
}

4.2 普通文件的复制

需要让用户指定两个文件路径,一个是源路径 (被复制的文件),一个是目标路径(复制之后生成的文件),打开源路径的文件,读取里面的内容,并写入到目标文件。

思路:

  1. 输入源文件和目标文件路径(判断源文件路径,因为OutputStream会自动创建一个文件)
  2. 运用InputStream和OutputStream从源文件读取写入到目标文件
public class Demo17 {
    public static void main(String[] args) throws FileNotFoundException {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入源路径:");
        String filename = sc.next();
        File file = new File(filename);
        System.out.println("请输入目标文件路径:");
        String copyName = sc.next();
        if(!file.isFile()){
            System.out.println("源路径不是一个普通的文件!返回!");
            return;
        }
        try(InputStream in = new FileInputStream(file);
            OutputStream ou=new FileOutputStream(copyName) ){
            while(true){
                byte[] b = new byte[1024];
                int len = in.read(b);
                if(len==-1){
                    break;
                }
                for(int i=0;i<len;i++){
                    ou.write(b[i]);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

4.3 文件内容查找

不仅要找到文件名包含关键字,还要找到文件内容包含关键字

思路:

1.遍历,递归,找到文件名包含关键字的文件并返回文件名

2.判断文件内容,这里用Reader,因为需要将读到的字符拼接起来(Stringbuffer),将拼接起来的字符和关键字对比

public class Demo18 {
    public static void main(String[] args) throws FileNotFoundException {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入要扫描的目录:");
        String filename = sc.next();
        File file = new File(filename);
        if (!file.isDirectory()) {
            System.out.println("您输⼊的不是目录,退出");
            return;
        }
        System.out.println("请输入关键字:");
        String impName = sc.next();
        traversefile(file, impName);
    }

    private static void traversefile(File file, String impName) throws FileNotFoundException {
        //1.遍历该目录下的文件
        File[] files = file.listFiles();
        //2.判断是否目录是否为空
        if (files == null || files.length == 0) {
            return;
        }
        for (File f : files) {
            //判断是否是目录还是普通文件
            if (f.isDirectory()) {
                traversefile(f, impName);
            } else {
                foudnfile(f, impName);
            }
        }

    }

    private static void foudnfile(File f, String impName) throws FileNotFoundException {
        if(f.getName().contains(impName)) {
            System.out.println("文件名包含关键字"+f.getName());
        }else{
            try(Reader reader= new FileReader(f)) {
                StringBuffer sb = new StringBuffer();
                while(true) {
                    char[] buf = new char[1024];
                    int len= reader.read(buf);
                   if (len == -1) {
                       break;
                   }
                   for(int i=0; i<len; i++) {
                       sb.append(buf[i]);
//                       sb.append(buf, 0, len);
                   }
                   //return stringBuilder.indexOf(word) != -1;
                   // stringBuilder.indexOf()是StringBuffer类中的一个方法,找到关键字word方法会返回 true;反之,返回false
                   if(sb.toString().contains(impName)) {
                       System.out.println(f.getName());
                   }
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}