【Java】I/O流的操作、认识使用

by img Microanswer 创建时间:Jun 11, 2019 6:10:28 PM 

标签: java IO 输入流 输出流 文件流



重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。

1、序

Java 编程中,I/O流的操作是必须学会的一项技能。无论是文件的复制粘贴,还是上传下载,无论是文本的读取写入,还是音视频的播放,都离不开I/O流的加持。I/O流如此重要,怎么能不了解它!本文将带你了解在Java里,如何自如的操控各种流。

2、理解

网上教程千千万,学校教材万千千,无数遍的使用水流来比喻Java里的I/O流。这无疑是一个非常恰当合适的比喻,但依旧不能解开每个人对流的疑问,当然也包括本文,不一定能被每个人理解,但希望哪怕一个人能够理解。

我也将使用水流来作比喻。现在,要将一个湖里的所有水全部转移到另一个水库里面,怎么办?当然是用抽水机啊,要么就用桶不同的挑嘛。为了能够更好地结合程序来理解,咱们假设选择了使用桶挑水过去的方案。

随即便产生了下面图案:

图1

有了这张图,对JavaI/O的理解就会更加简单一点。见名知意,为啥叫I/O流,是因为在java中,流的操作是通过 两个类的协同操作进行完成的。 这两个分别是:

  • I: InputStream 输入流
  • O: OutputStream 输出流

下面分别进行简述:

(1)、InputStream 输入流

输入流和输出流都是相对于程序来进行称呼的。脱离了程序,不能确定某个流就是输入或输出流,下图中“桶”就相当于是程序,那么输入流是什么,一目了然:

图2

可见,从湖泊A 到桶里的这部分水流,就可以说是输入流,但你不能单纯的认为就是这一通水就是输入流,而应该理解为:整个湖里的水,在放入桶的这个行为过程中,这时这些水对于这个桶来说是输入流。 所以同样的,在程序里,数据流在往程序里跑时这个数据流叫做输入流,承载这个数据跑到程序里面的类,是InputStream

(2)、OutputStream 输出流

与输入流相似,想必你看了上面的输入流,你对输出流可能也有那个概念了,只不过是反其道而行之的,如图所示:

图2

从桶里,往湖泊B倒水,这个行为过程中,所有的湖泊里的水,这可理解为输出流。在程序里,数据流在往程序外跑时,这个流叫做输出流,承载这个数据跑到外面去的类,是OutputStream

上诉的输入流,输出流都是对于程序来说的,把比作是这个运输水的程序,所以在阐述是都是以“桶”为对象来阐述的。

3、代码加入

(1)、InputStream 输入流

首先讲述InputStream输入流的代码应用。InputStream是一个abstract的抽象类,抽象类是不能直接实例化对象的,所以,Java为我们贴心的实现了许许多多的、用于处理各种场景的输入流类,比较常用的FileInputStream可见名知意,它是专门为读取文件而生的文件输入流承载对象。下面是文件输入流的使用案例:

// 为了保证程序的正常,请在D盘下建立文件:file.txt,文件内容:File Inputstream test.
// FileInputStream 在 java.io 包下,如果使用文本编辑器编写代码,记得导包:import java.io.FileInputStream;
public class Test {

    public static void main(String[] args) throws Exception {

        // 使用指定文件构建文件来构建文件输入流。
        FileInputStream fileInputStream = new FileInputStream("D:/file.txt");

        // 就如同上图要把一个湖泊里面的水全部传到另一个湖泊,中间借助了一个“桶”来帮助。
        // 并且,上面将“桶”比喻为整个程序。
        // 现在要摈弃:将“桶”比喻为整个程序。这个概念了。
        // 实际上“桶”只能充当程序里的一个变量。
        // 这个变量每次能容纳一定容量的数据。
        // 这里定义 datas 数组。是一个每次只能容纳4字节的“小桶”。
        byte[] datas = new byte[4];

        // 但是我们不能确定是不是每次重复用这只“桶”去舀水的时候都能把桶舀满。
        // 就比如上边图中,当桶儿把湖泊A里的水快舀完时,有一个人提着桶去舀最后一桶水,发小并不能舀满
        // 一整桶水,他索性舀了多少就带走多少了。
        // 而在程序里,程序是是的,它必须要你告诉他每次舀了多少水,
        // 特别是在最后一次没能舀满一“桶”水时,这个标记了舀了多少水的字段就必不可少了。
        // 定义一个字段表示每次舀了多少水。
        int size = 0;

        // 舀水一次可能完不成,毕竟我们桶只有这么大,所以要来来回回不同的舀,即循环进行。
        // 有人可能说,你定义一个超大的桶,不就可以一次搞定了。
        // 答:可以是可以,但是要明白,这个过程中是要把数据保存在你电脑的内存条上的,假如你的文件有100GB,你又定义了一个100GB的桶
        //    而你的电脑内存一共才16GB,程序一运行就会内存溢出的,直接报错了。
        // 所以桶的大小,要根据电脑内存有多大,读取行为发生的频率大不大来自己权衡的一个数值。

        // 下面开始循环读取
        while (size != -1) {
            // 当文件读取完成后, read 方法会返回 -1来表示已经读取完成了,所以,只要不是 -1 就一定表示数据还能继续读取。

            // 输入流的读取的方法会返回当前实际读取了多少数据的大小,正合适告诉我们舀了多少水。
            size = fileInputStream.read(datas);

            // 此时 datas 里面的数据就已经是舀到的数据了。
            // 拿到数据构建字符串:
            String str = new String(datas);

            // 输出结果:
            System.out.print(str);
        }

        // 当循环完成,也就表示读取完成,水舀完了。
        // 不要忘了最后调用输入流的关闭方法,你要是不调用关闭方法,就如同占着茅坑不拉屎是一样的道理。
        fileInputStream.close();
    }
}

运行这个程序,最后输出:File Inputstream test.

加上代码中的注释,相信理解起来非常简单。这是一个非常典型的代码编写方式,所有的输入流几乎都是这样的代码流程来进行数据的读取。

(2)、OutputStream 输出流

输入流是讲数据从程序的外部读入到程序的内部,那么输出流就是将数据从程序的内部输出到程序的外部,上面使用了一个简单的程序将外部文件“file.txt”的内容读取到了程序内部并显示,现在,我们要在程序内部设定好一个字符串,然后输出到文件中,同输入流一样,这里也采用文件相关的“FileOutputStream”文件输出流,来完成数据的输出。示列代码如下:

public class Test {
    public static void main(String[] args) throws Exception {
            // 准备好要输出的数据内容。
            String str = "File OutputStream test.";
    
            // 构建文件输出流:
            FileOutputStream fileOutputStream = new FileOutputStream("D:/outFile.txt");
    
            // 无论是流的输出,还是流的读取,它们操作的数据都是 byte 的二进制数据。
            // 而定义的准备用于输出数据是字符串,所以,需要先从字符串转换成 byte 二进制数据。
            byte[] datas = str.getBytes();
    
            // 将数据写到输出流。
            fileOutputStream.write(datas);
    
            // 这一步很重要。
            // 上面一行代码是将数据写入到了一个磁盘缓存区,内容其实(可能)还没有真的被写入到文件里。
            // 这一行代码的调用,可以强制的将数据从缓存区强制的写入的文件中,所以为了保证效果,务必每次在 “关闭” 输出流前
            // 记住要调用一次该方法。
            fileOutputStream.flush();
    
            // 最后不要忘了关闭输出流。
            fileOutputStream.close();
        }
}

运行程序,会在磁盘D盘下多出一个 outFile.txt 文件,打开这个文件,就可以看到文件内容:File OutputStream test.

你发现这个输出流并没有像输入流那样去循环的进行输出。是的,因为这里是为了让咱们快速理解输出流的使用方式,只是单独的进行单向输出,而且输出的只有几个字,我们能确定它的大小不是很大。为什么上面输入流读取的文件也很小,而我们也是用循环读取?因为我们只是在测试学习,知道这个文件不大,实际开发中,你永远不知道你要读取的文件有多大,对于我们不知道的事物,我们应该采取兼容性最大的程序代码方式去编写。所以输入时往往使用循环来读取。

现在了解了输入流,输出流,最有魅力的地方不是单独使用它们,而是将它们结合起来使用,达到更加强悍的功能。耳熟能详的的复制功能,就可以通过输入流输出流的相互配合完成。

4、结合使用

输入流输出流结合起来,学会I/O流的一个关键环节,任何输出流,输入流都可以相互结合,而不一定只能是文件的输入流对上文件的输出流。假如现在我们有一个 1GB 大小的 视频文件《video.mp4》在D盘根目录,要把它复制到 E盘下。示列代码如下:

public class Test {
    public static void main(String[] args) throws Exception {
    
            // 视频文件路径。
            String videoFile = "D:/video.mp4";
    
            // 建立输入流
            FileInputStream fileInputStream = new FileInputStream(videoFile);
    
            // 准备一个小“桶”每次舀这么多数据。
            byte[] datas = new byte[1024];
    
            // 每次舀水的量
            int size = 0;
    
            // ---------------------------------------------------------------
    
            // 准备输出文件路径
            String targetFile = "E:/video.pm4";
    
            // 建立输出流
            FileOutputStream fileOutputStream = new FileOutputStream(targetFile);
    
    
            // 循环的使用“小桶”舀。
            while (size != -1) {
    
                // 舀
                size = fileInputStream.read(datas);
    
                // 舀到数据,往目标倒。
                // 因为我们使用 size 标记每次舀了多少水,所以这里写的时候,也要讲“桶里”有多少水这个信息
                // 告诉写出的位置,这样就能拿在桶里拿到正确的数据量。
                fileOutputStream.write(datas, 0, size);
            }
    
            // 完成操作,关闭输入流,关闭输出流
            fileInputStream.close();
            fileOutputStream.close();
    
            // 提示完成
            System.out.println("复制完成!");
        }
}

执行程序,将会把D盘下的文件复制一份在E盘下,这样就完成了文件的复制,同时对文件输入流,输出流有了更深刻的印象。在Java里,流的操作中心都是围绕 read读的方法,和write写的方法,无论怎么样,只要搞清楚各个类是用来干嘛的以及明白数据流向的方向,就能轻松的理解流的操作了。

全文完, 转载请注明出处。 对你有帮助?不如赞一个吧:
发表评论(发表评论需要登录,你现在还没有登录。)
你需要先登录才可以评论。

评论列表 (0条)