IO流基本认识
Java中的I/O(Input/Output)流是用于处理输入和输出的机制。I/O 流以字节(byte)为基本单位,提供了一种灵活的方式来读取和写入数据。I/O 流分为输入流和输出流,根据数据的流向分为输入和输出。
Java的I/O流主要分为两大类:字节流和字符流。
字节流用于处理原始的二进制数据,而字符流用于处理文本数据。
字节流
- InputStream 和 OutputStream: 是所有字节输入流和输出流的抽象基类。它们分别用于读取和写入字节。
- FileInputStream 和 FileOutputStream: 用于从文件中读取字节和向文件中写入字节。
- ByteArrayInputStream 和 ByteArrayOutputStream: 分别用于从字节数组中读取数据和将数据写入字节数组。
字符流
- Reader 和 Writer: 是所有字符输入流和输出流的抽象基类。它们分别用于读取和写入字符。
- FileReader 和 FileWriter: 用于从文件中读取字符和向文件中写入字符。
- BufferedReader 和 BufferedWriter: 用于提供缓冲区,提高读取和写入的效率。
高级流
- ObjectInputStream 和 ObjectOutputStream: 用于读取和写入对象。可以序列化和反序列化对象。
- DataInputStream 和 DataOutputStream: 用于读取和写入基本数据类型。
谈一下你对IO流的基本理解
从字符流和字节流两方面回答包括输入输出以及两者对比
数据流的基本概念
在Java中,把这些不同类型的输入、输出源抽象为流(Stream),其中输入或输出的数据称为数据流 (Data Stream),用统一的接口来表示。
IO流的分类
数据流是指一组有顺序的、有起点和终点的字节集合。
按照流的流向分,可以分为输入流和输出流。注意:这里的输入、输出是针对程序来说的。
输出:把程序(内存)中的内容输出到磁盘、光盘等存储设备中。
输入:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
按处理数据单位不同分为字节流和字符流。
字节流:每次读取(写出)一个字节,当传输的资源文件有中 文时,就会出现乱码。
字符流:每次读取(写出)两个字节,有中文时,使用该流就可以正确传输显示中文。
1字符 = 2字节; 1字节(byte) = 8位(bit); 一个汉字占两个字节长度。
按照流的角色划分为节点流和处理流。
节点流:从或向一个特定的地方(节点)读写数据。如 FileInputStream。
处理流(包装流):是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader。
处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多 次包装,称为流的链接。
注意:一个IO流可以既是输入流又是字节流又或是以其他方式分类的流类型,是不冲突的。比如 FileInputStream,它既是输入流又是字节流还是文件节点流。
Java IO 流有4个抽象基类:
- 输入字节流:
InputStream - 输入字符流:
Reader - 输出字节流:
OutputStream - 输出字符流:
Writer
其他流都是继承于这四大基类的。下图是Java IO 流的整体架构图:

知道了 IO 流有这么多分类,那我们在使用的时候应该怎么选择呢?比如什么时候用输出流?什么时候 用字节流?可以根据下面三步选择适合自己的流:
首先自己要知道是选择输入流还是输出流。这就要根据自己的情况决定,如果想从程序写东西到别的地 方,那么就选择输入流,反之就选输出流;然后考虑你传输数据时,是每次传一个字节还是两个字节, 每次传输一个字节就选字节流,如果存在中文,那肯定就要选字符流了。通过前面两步就可以选出一个 合适的节点流了,比如字节输入流 InputStream,如果要在此基础上增强功能,那么就在处理流中选择 一个合适的即可。
字节输入流
java.io 包下所有的字节输入流都继承自 InputStream,并且实现了其中的方法。InputStream 中提供的主要数据操作方法如下:
- int read():从输入流中读取一个字节的二进制数据。
- int read(byte[] b):将多个字节读到数组中,填满 整个数组
- int read(byte[] b, int off, int len):从输入流中读取长度为 len 的数据,从数组 b 中下标为 off 的位置开始放置读入的数据,读完返回读取的字节数。v
- oid close():关闭数据流。
- int available(): 返回目前可以从数据流中读取的字节数(但实际的读操作所读得的字节数可能大于该返回值)
- long skip(long l):跳过数据流中指定数量的字节不读取,返回值表示实际跳过的字节数。对数据流中字节的 读取通常是按从头到尾顺序进行的,
- 如果需要以反方向读取,则需要使用回推(Push Back)操作。在 支持回推操作的数据流中经常用到如下几个方法:
- boolean markSupported():用于测试数据流是否支持回推操作,当一个数据流支持 mark() 和 reset() 方法时,返回 true,否则返回 false。
- void mark(int readlimit):用于标记数据流的当前位置,并划出 一个缓冲区,其大小至少为指定参数的大小。
- void reset():将输入流重新定位到对此流最后调用 mark() 方法时的位置。
字节输入流 InputStream 有很多子类,日常开发中,经常使用的一些类见下 图:

ByteArrayInputStream:字节数组输入流,该类的功能就是从字节数组 byte[] 中进行以字节为单位的 读取,也就是将资源文件都以字节形式存入到该类中的字节数组中去,我们拿数据也是从这个字节数组 中拿。
PipedInputStream:管道字节输入流,它和 PipedOutputStream 一起使用,能实现多线程间的 管道通信。
FilterInputStream:装饰者模式中充当装饰者的角色,具体的装饰者都要继承它,所以在该 类的子类下都是用来装饰别的流的,也就是处理类。
BufferedInputStream:缓冲流,对处理流进行装 饰、增强,内部会有一个缓冲区,用来存放字节,每次都是将缓冲区存满然后发送,而不是一个字节或 两个字节这样发送,效率更高。
DataInputStream:数据输入流,用来装饰其他输入流,它允许通过数 据流来读写Java基本类型。
FileInputStream:文件输入流,通常用于对文件进行读取操作。File:对指 定目录的文件进行操作。
ObjectInputStream:对象输入流,用来提供对“基本数据或对象”的持久存 储。通俗点讲,就是能直接传输Java对象(序列化、反序列化用)。
字节输出流
与字节输入流类似,java.io 包下所有字节输出流大多是从抽象类 OutputStream 继承而来的。
OutputStream 提供的主要数据操作方法:
void write(int i):将字节 i 写入到数据流中,它只输出所读入参数的最低 8 位,该方法是抽象方法,需 要在其输出流子类中加以实现,然后才能使用。
void write(byte[] b):将数组 b 中的全部 b.length 个字 节写入数据流。
void write(byte[] b, int off, int len):将数组 b 中从下标 off 开始的 len 个字节写入数据 流。元素 b[off] 是此操作写入的第一个字节,b[off + len - 1] 是此操作写入的最后一个字节。
void close():关闭输出流。
void flush():刷新此输出流并强制写出所有缓冲的输出字节。为了加快数据传输 速度,提高数据输出效率,又是输出数据流会在提交数据之前把所要输出的数据先暂时保存在内存缓冲 区中,然后成批进行输出,每次传输过程都以某特定数据长度为单位进行传输,在这种方式下,数据的 末尾一般都会有一部分数据由于数量不够一个批次,而存留在缓冲区里,调用 flush() 方法可以将这部 分数据强制提交。
IO 中输出字节流的继承图可见下图:

作用可以参考上面字节输入流中的各个子类的介绍。
字符流
字符流
从JDK1.1开始,java.io 包中加入了专门用于字符流处理的类,它们是以Reader和Writer为基础派生的 一系列类。
同其他程序设计语言使用ASCII字符集不同,Java使用Unicode字符集来表示字符串和字符。ASCII字符 集以一个字节(8bit)表示一个字符,可以认为一个字符就是一个字节(byte)。但Java使用的 Unicode是一种大字符集,用两个字节(16bit)来表示一个字符,这时字节与字符就不再相同。为了实 现与其他程序语言及不同平台的交互,Java提供一种新的数据流处理方案,称作读者(Reader)和写者 (Writer)。
字符输入流
Reader是所有的输入字符流的父类,它是一个抽象类。
Reader及其一些常用子类:
CharReader和SringReader是两种基本的介质流,它们分别将Char数组、String中读取数据。
PipedReader 是从与其它线程共用的管道中读取数据。
BufferedReader很明显是一个装饰器,它和其他子类负责装饰其他Reader对象。
FilterReader是所有自定义具体装饰流的父类,其子类PushBackReader对Reader对象进行装饰,会增 加一个行号。
InputStreamReader是其中最重要的一个,用来在字节输入流和字符输入流之间作为中介,可以将字节 输入流转换为字符输入流。
FileReader 可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream 转 变为Reader 的方法。
Reader 中各个类的用途和使用方法基本和InputStream 中的类使用一致。
字符输出流
Writer是所有的输出字符流的父类,它是一个抽象类。
Writer及其一些常用子类:
CharWriter、StringWriter 是两种基本的介质流,它们分别向Char 数组、String 中写入数据。
PipedWriter 是向与其它线程共用的管道中写入数据。BufferedWriter 是一个装饰器为Writer 提供缓冲 功能。
PrintWriter 和PrintStream 极其类似,功能和使用也非常相似。OutputStreamWriter是其中最重要的 一个,用来在字节输出流和字符输出流之间作为中介,可以将字节输出流转换为字符输出流。
FileWriter 可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将OutputStream转变 为Writer 的方法。
Writer 中各个类的用途和使用方法基本和OutputStream 中的类使用一致。
字节流和字符流的区别
1、要把一片二进制数据数据逐一输出到某个设备中,或者从某个设备中逐一读取一片二进制数据,不 管输入输出设备是什么,我们要用统一的方式来完成这些操作,用一种抽象的方式进行描述,这个抽象 描述方式起名为IO流,对应的抽象类为OutputStream和InputStream ,不同的实现类就代表不同的输 入和输出设备,它们都是针对字节进行操作的。
2、在应用中,经常要完全是字符的一段文本输出去或读进来,用字节流可以吗?计算机中的一切最终 都是二进制的字节形式存在。对于“中国”这些字符,首先要得到其对应的字节,然后将字节写入到输出 流。读取时,首先读到的是字节,可是我们要把它显示为字符,我们需要将字节转换成字符。由于这样 的需求很广泛,人家专门提供了字符流的包装类。
3、底层设备永远只接受字节数据,有时候要写字符串到底层设备,需要将字符串转成字节再进行写 入。字符流是字节流的包装,字符流则是直接接受字符串,它内部将串转成字节,再写入底层设备,这 为我们向IO设别写入或读取字符串提供了一点点方便。
4、字符向字节转换时,要注意编码的问题,因为字符串转成字节数组,其实是转成该字符的某种编码 的字节形式,读取也是反之的道理。
字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的;而字符流在操作时使用了缓冲 区,通过缓冲区再操作文件。

使用场景
字节流一般用来处理图像,视频,以及PPT,Word类型的文件。
字符流一般用于处理纯文本类型的文 件,如TXT文件等。
字节流可以用来处理纯文本文件,但是字符流不能用于处理图像视频等非文本类型 的文件。
I/O模型
- BIO(Blocking I/O阻塞I/O模型):
BIO 属于同步阻塞 IO 模型,读取或写入数据时,线程将一直等待,直到数据准备就绪或者写入操作完成, 但在高并发环境下可能导致性能问题,因为线程在等待 I/O 操作完成时被阻塞,无法执行其他任务。
- NIO(Non-blocking I/O):
在非阻塞 I/O 模型中,线程执行一个 I/O 操作时不会等待,而是继续执行其他任务, 这需要通过轮询(polling)或者使用回调函数等机制来检查 I/O 操作是否完成。
这种模型相对于阻塞 I/O 可以更好地支持并发,但轮询的方式可能会导致 CPU 资源的浪费。
- IO多路复用
I/O 多路复用模型使用了操作系统提供的选择器(Selector)机制,例如 Java 中的 Selector 类。通过选择器,一个线程可以监听多个通道上的 I/O 事件,从而在单线程中处理多个连接。
- AIO(Asynchronous I/O)
异步I/O允许程序在执行I/O操作时继续执行其他任务,而不需要等待I/O操作完成。在Java中,AIO主要是通过Java NIO.2中的AsynchronousChannel和CompletionHandler接口来实现的。
AIO 允许程序发起一个I/O操作,并在操作完成时得到通知。在这个过程中,程序可以继续执行其他任务而无需等待I/O操作完成,当操作完成之后,进行回调。
什么是Java中的BIO、NIO和AIO?有哪些区别?(考点:Java I/O模型) 【中等】
BIO(Blocking I/O)
- 概念:传统的Java I/O模型,基于流(Stream)进行数据的读写操作。
- 特点:- 阻塞式:每个I/O操作都会阻塞线程,直到操作完成。
- 简单易用,适用于连接数较少的场景。
- 应用场景:适合处理较少并发连接的应用,如传统的文件读取或简单的服务器。
NIO(Non-blocking I/O)
- 概念:Java 1.4引入的非阻塞I/O模型,基于通道(Channel)和缓冲区(Buffer)进行数据传输。
- 特点:- 非阻塞式:线程可以在等待I/O操作时执行其他任务。
- 支持选择器(Selector),可同时处理多个通道。
- 更高的性能和扩展性,适合高并发场景。
- 应用场景:适用于需要处理大量并发连接的服务器,如高性能网络服务器。
AIO(Asynchronous I/O)
- 概念:Java 7引入的异步I/O模型,允许I/O操作以异步方式进行,线程无需等待操作完成。
- 特点:- 完全异步:I/O操作完成后,通过回调或Future机制通知应用程序。
- 更高的资源利用率,适合极高并发和低延迟的应用。
- 复杂度较高,实现难度大于BIO和NIO。
- 应用场景:适用于需要极高性能和资源利用率的系统,如大型分布式系统和实时数据处理。
主要区别
| 特性 | BIO | NIO | AIO |
|---|---|---|---|
| 模型 | 阻塞式 | 非阻塞式 | 异步非阻塞式 |
| 线程使用 | 每个连接一个线程 | 单线程或少量线程处理多个连接 | 线程无需等待I/O完成 |
| 复杂度 | 简单 | 中等 | 较高 |
| 性能 | 适合低并发 | 适合中高并发 | 适合极高并发和低延迟 |
| API支持 | java.io包 | java.nio包 | java.nio.channels.AsynchronousChannel |
- Java中BufferedReader比BufferedInputStream快吗?为什么?(考点:I/O性能优化) 【简单】
比较BufferedReader和BufferedInputStream的性能
- BufferedReader:- 主要用于字符输入,基于字符流(Reader)。
- 内部使用缓冲区来减少实际读取操作的次数,提高读取字符数据的效率。
- BufferedInputStream:- 主要用于字节输入,基于字节流(InputStream)。
- 同样使用缓冲区来优化读取字节数据的性能。
性能差异
- 用途不同:BufferedReader用于处理字符数据,而BufferedInputStream用于处理字节数据。它们的性能取决于具体的使用场景和数据类型。
- 字符处理:在处理文本数据时,BufferedReader通常表现得更快,因为它优化了字符读取和转换的过程,减少了字符编码转换的开销。
- 字节处理:对于纯字节数据,BufferedInputStream可能更高效,因为它避免了字符转换的额外开销。
结论 在处理字符数据时,BufferedReader通常比BufferedInputStream更快,因为它专为字符流优化,减少了不必要的转换开销。然而,在处理字节数据时,BufferedInputStream更为合适和高效。
介绍一下Java的序列化与反序列化
序列化机制可以将对象转换成字节序列,这些字节序列可以保存在磁盘上,也可以在网络中传输,并允许程序将这些字节序列再次恢复成原来的对象。其中,对象的序列化(Serialize),是指将一个Java对象写入IO流中,对象的反序列化(Deserialize),则是指从IO流中恢复该Java对象。
若对象要支持序列化机制,则它的类需要实现Serializable接口,该接口是一个标记接口,它没有提供任何方法,只是标明该类是可以序列化的,Java的很多类已经实现了Serializable接口,如包装类、String、Date等。
若要实现序列化,则需要使用对象流ObjectInputStream和ObjectOutputStream。其中,在序列化时需要调用ObjectOutputStream对象的writeObject()方法,以输出对象序列。在反序列化时需要调用ObjectInputStream对象的readObject()方法,将对象序列恢复为对象。

3031

被折叠的 条评论
为什么被折叠?



