IO 流在 Java 中分为输入流和输出流,按照传输单位(数据处理方式)又分为字节流和字符流。Java 有四个 IO 流抽象类基类:
- InputStream/OutputStream:字节输入流,字节输出流。
- Reader/Writer:字符输入流,字符输出流。
字节流
InputStream
InputStream 用于从源头(如文件)读取字节数据到内存中,InputStream 的常用用法:
read()
:返回输入流中下一个字节的数据。返回的值介于 0 到 255 之间。如果未读取任何字节,则代码返回-1
,表示文件结束。read(byte b[ ])
: 从输入流中读取一些字节存储到数组b
中。如果数组b
的长度为零,则不读取。如果没有可用字节读取,返回-1
。如果有可用字节读取,则最多读取的字节数最多等于b.length
, 返回读取的字节数。这个方法等价于read(b, 0, b.length)
。read(byte b[], int off, int len)
:在read(byte b[ ])
方法的基础上增加了off
参数(偏移量)和len
参数(要读取的最大字节数)。skip(long n)
:忽略输入流中的 n 个字节 ,返回实际忽略的字节数。available()
:返回输入流中可以读取的字节数。close()
:关闭输入流释放相关的系统资源。
从 Java 9 开始,InputStream 新增加了多个实用的方法:
readAllBytes()
:读取输入流中的所有字节,返回字节数组。readNBytes(byte[] b, int off, int len)
:阻塞直到读取len
个字节。transferTo(OutputStream out)
:将所有字节从一个输入流传递到一个输出流。
常见 InputStream
常用的 InputStream 如下:
- FileInputStream:用于读取文件。
- BufferInputStream:带缓冲区的 InputStream,缓冲区缓冲数据,有助于减少 IO。
- DataInputStream:用于读取指定类型数据,如 readBoolean、readInt、readUTF 等。
- ObjectInputStream:用于从输入流中读取 Java 对象(反序列化)。
OnjectInputStream 用于序列化和反序列化的类必须实现
Serializable
接口,对象中如果有属性不想被序列化,使用transient
修饰。
OutputStream
OutputStream 用于将字节数据写入到目的地(文件等),OutputStream 的常用用法:
write(int b)
:将特定字节写入输出流。write(byte b[ ])
: 将数组b
写入到输出流,等价于write(b, 0, b.length)
。write(byte[] b, int off, int len)
: 在write(byte b[ ])
方法的基础上增加了off
参数(偏移量)和len
参数(要读取的最大字节数)。flush()
:刷新此输出流并强制写出所有缓冲的输出字节。close()
:关闭输出流释放相关的系统资源。
常用 OutputStream
常用 OutputStream 如下:
- FileOutputStream:用于写入文件。
- BufferOutputStream:带缓冲区的 OutputStream。
- DataOutputStream:用于写入指定类型数据。
- ObjectOutputStream:用于对象序列化。
字节缓冲流
IO 操作是很消耗性能的,缓冲流将数据加载至缓冲区,一次性读取/写入多个字节,从而避免频繁的 IO 操作,提高流的传输效率。字节缓冲流这里采用了装饰器模式来增强 InputStream
和OutputStream
子类对象的功能。
字节流和字节缓冲流的性能差别主要体现在:调用 write(int b)
和 read()
这两个一次只读取一个字节的方法的时候。由于字节缓冲流内部有缓冲区(字节数组),因此,字节缓冲流会先将读取到的字节存放在缓存区,大幅减少 IO 次数,提高读取效率。
如果使用 read(byte b[])
和 write(byte b[], int offset, int len)
方法进行批量读取,性能是差不多的。
原理
BufferedInputStream 和 BufferedOutputStream 内部维护了一个字节数组缓冲区,不会一个字节的进行读取或者写入,而是先缓存到缓冲区中,大幅度减少 IO 次数。
缓冲区的大小,默认是 8192 字节。
打印流
System.out.print
方法中,System.out
实际是用于获取一个 PrintStream
对象,print
方法实际调用的是 PrintStream
对象的 write
方法。
PrintStream
属于字节打印流,与之对应的是 PrintWriter
(字符打印流)。PrintStream
是 OutputStream
的子类,PrintWriter
是 Writer
的子类。
随机访问流
构造方法
随机访问流指支持随意跳转到文件的任意位置进行读写的 RandomAccessFile,构造方法如下,其中 mode 可以指定读写模式。读写模式包括:
- r:只读。
- rw:读写。
- rws:同步更新对”文件的内容”或”元数据”的修改到外部存储设备。
- rwd:同步更新对”文件的内容”的修改到外部存储设备。
1 | // openAndDelete 参数默认为 false 表示打开文件并且这个文件不会被删除 |
原理
RandomAccessFile 内部维护了一个文件指针,用来表示下一个将要被写入或者读取的字节所处的位置。可以通过 seek(long pos)
方法设置指针的偏移量。
应用 - 断点续传
RandomAccessFile 的一个比较常见的应用就是实现文件的断点续传,分片(先将文件切分成多个文件分片)上传是断点续传的基础。RandomAccessFile 可以实现文件的分片:
字符流
字节流不能处理如 Unicode(占两字节)、UTF-8(英文一字节,中文三字节)、GBK(英文一字节,中文两字节)等编码的情况,所以引入字符流,用于直接操作字符。
Reader
Reader 用于从源头(文件等)读取字符数据到内存中,Reader 常用方法:
read()
: 从输入流读取一个字符。read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组cbuf
中,等价于read(cbuf, 0, cbuf.length)
。read(char[] cbuf, int off, int len)
:在read(char[] cbuf)
方法的基础上增加了off
参数(偏移量)和len
参数(要读取的最大字符数)。skip(long n)
:忽略输入流中的 n 个字符 ,返回实际忽略的字符数。close()
: 关闭输入流并释放相关的系统资源。
Writer
Writer 用于将字符数据写入到目的地(文件等),常用方法如下:
write(int c)
: 写入单个字符。write(char[] cbuf)
:写入字符数组cbuf
,等价于write(cbuf, 0, cbuf.length)
。write(char[] cbuf, int off, int len)
:在write(char[] cbuf)
方法的基础上增加了off
参数(偏移量)和len
参数(要读取的最大字符数)。write(String str)
:写入字符串,等价于write(str, 0, str.length())
。write(String str, int off, int len)
:在write(String str)
方法的基础上增加了off
参数(偏移量)和len
参数(要读取的最大字符数)。append(CharSequence csq)
:将指定的字符序列附加到指定的Writer
对象并返回该Writer
对象。append(char c)
:将指定的字符附加到指定的Writer
对象并返回该Writer
对象。flush()
:刷新此输出流并强制写出所有缓冲的输出字符。close()
:关闭输出流释放相关的系统资源。
转换流
转换流提供了在字节流和字符流之间的转换,Java API 提供了两个转换流:
InputStreamReader:将 InputStream 转换为 Reader(解码)。
- 构造器:public InputStreamReader(InputStream in)、public InputSreamReader(InputStream in,String charsetName)
OutputStreamWriter:将 Writer 转换为 OutputStream(编码)。
- 构造器:public OutputStreamWriter(OutputStream out)、public OutputSreamWriter(OutputStream out,String charsetName)