深入理解JavaSE输入输出流:掌握数据流动的技巧!

咦咦咦,各位小可爱,我是你们的好伙伴------bug菌,今天我又来给同学们梳理Java SE的核心相关知识点咯,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~


🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!!

js 复制代码
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8

前言

首先,我们都知道输入输出流是Java SE开发中非常重要的一个组成部分,它们可以让程序与外部环境进行数据的交互,若无进行数据交换,则无法动态展示数据。借此,本文将深入探讨JavaSE中的输入输出流机制,并通过详细的源代码解析和实际应用场景案例,帮助大家全面理解JavaSE输入输出流的原理和用法,这对日常工作中的你或者即将步入职场的你都及其有帮助。

摘要

JavaSE针对输入输出流,它提供了非常丰富的类和方法,供日常处理各种类型的数据流动。通过输入流,我们可以读取外部数据到程序中,拿到数据再进一步操作等;而对应输出流,我们可以将程序中的数据输出到外部环境。这里大家需要理解和灵活运用 Java之输入输出流,这也是身为Java开发人员(程序猿)必备的技能,毕竟我入社会也是从这些知识点学起的。

正文

简介

首先,大家需要明确知道一点,JavaSE的输入输出流,它是属于面向字节的流,是基于抽象类InputStreamOutputStream以及相应的子类来实现的。数据流动的核心是字节流,而JavaSE中就提供了许多类和方法供大家可方便的操作字节流。下面我们将对JavaSE输入输出流的一些常用类进行源码解析及实战演示,以便于同学们加深理解。

源代码解析

InputStream类

InputStream,首先它是一个抽象类,它定义了读取字节流的基本方法和属性,比如read()skip(long n)read(byte[] b, int off, int len)close()等,除了这些基本的读取方法外,InputStream还提供了一些其他方法,比如mark(int readlimit)reset()方法,允许在流中标记一个位置,并在需要时返回到该位置。它常用的子类有FileInputStreamByteArrayInputStream。其中,FileInputStream可以从文件中读取数据,而ByteArrayInputStream则可以从字节数组中读取数据。

源码部分截图,如下示意:

需要注意的是,由于InputStream是一个抽象类,它是不能直接实例化,只能通过其子类来实现具体的输入流。

OutputStream类

OutputStream,InputStream类一样,也是一个抽象类;它定义了写入字节流的基本方法。常用的子类有FileOutputStream和ByteArrayOutputStream,我们可以使用它的子类来读取和写入字节流。其中,FileOutputStream可以将数据写入到文件中,而ByteArrayOutputStream则可以将数据写入到字节数组中,这点我们在后面会直接演示给大家看,这里就不详细赘述了。

对于OutputStream类,它的主要子类有,参考如下:

  • FileOutputStream:用于将数据写入文件。
  • ByteArrayOutputStream:用于将数据写入字节数组。
  • FilterOutputStream:用于添加过滤器功能,例如数据压缩或加密。
  • DataOutputStream:用于将基本数据类型写入输出流。
  • ObjectOutputStream:用于将对象写入输出流。

接着,OutputStream类定义了以下常用方法,仅供参考:

  • write(int b):将一个字节写入输出流。
  • write(byte[] b):将一个字节数组写入输出流。
  • write(byte[] b, int off, int len):将一个指定长度的字节数组的一部分写入输出流。
  • flush():刷新输出流,确保所有缓冲的字节都被写入输出流。
  • close():关闭输出流,释放相关的资源。

BufferedInputStream类

BufferedInputStream,它是InputStream类的装饰者类,它提供了带缓冲功能的读取方法,可以提高读取效率。BufferedInputStream内部维护了一个缓冲区,当需要读取数据时,先从缓冲区读取,如果缓冲区没有数据,则从底层流中读取新的数据。

BufferedOutputStream类

BufferedOutputStream,它是OutputStream的装饰者类,它提供了带缓冲功能的写入方法,可以提高写入效率。BufferedOutputStream内部维护了一个缓冲区,当需要写入数据时,先将数据写入到缓冲区,当缓冲区满了或者需要刷新时,再将缓冲区的数据写入到底层流中。

应用场景案例

接下来,我们就通过几个案例,由浅入深式的带着大家从理论过渡到实践中去,帮助大家更好的通过实践来理解理论知识点,"实践是检验真理的唯一标准!"毛爷爷曾言,接下来便开始实践部分。

文件复制

首先,我们先通过使用输入输出流,实现一个文件的复制功能,这里大家可以先思考一下,不太能想到的也没关系,可以接着看下边的。

实现思路大致步骤如下:首先我们可以使用FileInputStream类创建一个输入流,然后再使用FileOutputStream创建一个输出流,通过循环的方式依次读取输入流中的数据,并将其写入到输出流中,这样一个就可以实现文件的复制,是不是很简单呢?接着大家来参考下我写的代码。

java 复制代码
package com.demo.javase.day74;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @Author bug菌
 * @Date 2023-12-27 16:51
 */
public class FileCopy {
    
    public static void main(String[] args) {
        try {
            FileInputStream in = new FileInputStream("source.txt");
            FileOutputStream out = new FileOutputStream("target.txt");

            byte[] buffer = new byte[1024];
            int length;
            while ((length = in.read(buffer)) != -1) {
                out.write(buffer, 0, length);
            }

            in.close();
            out.close();
            System.out.println("文件复制成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

代码详细分析:

如上代码,它是一个文件复制的程序,实现功能就是将source.txt文件内容复制到target.txt文件中,具体实现思路如下,不太懂的同学这里要着重听:

  1. 首先是引入了java.io中的相关类,用于文件的输入和输出操作(这里大家肯定都能懂)。

  2. 其次,定义一个main函数,在main方法中,通过FileInputStream来创建一个输入流对象in,并将源文件"source.txt"作为参数传递给它。

  3. 同样地,再通过FileOutputStream来创建一个输出流对象out,并将目标文件"target.txt"作为参数传递过去。

  4. 创建一个字节数组buffer,用于存储读取到的文件数据。

  5. 依次循环,使用in.read(buffer)方法来读取文件中的内容,并将读取到的字节数保存在length变量中,是不是能懂。

  6. 然后呢,我们就要通过判断length的值,如果值不为-1,表示还有数据可以读取,没读取完,则使用out.write(buffer, 0, length)方法将读取到的数据继续写入目标文件即可。

  7. 然后这里循环继续,那什么时候循环结束呢?很简单,就是判断length的值等于-1时,即文件读取完,这里直接跳过循环。

  8. 最后,记得关闭输入流和输出流(如果不关呢?其实大家也需要知道,如下我单独给大家做个拓展。)。

拓展一下:

如果不关闭输入流和输出流,可能会导致以下问题:

  1. 资源泄漏:输入流和输出流占用系统资源,如果不关闭它们,将导致资源无法被释放和复用,最终可能导致系统性能下降或崩溃。

  2. 内存泄漏:输入流和输出流需要占用一定的内存空间,如果不关闭它们,可能会导致内存泄漏问题,即占用的内存空间无法被释放,导致内存溢出。

  3. 数据丢失:如果未关闭输出流,可能会导致数据未完全写入到磁盘或目标文件中,从而导致数据丢失。

  4. 数据损坏:如果未关闭输出流和输入流,可能会导致数据在传输过程中发生错误或被破坏,导致数据的完整性受到影响。

  5. 接着,我们给个文字提示,输出"文件复制成功!"。

  6. 最后,捕获可能抛出的IOException异常,并打印异常信息。

以上,就是该程序代码实现文件复制功能的思路了,总的来说就是使用了字节数组作为中间存储器,从输入流读取数据,然后通过输出流写入数据到目标文件中,非常的简单。

优缺点讲解

优点分析

  • 输入输出流,它提供了灵活的访问外部数据的方式,可以读取和写入各种类型的数据。
  • 输入输出流,它可以处理大数据量,通过缓冲区的使用,可以提高读写效率。
  • 输入输出流的接口和方法简单易用,对于开发人员来说学习成本较低。

缺点分析

  • 使用输入输出流操作文件需要处理异常,繁琐而且容易出错(这点实际操作中确实)。
  • 输入输出流只能处理字节流,对于字符数据需要进行字符编码的转换。

类常用方法介绍

接着,我给同学们梳理下InputStream、BufferedInputStream等类的常用方法介绍,方便大家对比及区分方法。

InputStream类

常用方法包括:

  • int read():读取一个字节的数据。
  • int read(byte[] buffer):读取一组字节的数据,并存储到指定的字节数组中。
  • void close():关闭输入流。
  • long skip(long n):跳过n个字节的数据,并返回实际跳过的字节数。如果已经到达流的末尾,则返回0。
  • int available():返回输入流中可以读取的字节数。

如上这些方法,可以用来从输入流中读取数据,并处理流的末尾、跳过字节、获取可读字节数等操作,具体演示我们往下看。

OutputStream类

常用方法包括:

  • write(int b):将一个字节写入输出流。
  • write(byte[] b):将一个字节数组的所有字节写入输出流。
  • write(byte[] b, int off, int len):将字节数组的一部分字节写入输出流,从偏移量off开始,写入len个字节。
  • flush():刷新输出流,将缓冲区中的数据强制写入输出流。
  • close():关闭输出流,释放与之关联的系统资源。
  • flush()和close()方法都会自动调用write()方法将缓冲区中的数据写入输出流。

BufferedInputStream类

常用方法包括:

  • public int read() throws IOException:从输入流中读取一个字节,并返回读取的字节的整数表示。如果已到达流的末尾,则返回-1。

  • public int read(byte[] b, int off, int len) throws IOException:从输入流中读取最多len个字节到字节数组b的指定偏移量off处,返回实际读取的字节数。如果已到达流的末尾,则返回-1。

  • public long skip(long n) throws IOException:从输入流中跳过n个字节的数据,并返回实际跳过的字节数。

  • public int available() throws IOException:返回可以从输入流中读取的字节数。

  • public synchronized void mark(int readlimit):在当前位置设置一个标记点。

  • public synchronized void reset() throws IOException:将流重置到上次设置的标记点。

BufferedOutputStream类

常用方法包括:

  • void write(int b):写入一个字节的数据。
  • void write(byte[] buffer):写入一组字节的数据。
  • void flush():刷新输出流,将缓冲区的数据写入到底层流中。
  • void close():关闭输出流。

测试用例

如下,到了大家最激动人心的阶段,实战环节,检验下大家理论基础到底学习及掌握的如何?所以,这里我给大家通过几个测试用例,尽可能的使用到它们的一些常规方法,运用到实际代码中去,以辅助大家理解。

测试代码

这里我定义了一个MyInputStream类,具体测试代码如下,在阅读我写的代码的同时,可以思考下我写的内容有何目的性,运用到了那些常规知识点,代码如下:

java 复制代码
package com.demo.javase.day74;

import java.io.IOException;
import java.io.InputStream;

/**
 * @Author bug菌
 * @Date 2023-12-27 16:48
 */
public class MyInputStream extends InputStream {
    private byte[] data = {1, 2, 3, 4, 5};
    private int pos = 0;

    @Override
    public int read() throws IOException {
        if (pos < data.length) {
            return data[pos++];
        } else {
            return -1;
        }
    }
}

不知道大家看懂了我写的这段测试代码没,其实很简单的,无非就是模拟读取数据流。接着我来给大家详细分析一下,这段代码的完整思路

代码分析:

我先自定义了一个的InputStream类MyInputStream,继承自java.io.InputStream。该类重写了InputStream的read()方法,实现了从一个固定的byte数组data中读取数据的功能。

在MyInputStream类中,定义了一个私有的byte数组data,用于存储数据。还定义了一个私有的整型变量pos,用于记录读取data中的位置。

在重写的read()方法中,首先判断pos是否小于data的长度,如果是,表示还有数据可以读取,就返回data[pos]对应的字节,并将pos++。如果pos等于或大于data的长度,表示已经读取完所有数据,返回-1。

总之,则这个MyInputStream类功能就是用于模拟读取数据流,在每次调用read()方法都可以读取data数组中的下一个字节,仅此而已,你们也可以拓展下。

接着,我再定义一个MyOutputStream.java,大家可以先看,从代码中理解这段代码干了件什么事,具体实现代码如下:

java 复制代码
package com.demo.javase.day74;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class MyOutputStream extends OutputStream {
    private ByteArrayOutputStream buffer = new ByteArrayOutputStream();

    @Override
    public void write(int b) throws IOException {
        buffer.write(b);
    }

    public byte[] getData() {
        return buffer.toByteArray();
    }
}

代码分析:

这里我给大家也解读下,理解这段代码实现了如何功能,大家请看:

  1. 首先,在 MyOutputStream类中有一个成员变量buffer,它是一个ByteArrayOutputStream类型的对象。
  2. ByteArrayOutputStream类是一个在内存中创建字节数组缓冲区的输出流,可以将数据写入到内存中的字节数组中。
  3. MyOutputStream类重写了write方法,该方法将传入的字节写入到buffer中。
  4. getData方法返回buffer的字节数组表示形式。

总之,这段代码定义了一个自定义的输出流类MyOutputStream,它可以将数据写入到内存中的字节数组中,并可以获取该字节数组的数据,你们学废了么。

测试3

最后,再次给大家演示下,测试输入流和输出流类的实际操作。

java 复制代码
package com.demo.javase.day74;

import java.io.IOException;

/**
 * @Author bug菌
 * @Date 2023-12-27 16:49
 */
public class Test {

    public static void main(String[] args) {
        try {
            MyInputStream in = new MyInputStream();
            MyOutputStream out = new MyOutputStream();

            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }

            in.close();
            out.close();

            byte[] data = out.getData();
            for (byte d : data) {
                System.out.print(d + " ");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试代码分析

根据如上测试用例,在此我给大家进行深入详细的解读一下测试代码,以便于更多的同学能够理解并加深印象。

这个小程序,我主要是为了测试自定义的输入流和输出流类。在主方法中,创建一个MyInputStream对象和一个MyOutputStream对象。然后通过循环,从输入流中读取字节,并将其写入到输出流中。在读取和写入的过程中,如果读取的字节等于-1,即表示输入流已经读取完毕,循环结束。接着调用输入流和输出流的close()方法关闭流。

然后,通过调用输出流的getData()方法获取输出流中的数据,并将数据以字节数组的形式存储在data数组中。最后,通过遍历data数组,将每个字节打印输出。

需要注意的是,在上述代码中使用了try-catch块来捕获IOException异常。如果在读写过程中发生异常,将会打印异常信息。

测试结果

根据如上的测试用例,作者在本地进行测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加其他的测试数据或测试方法,以便于进行熟练学习以此加深知识点的理解。

小结

在此,给大家做个小结,本文着重演示了并深入理解JavaSE输入输出流的原理和用法,通过源代码解析和实际应用案例的介绍,可以帮助大家更好地掌握输入输出流的知识。输入输出流在Java开发中非常重要,对于处理外部数据流动具有重要作用。

全文总结

总而言之,JavaSE输入输出流作为Java开发中不可或缺的部分,通过对InputStream、OutputStream、BufferedInputStream和BufferedOutputStream等类的源代码解析,我们可以了解了它们的基本原理和用法。通过如上的应用场景案例讲解,我们能够更加清楚输入输出流在实际开发中的应用价值。掌握输入输出流的知识,对于Java开发人员来说是非常重要的,所以说,大家听我讲,一定可以轻松掌握。

...

好啦,这期的内容就基本接近尾声啦,若你想学习更多,可以参考这篇专栏总结《「滚雪球学Java」教程导航帖》,本专栏致力打造最硬核 Java 零基础系列学习内容,🚀打造全网精品硬核专栏,带你直线超车;欢迎大家订阅持续学习。

附录源码

如上涉及所有源码均已上传同步在「Gitee」,提供给同学们一对一参考学习,辅助你更迅速的掌握。

☀️建议/推荐你


无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学Java」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门Java编程,就像滚雪球一样,越滚越大,指数级提升。

最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。
  同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。

📣关于我

我是bug菌,CSDN | 掘金 | infoQ | 51CTO 等社区博客专家,历届博客之星Top30,掘金年度人气作者Top40,51CTO年度博主Top12,华为云 | 阿里云| 腾讯云等社区优质创作者,全网粉丝合计15w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板等海量资料。


本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!"

相关推荐
爱上语文1 分钟前
Java LeetCode每日一题
java·开发语言·leetcode
bug菌24 分钟前
Java GUI编程进阶:多线程与并发处理的实战指南
java·后端·java ee
程序猿小D36 分钟前
第二百六十九节 JPA教程 - JPA查询OrderBy两个属性示例
java·开发语言·数据库·windows·jpa
极客先躯1 小时前
高级java每日一道面试题-2024年10月3日-分布式篇-分布式系统中的容错策略都有哪些?
java·分布式·版本控制·共识算法·超时重试·心跳检测·容错策略
夜月行者2 小时前
如何使用ssm实现基于SSM的宠物服务平台的设计与实现+vue
java·后端·ssm
程序猿小D2 小时前
第二百六十七节 JPA教程 - JPA查询AND条件示例
java·开发语言·前端·数据库·windows·python·jpa
Yvemil72 小时前
RabbitMQ 入门到精通指南
开发语言·后端·ruby
sdg_advance2 小时前
Spring Cloud之OpenFeign的具体实践
后端·spring cloud·openfeign
潘多编程2 小时前
Java中的状态机实现:使用Spring State Machine管理复杂状态流转
java·开发语言·spring
_阿伟_2 小时前
SpringMVC
java·spring