Java IO学习小结
IO操作算是java的一个基本知识点了,比如我们常见的网络IO,文件读写等,而且这一块基本上大家并不会频繁的来操作,大多会用一些封装得好用的工具来代替,某些时候真的需要做的时候,基本上也很难一下子很顺利的写完
本篇将主要集中在:
- 几种IO分类
- 字节IO和字符IO的转换
- 装饰类IO是什么
- 序列化的实现机制
I. IO分类
Java中的IO操作,一般都是基于流进行,以输入输出流进行分类可以分为
字节流:InputStream, OutputStream
字符流:Reader, Writer
从数据源进行分类,又可以区分为:
- 文件读写: FileInputStream, FileOutputStream,FileReader
- 字符串流: StringBufferInputStream, StringReader
- 数组流: ByteArrayInputStream
- 网络: Socket
从去向分析,就是输入流和输出流:
- 输入流: xxxInputStream, xxxReader
- 输出流: xxxOutputStream, xxxWriter
II. IO流的基本知识点
IO操作,最主要的一点就是需要清晰如何使用了,一般来讲,网络或文件读写,都是基于字节进行交互的,但实际上为了能友好的读取或写入信息,一般都是字符方式,由字符到字节之间则需要一个编码规则的映射
所有,很简单就可以知晓,这里至少有三种不同应用场景的类和一种设计模式
- 字节流
- 字符流
- 字节映射字符流 (适配器模式)
1. 基本使用
以常见的文件读写为例进行说明,一般的读写操作是啥样的
1 | @Test |
上面的流程基本上就下面五步:
- 创建一个File对象
- 包装为IO流:
new FileInputStream(new File("test.d"))
- 字节流转换为字符流:
new InputStreamReader(input, Charset.forName("utf-8"))
- 字符流使用缓冲修饰,支持读一行
- 关闭流
基本上上面这个套路是比较适合常见的IO操作的,那么为什么是这么个流程呢?这个流程中又有些什么有意思的东西呢?
2. IO流使用姿势分析
声明:下面这一段纯属个人理解,如有不误,请不吝指正
对操作系统而言(网络传输也一样),他们关心的是一个字节一个字节的行为,所以与它们打交道,就需要遵循他们的规则来办事,使用字节来操作,所以最开始我们都是采用字节流来定义与数据源的交互规则
然而字节流虽好,但是所有的数据最终都是为人服务的,而由于客观原因,不同的国家有不同的语言,为了面向人类的友好,出现了字符这个东西,所以一般我们的操作也更多的是基于字符进行操作
上面这两个出现之后,一个自然而然的东西–>InputStreamReader
就出现了,作为字节和字符转换的桥梁
上面这三个可算是一个基本的操作流程了,可以满足我们的输入输出需求,但依然不是特别友好,比如一个一个字符的操作不友好啊,比如我希望过滤某些东西,或者做其他的一些辅助操作之类的,因此就出现了各种装饰流,主要就是提供一些服务方法,增强接口的易用性
简单的使用姿势流图:
1 | graph TD |
3. 常见IO类
a. 基本介质流
与提供读写数据的数据源打交道的流
- FileInputStream : 数据源为文件
- ByteArrayInputStream: 数据源为byte数组
- StringBufferInputStream: StringBuffer作为数据源,已废弃
- PipedInputStream:管道,多线程协作使用
使用姿势
1 | // 数组 |
b. 字节字符转换
两个:
- InputStreamReader
- OutputStreamWriter
使用姿势也比较简单,标准的适配器模式,用构造方法传参即可,下面给出一个demo,结合上面的,实现将数组流的数据写入的文件(说明,下面的实现更多的是为了演示这种用法,实际编码中有较大的优化空间)
1 | byte[] bytes = new byte[]{'a', 'b', 'c', '1', '2'}; |
c. 装饰流
主要是基于装饰模式,对IO流,增强一些操作方法,最明显的是特征是他们继承自 FilterInputStream
,比如我们最常用的BufferedInputStream
和 DataInputStream
- 基本数据类型读写流:
DataInputStream
- 8中基本类型的读入写出流,没什么好说的
- 缓存流:
BufferedInputStream
- 为了提升性能引入,避免频繁的磁盘读写
- 对象流:
ObjectInputStream
- 虽然没有继承自
FilterInputStream
,依然把它作为装饰流,后面单独说
- 虽然没有继承自
这里面有必要好好的说到以下 BufferedInputStream
,缓冲流的底层原理是什么,又有什么好处,为什么会引入这个?
API解释:在创建 BufferedInputStream时,会创建一个内部缓冲区数组。在读取流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。
也就是说,Buffered类初始化时会创建一个较大的byte数组,一次性从底层输入流中读取多个字节来填充byte数组,当程序读取一个或多个字节时,可直接从byte数组中获取,当内存中的byte读取完后,会再次用底层输入流填充缓冲区数组。
以文件读写为例,在实际的落盘和读取过程,这个是加锁阻塞的操作,如果每次都只读1个字节,在大量的读写情况下,这个性能就很脆了
加上这个缓冲之后呢?在一次加锁的过程中,尽量多的读取数据,放在本地内存,这样即便是在使用的地方一个一个的获取,也不会与其他的任务产生竞争,所有有效的提高了效率
III. 序列化
在平常的工作中,基本上离不开序列化了,比如web应用中,最常见的基于JSON的数据结构交互,就算是一种JSON序列化方式;当然我们这里谈的序列化主要是JDK原生的方式
1. 背景
出现序列化需求的背景比较清晰,我们希望某些对象可以更方便的共享,如即便程序over了,它们可以以某种方式存在(比如写在一个临时文件中),如RPC中传参和返回等
使用时注意事项
- 一个类需要序列化,需要实现
Serializable
这个空接口,会告知编译器对它进行特殊处理 - 一个友好的习惯是,在可序列化的类中,定义一个
static final long serialVersionUID
- transient变量可标识出来不被序列化的字段
2. 实现
给出一个使用case
1 | public static class Demo implements Serializable { |
输出结果
1 | -------- over ---------- |
对应的文本内容
IV. 小结
io可以说是java中最基本的操作方式了,jdk本身设计是比较优雅的,从上面简单的学习就看到了两种设计模式:适配器+装饰器
提到IO,就不能跳过NIO,特别是在实际的工作中,用得非常多的网络交互,现在基本上是Netty占据主流,这个里面又是有不少东西可以学习的,放在下一篇,下面简单回顾下IO流的认知与使用
1. 流分类:
- 字节流: InputStream , OutputStream
- 字符流: Reader, Writer
2. 从设计角度分类
- 介质流:直接与数据源打交道 FileInputStream, StringBufferInputStream(已经不用,改StringBufferReader), ByteArrayInputStream (网络传输的二进制流,基本就是这个)
- 转换: 字节流和字符流的转换 InputStreamReader, OutputStreamWriter
- 装饰流: BufferedInputStream, DataInputStream, ObjectInputStream
3. 序列化和反序列化
序列化是指将对象输出为二进制流的过程,反序列化则是指将二进制流反序列化为对象的过程
一般序列化的对象需要实现Serializable
接口,内部不需要序列化的对象,用transient
关键字进行声明
4. IO基本使用姿势
介质流与数据源进行交互 –> 转换流包装为字符流 –> 装饰流进行实际操作 –> 关闭流
以文件读写为例:
1 | BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("test.txt"), Chaset.forName("utf-8"))); |
IV. 其他
参考:
个人博客: Z+|blog
基于hexo + github pages搭建的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
声明
尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见识有限,如发现bug或者有更好的建议,随时欢迎批评指正
- 微博地址: 小灰灰Blog
- QQ: 一灰灰/3302797840