咦咦咦,各位小可爱,我是你们的好伙伴 bug菌,今天又来给大家手把手教学Java SE系列知识点啦,赶紧出来哇,别躲起来啊,听我讲干货记得点点赞,赞多了我就更有动力讲得更欢哦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~
🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8
文章目录
- 前言
- 摘要
- 正文
- 简介
- 源代码解析
- InputStream类
- OutputStream类
- BufferedInputStream类
- BufferedOutputStream类
- 应用场景案例
- 文件复制
- 优缺点讲解
- 优点分析
- 缺点分析
- 类常用方法介绍
- InputStream类
- OutputStream类
- BufferedInputStream类
- BufferedOutputStream类
- 测试用例
- 测试代码
- 测试代码分析
- 测试结果
- 小结
- 全文总结
- 附录源码
- ☀️建议/推荐你
- 📣关于我
前言
首先,我们都知道输入输出流是Java SE开发中非常重要的一个组成部分,它们可以让程序与外部环境进行数据的交互,若无进行数据交换,则无法动态展示数据。借此,本文将深入探讨JavaSE中的输入输出流机制,并通过详细的源代码解析和实际应用场景案例,帮助大家全面理解JavaSE输入输出流的原理和用法,这对日常工作中的你或者即将步入职场的你都及其有帮助。
摘要
JavaSE针对输入输出流,它提供了非常丰富的类和方法,供日常处理各种类型的数据流动。通过输入流,我们可以读取外部数据到程序中,拿到数据再进一步操作等;而对应输出流,我们可以将程序中的数据输出到外部环境。这里大家需要理解和灵活运用 Java之输入输出流,这也是身为Java开发人员(程序猿)必备的技能,毕竟我入社会也是从这些知识点学起的。
正文
简介
首先,大家需要明确知道一点,JavaSE的输入输出流,它是属于面向字节的流,是基于抽象类InputStream
和OutputStream
以及相应的子类来实现的。数据流动的核心是字节流,而JavaSE中就提供了许多类和方法供大家可方便的操作字节流。下面我们将对JavaSE输入输出流的一些常用类进行源码解析及实战演示,以便于同学们加深理解。
源代码解析
InputStream类
InputStream
,首先它是一个抽象类,它定义了读取字节流的基本方法和属性,比如read()
、skip(long n)
、read(byte[] b, int off, int len)
、close()
等,除了这些基本的读取方法外,InputStream还提供了一些其他方法,比如mark(int readlimit)
和reset()
方法,允许在流中标记一个位置,并在需要时返回到该位置。它常用的子类有FileInputStream
和ByteArrayInputStream
。其中,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创建一个输出流,通过循环的方式依次读取输入流中的数据,并将其写入到输出流中,这样一个就可以实现文件的复制,是不是很简单呢?接着大家来参考下我写的代码。
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文件中,具体实现思路如下,不太懂的同学这里要着重听:
-
首先是引入了
java.io
中的相关类,用于文件的输入和输出操作(这里大家肯定都能懂)。 -
其次,定义一个main函数,在main方法中,通过
FileInputStream
来创建一个输入流对象in
,并将源文件"source.txt"作为参数传递给它。 -
同样地,再通过
FileOutputStream
来创建一个输出流对象out
,并将目标文件"target.txt"作为参数传递过去。 -
创建一个字节数组
buffer
,用于存储读取到的文件数据。 -
依次循环,使用
in.read(buffer)
方法来读取文件中的内容,并将读取到的字节数保存在length
变量中,是不是能懂。 -
然后呢,我们就要通过判断
length
的值,如果值不为-1,表示还有数据可以读取,没读取完,则使用out.write(buffer, 0, length)
方法将读取到的数据继续写入目标文件即可。 -
然后这里循环继续,那什么时候循环结束呢?很简单,就是判断
length
的值等于-1时,即文件读取完,这里直接跳过循环。 -
最后,记得关闭输入流和输出流(如果不关呢?其实大家也需要知道,如下我单独给大家做个拓展。)。
拓展一下:
如果不关闭输入流和输出流,可能会导致以下问题:
-
资源泄漏:输入流和输出流占用系统资源,如果不关闭它们,将导致资源无法被释放和复用,最终可能导致系统性能下降或崩溃。
-
内存泄漏:输入流和输出流需要占用一定的内存空间,如果不关闭它们,可能会导致内存泄漏问题,即占用的内存空间无法被释放,导致内存溢出。
-
数据丢失:如果未关闭输出流,可能会导致数据未完全写入到磁盘或目标文件中,从而导致数据丢失。
-
数据损坏:如果未关闭输出流和输入流,可能会导致数据在传输过程中发生错误或被破坏,导致数据的完整性受到影响。
-
接着,我们给个文字提示,输出"文件复制成功!"。
-
最后,捕获可能抛出的
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类,具体测试代码如下,在阅读我写的代码的同时,可以思考下我写的内容有何目的性,运用到了那些常规知识点,代码如下:
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;@Overridepublic 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,大家可以先看,从代码中理解这段代码干了件什么事,具体实现代码如下:
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();@Overridepublic void write(int b) throws IOException {buffer.write(b);}public byte[] getData() {return buffer.toByteArray();}
}
代码分析:
这里我给大家也解读下,理解这段代码实现了如何功能,大家请看:
- 首先,在
MyOutputStream
类中有一个成员变量buffer
,它是一个ByteArrayOutputStream
类型的对象。 ByteArrayOutputStream
类是一个在内存中创建字节数组缓冲区的输出流,可以将数据写入到内存中的字节数组中。MyOutputStream
类重写了write
方法,该方法将传入的字节写入到buffer
中。getData
方法返回buffer
的字节数组表示形式。
总之,这段代码定义了一个自定义的输出流类MyOutputStream
,它可以将数据写入到内存中的字节数组中,并可以获取该字节数组的数据,你们学废了么。
测试3
最后,再次给大家演示下,测试输入流和输出流类的实际操作。
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 SE,以全网最快速掌握Java语言,每章节源码均同步「Gitee」,你真值得拥有;学习就像滚雪球一样,越滚越大,带你指数级提升。
码字不易,如果这篇文章对你有所帮助,帮忙给bugj菌来个一键三连(关注、点赞、收藏) ,您的支持就是我坚持写作分享知识点传播技术的最大动力。
同时也推荐大家关注我的硬核公众号:「猿圈奇妙屋」 ;以第一手学习bug菌的首发干货,不仅能学习更多技术硬货,还可白嫖最新BAT大厂面试真题、4000G Pdf技术书籍、万份简历/PPT模板、技术文章Markdown文档等海量资料,你想要的我都有!
📣关于我
我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云2023年度十佳博主,掘金多年度人气作者Top40,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 20w+;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。