对IO流原理及、分类及IO模型的一个大概认识【Java基础题】
1.流的分类
-
根据操作数据单位分类:
- 字节流
- 字符流
一般来说,字符流会比字节流效率更高,因为1个字符一般比1个字节(8bit)大(it depends on 具体的编码规则,例如UTF-8中1个中文等于3个字节,1个英文等于1个字节)。可以理解为一个工人一次可以搬的东西更多,则效率更高。但是,它们的应用场景不同,使用字节流来操作二进制的文件(例如图片、视频等)可以保证对文件的无损操作。而字符流就天然地更适合文本文件。
-
根据数据流的流向分类:
- 输入流
- 输出流
输入、输出是以JAVA程序(运行时内存)的角度来定义的,从外部数据源(硬盘或网络传输等外部数据源)传输到JAVA程序是输入,从JAVA程序传输到外部是输出。
字节流根据输入输出又分为字节输入流,字节输出流。
字节输入流的抽象基类是InputStream、字节输出流的抽象基类是OutputStream。
字符流根据输入输出又分为字符输入流,字符输出流。
字符输入流的抽象基类是Reader、字符输出流的抽象基类是Writer。
这些抽象基类是不能被直接实例化的,只能用相应的子类来实例化对象。
(1)JAVA中IO流总共涉及40多个类,但是都是由:InputStream、Outputstream、Reader、Writer这四个抽象基类派生的
(2)由上述四种类派生出来的子类名称都是以其父类名作为其名称后缀。
JAVA IO体系图参考:Java的IO流体系-CSDN博客
-
根据流的角色分类:
- 节点流 Node Stream
- 处理流/包装流
节点流是可以从特定的数据源读写数据。是比较底层的流,因为它门是直接连接、操作数据源的,没有过多的处理和包装。包装流(处理流);对节点流进行包装,使它功能更加强大,既可以消除不同节点流的实现差异,也可以提供更方便的方法完成输入输出。
例如:BufferedReader继承Reader,它有一个Reader类型的成员变量in,这个in可以是Reader的任何子类,也就是BufferedReader是对这个节点流进行了一个封装。这里的设计模式属于修饰器模式。
处理流的功能主要主要体现在以下两个方面:
1.性能的提高:主要是以增加缓存的方式来提高输入输出的效率。
缓冲流的工作原理是预先从数据源读取数据存入内部缓冲区,或将输出数据暂存到内部缓冲区,直到缓冲区满了再一次性写入目的地。这样减少了与物理读写设备的交互次数,因为访问磁盘或网络资源通常比访问内存慢得多。缓冲流自动管理缓冲区,开发者不需要关心缓冲区的具体操作细节。
Java提供了四个主要的缓冲流类,分别用于不同的输入输出流:
BufferedInputStream
和BufferedOutputStream
:用于增加缓冲功能的字节流。它们分别包装任何类型的InputStream
和OutputStream
,提供了缓冲的读写能力。
BufferedReader
和BufferedWriter
:用于增加缓冲功能的字符流。它们分别包装任何类型的Reader
和Writer
,同样提供了缓冲的读写能力。
2.操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便。
其他节点流:
- 对象流:ObjectInputStream、ObjectOutputStream 能够将基本数据类型和对象进行序列化(以指定的格式写入文件,非文本,dat后缀)和反序列化(读取到程序内存中),传入对象流中的对象一定要实现序列化Serializable接口,否则就会报NotSerializableException。要注意的是write和read的顺序要一一对应。序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员。
- 标准输入输出:
- System.in 标准输入 它的编译类型是 InputStream 它的运行类型是BufferedInputStream 默认设备是键盘
- System.out 标准输出 它的编译类型是 PrintStream 运行时类型是 PrintStream 默认设备是屏幕
- 转换流:InputStreamReader、OutputStreamWriter---->用于将字节流转换成字符流,可用于解决中文乱码问题
- 打印流:打印流只有输出流没有输入流,PrintStream、PrintWriter
2.文件与流之间的关系
流是文件(数据)的通道
创建流对象和指定的文件关联起来后,就用这个流对象操作文件
相关的类:
1.FileInputStream 构造方法是需要与实际的文件或者File对象相关联起来(也就是一个FileInputStream对应一个文件?)
在Java中,
FileInputStream
是用于从文件中读取数据的一种输入流。当你创建一个FileInputStream
实例时,你必须指定一个文件源,这可以是一个文件路径的字符串,也可以是一个File
对象。如果指定的文件不存在,构造FileInputStream
时会抛出一个FileNotFoundException
,这意味着在创建流对象之前,必须确保文件确实存在且对当前程序可读。每个
FileInputStream
流对象和一个具体的文件是一对一对应的。这意味着流对象是专门为了从一个特定的文件中读取数据而打开的,它与那个文件绑定在一起。如果你需要从另一个文件读取数据,就必须创建一个新的FileInputStream
对象来指向新的文件。这种设计反映了流(Stream)的概念:流是一次性的、有方向的数据序列,它们连接程序和数据源或数据目标。在文件IO中,数据源或目标就是特定的文件。一旦流与数据源或目标建立了连接,它就只能用于那个特定的数据源或目标。如果要处理另一个文件,就需要开启一个新的流。
这不仅适用于
FileInputStream
,还适用于Java中的其他类型的流,如FileOutputStream
(用于写文件)、ObjectInputStream
和ObjectOutputStream
(用于读写对象)等。每次使用这些流时,都需要为它们指定一个具体的数据源或目标。举个例子,如果你先后需要从两个不同的文件中读取数据,你需要为每个文件创建各自的
FileInputStream
对象
2.FileOutputStream
3.FileReader 继承的是--->InputStreamReader--->Reader
4.FileWriter 继承的是--->OutputStreamWriter--->Writer
FileWriter调用write方法,要close或者flush才能写到指定的文件?为什么?
Write只是写到FileWriter对象,最后close才会保存到磁盘。FileWriter的底层是用的FileOutputStream,最终要写到磁盘用的是FileOutputStream的Write
其他具体的细节内容需要在后面的文章中拓展。
3.什么是IO模型?BIO NIO AIO?
IO模型:IO模型决定了数据如何在应用程序和网络间传输。IO模型是系统底层的支持,不同语言和框架对这些模型的支持和实现方式可能会有所不同。虽然IO模型最初是针对网络IO操作来描述的,但它们的基本原则和方法同样适用于本地IO操作。
BIO:BlockingIO,传统IO模型,同步阻塞IO--->可以大概对应的实现是java中java.io包
应用程序执行IO操作时,如果数据未准备好,它将一直等待数据准备完毕。一旦数据准备好了,再将数据从内核空间拷贝到用户空间,此过程中应用程序是阻塞的。
每个请求都需要在处理过程中阻塞等待。(比如socket在服务端一般是while(ture){server.accept()},没有接收到数据的时候,这个线程就会一直停在这里)
这意味着如果要同时处理多个请求,就需要为每个请求分配一个线程,随着请求数量的增加,线程数量也会相应增长,这在并发量较高时会占用大量的系统资源。
NIO:Non-BlockingIO,同步非阻塞IO--->可以大概对应的实现是java中java.nio包
应用程序执行IO操作时,如果数据未准备好,它不会等待数据准备完毕,而是直接返回一个错误码。应用程序可以继续做其他事情,但需要不断地检查数据是否准备好。
可以让你进行非阻塞的IO操作。核心组成部分包括Buffers(缓冲区)、Channels(通道)、Selectors(选择器)等。NIO允许单个线程管理多个IO操作。如果在非阻塞模式下没有IO操作可用,线程可以进行其他任务,而不是阻塞等待,从而提高了资源利用率。Java中的
java.nio
包提供了NIO功能,核心类包括ByteBuffer
、CharBuffer
、Selector
、ServerSocketChannel
、SocketChannel
等。
AIO: AsynchronousIO,异步非阻塞IO--->可以大概对应的实现是java中java.nio.channels中的异步类
应用程序发起IO操作后可以直接进行其他任务,当IO操作完成后,系统会通知应用程序IO操作已经完成。
AIO适用于连接数较多且连接时间较长的应用,Java中的
java.nio.channels.AsynchronousFileChannel
和java.nio.channels.AsynchronousSocketChannel
是AIO模型的两个例子。