Java 文件操作(下)

简介: Java 文件操作(下)

二、InputStream



009b70b2814345ebba5f7b58345d7301.png


我们在当前的工作目录下,创建两个 .txt 文件,里面的内容如下:

现在我们通过输入流来测试一下读取文件中的内容。


65486a3651584fdda8ddd58c6400b49e.png


测试一


程序清单8:


import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class Test8 {
    public static void main(String[] args) throws IOException {
        //创建实例的过程:先打开文件,再进行读写
        InputStream inputStream = new FileInputStream("./test.txt");
        while (true) {
            //read 无参版本一次只能读取一个字节,返回值即当前读到的字节
            //如果读到文件的末尾,就会返回 -1
            int a = inputStream.read();
            if (a == -1) {
                break;
            }
            System.out.printf("%c", a);
        }
        //这里记得关闭流
        inputStream.close();
    }
}


输出结果:


a7e3f5b568084df48747f97a05649fa2.png


注意几个点:


① InputStream 是抽象类,它本身不能实例化对象,我们只能使用它的实现类来完成 new


② 程序清单8 中的 read 方法是无参的方法,一次只能读一个字节,另外,当读到文件末尾的时候,就会返回 -1,即 EOF ( end of file ),文件结束符。


③ 如果我们读取的是英文,直接转换成字符的形式输出,否则就会输出数字,即 ASCII 码。


④ 在程序的最后,需要使用 close 方法来关闭流,以防资源泄露。一个进程能打开的文件数量是有上限的,其能打开的文件描述符表也是有上限的,如果不使用 close 及时关闭,那么当文件描述表中的资源占满的时候,就会使程序崩溃。


举个例子:这就和我们平时使用自来水一样,当你用完了水龙头,需要关闭,否则,你就会浪费水资源!如果一个小区都这样浪费水的话,那么很快就会发生水资源不足,因为水资源是有上限的。


优化代码:


3850ed9ebe37442ca0f2eb80e4cbcefa.png


try with resources 语法


在我们解决了所有的问题后,将上图的左边代码转换成了右边代码,但我们可以发现,右边的代码又较为繁杂,所以我们可以使用 【 try with resources 】这个语法,即在 try( ) 中添加类,那么哪些类可以放入这个 try 的括号中呢?

我们查看输入流和输出流都实现了 Closeable 接口,在这个接口的源代码中,我们看到它只有一个方法 close,也就是说:实现了这个 Closeable 接口的类,当我们在 try( ) 中添入的类,这个类就会自动使用 close 方法来关闭资源。


f5bb3939ac2c480c8ab0713ce81153f2.png


try( ) 这个方法更加简单,而且能够自动使用 close 方法关闭流。也需要我们重点掌握,如下程序清单9。


程序清单9:


import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class Test9 {
    public static void main(String[] args) throws IOException {
        try ( InputStream inputStream = new FileInputStream("./test.txt") ) {
            while (true) {
                int a = inputStream.read();
                if (a == -1) {
                    break;
                }
                System.out.printf("%c", a);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


测试二


程序清单10:


import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Test10 {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("./test.txt") ){
            byte[] buffer = new byte[1024];
            while (true) {
                int len = inputStream.read(buffer);
                if (len == -1) {
                    break;
                }
                for (int i = 0; i < len; i++) {
                    System.out.printf("%c", buffer[i]);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


输出结果:


c01517f27bbe45db8a460358dbfcc727.png


程序清单10 中的 read 方法会尝试把参数的这个 buffer 给填满,buffer 数组共 1024 个字节。


假设该文件的长度是2049,下面是读取文件的总过程:


第一次循环,读取出1024个字节,放到 buffer 数组中,read 方法返回1024,

第二次循环,读取出1024个字节,放到 buffer 数组中,read 方法返回1024,

第三次循环,读取出1个字节,放到 buffer 数组中,read 方法返回1,

第四次循环,此时已经读到文件末尾了 ( EOF ),read 方法返回-1.


注意几个点:


① 程序清单10 中的 read 方法,一次可以读取多个字节。


我们需要明确一个点,读取数据的速度:
CPU 上的寄存器 >> 内存 >> 磁盘 ( A >> B,表示 A 的速度远快于 B )


也就是说,相比于一次只读一个字节,这样一次读取多个字节的效果更好,因为一次多读点数据,那么读的次数就会减少,那么磁盘的 IO 次数就会减少,效率也就更高!


举个例子:小明的宿舍住三楼,他准备去一楼的水房接开水,他准备接 1 L 的水,但他打算用水杯接水,所以他来来回回地上下楼跑了好多次。下一次的时候,他心想:用水杯接水太麻烦了,于是他就拿着两个水瓶接水了,这样效率就高很多了。而这就对应着上面的读操作,一次多接点水,那么跑路就少很多。


② read 方法的返回值是一次读取的字节长度,如果读的不为空文件,那么至少一次读取1个字节,和之前一样,当读到文件末尾,read 方法就返回 -1.


测试三


我们来测试一下读取内容是中文的文件。


程序清单11:


import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class Test11 {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("./test3.txt") ) {
            try (Scanner scanner = new Scanner(inputStream, "UTF-8")) {
                while (scanner.hasNext()) {
                    String s = scanner.nextLine();
                    System.out.println(s);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


输出结果:


d521c5af147a4bfbbb7183e584c8e831.png


注意几个点:


① 在程序去清单11 中,我们这里使用 Scanner 对象,并不是我们之前的 System.in 的标准输入。在这里,我们传入的参数分别是:【 流,字符集】。同样地,这里的 Scanner 对象涉及到流了,那么就得使用 close 方法,当然,使用【 try with resources 】这个语法也行得通!


② 使用 Scanner 这个语法也能读取到中英文混合的文件,我们可以使用 scanner.nextLine 方法来实现这个读取操作。



三、OutputStream



image.png



对 flush 方法进行说明:我们知道,计算机读写内存数据的速度是比读写磁盘数据的速度是要快很多的。所以,大多数的 OutputStream 为了减少访问磁盘的次数,在写数据的时候都会将数据暂时先写入内存的缓冲区中,直到该区域满了或者其他指定条件时才真正将数据写入磁盘中。但这可能会造成一个结果,就是我们写的数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置,调用 flush(刷新)操作,将数据刷到设备中,这就是 " 临门一脚 "。


举个例子:当一家人嗑瓜子的时候,不需要嗑一个瓜子,就把瓜子皮往垃圾桶里放,垃圾桶就一个,如果所有人将瓜子皮一个一个往里放,效率太低。我们可以先把瓜子皮放在手中,等吃完了所以瓜子,再一下子放入垃圾桶中。那么最后将手中所以的瓜子皮全放入到垃圾桶中,这个操作就相当于 flush 操作,即刷新缓冲区。


我们在当前的工作目录下,创建一个 .txt 文件,里面的内容如下:

现在我们通过输出流来测试一下读取文件中的内容。


4609317c79474db1892d93ab44a6cd7c.png


测试一


程序清单12:


import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Test12 {
    public static void main(String[] args) {
        //一旦按照 OutputStream 的方式打开文件,就会把文件中的原来内容给清空掉
        try (OutputStream outputStream = new FileOutputStream("./test.txt") ) {
            //写入一个字符
            outputStream.write('a');
            outputStream.write('b');
            outputStream.write('c');
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}



文件写入结果:


19bb634dcb14432787b3627538e64cd2.png


程序清单13:


import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Test13 {
    public static void main(String[] args) {
        try (OutputStream outputStream = new FileOutputStream("./test.txt") ) {
            //按照字节来写入
            byte[] bytes = new byte[] {
                    (byte)'O',(byte)'P',(byte)'Q'
            };
            outputStream.write(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}



文件写入结果:


24dc82cd978f4b03ada2d027f50b72ca.png


程序清单14:


import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Test14 {
    public static void main(String[] args) {
        try (OutputStream outputStream = new FileOutputStream("./test.txt") ) {
            //按照字符串来写入
            String s = "welcome to the world !";
            outputStream.write(s.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}



文件写入结果:


9a4951fa21c64e82a3d5630a8ae54a21.png


测试二


程序清单15:


import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
public class Test15 {
    public static void main(String[] args) {
        //使用
        try (OutputStream outputStream = new FileOutputStream("./test.txt") ) {
            try (PrintWriter printWriter = new PrintWriter(outputStream)) {
                printWriter.print("Hello, Jack  ");
                printWriter.println("欢迎来到这个世界!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


文件写入结果:


a316a0fe4f0b4c09b5d58118afc007a8.png


总结


注意几个点:


① OutputStream 和 InputStream 这两个类在用法上差不多,它也需要通过 close 关闭流。

② 正常情况下,按照 OutputStream 的方式打开文件,就会把文件中的原来内容给清空掉,原来的文件内容将被直接替换成重新写入的内容。但我们也可以自己设置,将重新写的内容续在文件的末尾。


只需要将 new FileOutputStream 的第二个参数设置为 true 即可。


try ( OutputStream outputStream = new FileOutputStream(filePath, true) )


③ OutputStream 类支持以字符写入、以字节写入、也可以按照以中英文混合写入。


四、案例



案例1 检索文件名为 key 的普通文件


指定一个待检测的目录,并扫描这个目录,查找整个目录所有普通文件的文件名带有关键字 key,符合关键字 key 的普通文件,展示出来,最后询问用户是否删除。


假设我要删除 D 盘下文件目录下的 test5.txt,很显然,我只要让它递归去跑即可,这就和树的查找很类似了!


a1378df399f34f88a47ca22c8efabd5d.png


程序清单16:


import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Test16 {
    public static void main(String[] args) {
        //1. 让用户输入一个待扫描的目录、待查询的关键字
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入你所要查找的目录对应的绝对路径:");
        String root = scanner.nextLine();
        File rootDir = new File(root);
        if(!rootDir.isDirectory()) {
            System.out.println("你所输入的不是目录,即将退出程序!");
            return;
        }
        System.out.println("请输入你所要查找的关键字:");
        String key = scanner.nextLine();
        //2. 递归整个目录
        // list 中表示递归遍历的结果,里面存放着所有带 key 关键字的文件路径
        List<File> list = new ArrayList<>();
        finding(rootDir,key,list);
        //3. 遍历 list,询问用户是否要删除该文件,再根据用户的输入来决定是否要删除
        for (File f : list) {
            System.out.println("是否要删除该文件:" + f.getName());
            System.out.println("删除请输入 1  /  保留请输入 0");
            int r = scanner.nextInt();
            if (r == 1) {
                f.delete();
                break;
            }
        }
    }
    /**
     * 用 finding 方法进行递归查找
     */
    public static void finding(File rootDir, String key, List<File> list) {
      //从主目录出发,往下搜寻对应的文件夹和普通文件
        File[] files = rootDir.listFiles();
        if (files == null || files.length == 0) {
            //当前目录是一个空目录,就直接返回
            return;
        }
        for (File f : files ) {
            if(f.isDirectory()) {
                //如果当前的文件是一个目录,就继续递归查找
                finding(f, key, list);
            } else {
                if( f.getName().contains(key) ) {
                    list.add(f.getAbsoluteFile());
                }
            }
        }
    }
}



输出结果:


446603431d6f4b3a9657287d02d403c3.png


案例2 普通文件的全文检索


扫描指定目录,并找到内容中包含指定内容关键字的所有普通文件。


假设我们在文件目录下的测试目录下查找内容包含 【world】关键字的普通文件,如果存在,就打印出普通文件的路径。


cfbb60d4706848d5adcc3fbdc7a92f4d.png



程序清单17:


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Test17 {
    public static void main(String[] args) {
        //1. 输入起始目录和内容关键字
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入起始目录:");
        String srcPath = scanner.nextLine();
        File rootDir = new File(srcPath);
        if (!rootDir.isDirectory()) {
            System.out.println("你所指定的不是目录,程序即将退出!");
            return;
        }
        System.out.println("请输入查询的内容关键字:");
        String keyword = scanner.nextLine();
        //2. 利用递归来查找内容
        List<File> list = new ArrayList<>();
        finding(rootDir,keyword, list);
        //3. 打印出所有符合要求的文件名
        for (File f : list) {
            System.out.println(f.getAbsoluteFile());
        }
    }
    /**
     * 利用递归来找到包含指定 keyword 的普通文件,并所有符合要求的放入顺序表中
     */
    public static void finding(File rootDir, String keyword, List<File> list) {
        //从主目录出发,往下搜寻对应的文件夹和普通文件
        File[] files = rootDir.listFiles();
        if (files == null || files.length == 0) {
            return;
        }
        for (File f : files) {
            if (f.isDirectory()) {
                finding(f, keyword, list);
            }else{
                if ( isContains(f, keyword) ){
                    list.add(f);
                }
            }
        }
    }
    /**
     * 判断是否满足普通文件中包含关键字 keyword,利用主串与子串的关系来解决
     */
    private static boolean isContains(File f, String keyword) {
        StringBuilder stringBuilder = new StringBuilder();
        try( InputStream inputStream = new FileInputStream(f.getAbsoluteFile()) ) {
            Scanner scanner = new Scanner(inputStream, "utf-8");
            while (scanner.hasNext()) {
                //将普通文件中的所有内容全部拼接至 stringBuilder 中
                String str = scanner.nextLine();
                stringBuilder.append(str + "\n" );
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        //看看主串 stringBuilder 中是否有子串 keyword,如果有,必定不是-1
        return stringBuilder.indexOf(keyword) != -1;
    }
}



测试结果:


cf985ead7e47410a9c2e2541a9552945.png


在程序清单17 中的代码,在扫描目录和普通文件数量较多时,效率较低,因为它涉及到了密集的磁盘 IO,每一次查询都要重新扫一遍磁盘,扫描的工作都是在查询中进行,所以较慢。举个例子:小明口渴了,准备从家里出发去小卖部买水,于是他就得跑过去再跑回来,当回家的时候,他的妈妈说,烧菜的时候缺盐,于是小明又得重新从家里出发,去便利店买了袋盐。


有一个软件叫做 《Everything》,这个软件的查询速率十分高效,当你按文件名搜索的时候,2秒钟不到,磁盘中的所有数据都能够按照关键字检索出来,相比于 windows 的直接搜索,Everything 这个软件极快。而 Everything 这个软件就是在查找数据之前,其通过了一些数据结构对磁盘数据做了预处理,做足了一些准备工作,所以快是有原因的。

举个例子:刚刚小明需要什么东西才出门买,那么当小明从家里出发之前,如果事先列好一份清单,再制定出行程路线,那么效率自然就上去了。


案例3 复制普通文件


编写代码复制一个文件,要求这个文件是一个普通文件,不是目录。启动程序之后,让用户指定一个待复制文件的绝对路径,再指定一个粘贴文件的终止路径。再通过这两个路径,来完成复制文件的逻辑。


假设我要让文件目录下的 BBB目录中 的 1.txt 和 1.png 分别复制成 2.txt 和 2.png


4b62c082d5b84d0895328a246b39d891.png


程序清单18:


import java.io.*;
import java.util.Scanner;
public class Test18 {
    public static void main(String[] args) {
        //1. 让用户输入一个起始路径和一个目标路径
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入需要复制文件的起始路径:");
        String srcPath = scanner.nextLine();
        File srcFile = new File(srcPath);
        //2. 判断几种特殊情况
        //2.1 所复制的文件不存在 或 不是一个文件格式
        if (!srcFile.exists() || !srcFile.isFile()) {
            System.out.println("你所要复制的文件不存在 或 它不是一个文件格式,请检查格式!");
            return;
        }
        System.out.println("请输入需要粘贴的终止路径:");
        String destPath = scanner.nextLine();
        File destFile = new File(destPath);
        //2.2 所复制的文件在终止路径下重名
        if (destFile.exists()) {
            System.out.println("你所要复制的文件在终止路径下已经存在,即将退出程序!");
            return;
        }
        //2.3 所复制的文件的父目录不存在
        if (!destFile.getParentFile().exists() ) {
            System.out.println("你所要复制的文件的终止路径不存在,即将退出程序!");
            return;
        }
        //3. 先读待复制的文件,再将读到的内容写入到终止路径中
        try ( InputStream inputStream = new FileInputStream(srcFile);
             OutputStream outputStream = new FileOutputStream(destFile) ) {
            while (true) {
                byte[] buffer = new byte[1024];
                int len = inputStream.read(buffer);
                if (len == -1) {
                    break;
                }
                //这里应该注意 len 的范围,当 len > 1024 还好说
                //但假设 len = 500 < 1024 的时候,我们就必须控制范围,
                //最好不要超过当前 len 的值,防止写入一些无效的数据
                //outputStream.write(buffer); //不建议使用带有一个参数的 write 方法
                outputStream.write(buffer,0,len);
            }
            //刷新缓冲区
            //这里如果不加 flush,try() 会自动触发 close 方法,也就能自动刷新缓冲区
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


测试结果:


06cf467161b342be92f4a9981ad68e00.png


目录
相关文章
|
5月前
|
Java
揭秘Java文件操作背后的惊天秘密:读写、复制、删除一网打尽!
【6月更文挑战第27天】Java文件操作涵盖读、写、复制和删除。例如,读文件使用`BufferedReader`和`FileReader`;写文件利用`BufferedWriter`和`FileWriter`;复制文件通过读写流实现;删除文件则依赖`Files.delete()`。以上代码示例展示了具体实现。
38 5
|
5月前
|
Java API
Java文件操作,你的电脑“硬盘”竟然可以这样玩?!
【6月更文挑战第27天】Java文件操作涵盖创建、读取、写入、复制、删除等。`java.io`和`java.nio.file`包提供API,如`BufferedReader/FileReader`用于读取,`BufferedWriter/FileWriter`用于写入,`Files.copy()`用于复制,`Files.delete()`用于删除文件。以上代码示例展示了这些功能的简单用法。
38 1
|
6月前
|
Java API
Java的文件操作:创建、读写与删除的技术性详解
Java的文件操作:创建、读写与删除的技术性详解
48 1
|
5月前
|
安全 Java 开发者
掌握Java文件操作,让你的代码“飞”起来!读写、复制、删除全攻略!
【6月更文挑战第27天】Java文件操作涵盖读写、复制、删除。使用NIO的`Files`类提升效率:读取文件用`newBufferedReader`实现逐行读,写入文件用`newBufferedWriter`,复制文件用`copy`方法,删除文件用`deleteIfExists`,确保安全。这些最佳实践优化了内存使用,简化了代码。
38 0
|
2月前
|
Java
Java文件操作
本文介绍了Java中`File`类的使用方法,包括构造方法、常见成员方法及其实现功能。`File`对象可以表示文件或文件夹,支持绝对路径和相对路径。构造方法有三种:基于完整路径、父级路径与子路径组合、`File`对象与字符串组合。成员方法涵盖判断类型、获取大小、获取路径、创建与删除文件/文件夹、获取文件列表、重命名等操作。文章通过示例代码详细展示了各个方法的应用场景及注意事项。
41 1
Java文件操作
|
5月前
|
监控 Java API
Java文件操作大揭秘:不只是读写,还有这些逆天技巧!
【6月更文挑战第27天】Java文件操作不仅包括基础的读写,还有NIO的文件过滤、锁定、映射以及压缩解压和文件变化监控。例如,使用Files.walk结合PathMatcher查找特定类型的文件,利用FileChannel进行文件锁定和内存映射以提升效率,借助ZipOutputStream压缩文件,以及用WatchService监听文件系统变化。这些高级技巧能提升开发效率。
33 3
|
5月前
|
Java API
惊呆了!Java文件操作竟能如此简单:一分钟学会读写、复制、删除!
【6月更文挑战第27天】Java编程中的文件操作简单易行。使用`java.io`包中的`FileInputStream`和`FileOutputStream`进行读写,例如写文件将字符串转为字节写入,读文件则循环读取字节。文件复制涉及两个流,从源文件读取后写入目标文件。删除文件只需调用`File`对象的`delete`方法。这些基本操作让Java文件处理变得直观且易于掌握。
33 1
|
5月前
|
存储 网络协议 Java
Java I/O 详解:基础、文件操作与 NIO 实践
Java I/O 详解:基础、文件操作与 NIO 实践
54 1
|
5月前
|
存储 安全 Java
Java文件操作,让你的代码更“有型”!读写、复制、删除全解析!
【6月更文挑战第27天】在Java中进行文件操作是程序基础功能之一,涉及读写、复制和删除。使用`FileReader/FileWriter`进行文本文件的读写,如示例所示,通过`try-with-resources`保证资源释放。文件复制利用`FileInputStream/FileOutputStream`,而删除文件则依赖`File`的`delete()`方法,确保条件安全执行。这些标准库类提供了高效且健壮的文件管理能力。
38 0
|
5月前
|
安全 Java API
Java文件操作:从入门到精通,读写、复制、删除一个都不能少!
【6月更文挑战第27天】在Java编程中,学习文件操作至关重要,涵盖读写、复制和删除。入门文件读写涉及读取`example.txt`内容并追加文字;进阶文件复制展示如何使用`FileInputStream`和`FileOutputStream`从`source.txt`复制到`destination.txt`;精通文件删除则利用`File`的`delete()`方法安全删除`destination.txt`,前提检查文件存在且有权限。这些实例带你全面掌握Java文件操作基础!
39 0