Dawn's Blogs

分享技术 记录成长

0%

Java面试之IO (1) IO流

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 操作,提高流的传输效率。字节缓冲流这里采用了装饰器模式来增强 InputStreamOutputStream子类对象的功能。

字节流和字节缓冲流的性能差别主要体现在:调用 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 (字符打印流)。PrintStreamOutputStream 的子类,PrintWriterWriter 的子类。

随机访问流

构造方法

随机访问流指支持随意跳转到文件的任意位置进行读写的 RandomAccessFile,构造方法如下,其中 mode 可以指定读写模式。读写模式包括:

  • r:只读。
  • rw:读写。
  • rws:同步更新对”文件的内容”或”元数据”的修改到外部存储设备。
  • rwd:同步更新对”文件的内容”的修改到外部存储设备。
1
2
3
4
5
6
7
8
9
// openAndDelete 参数默认为 false 表示打开文件并且这个文件不会被删除
public RandomAccessFile(File file, String mode)
throws FileNotFoundException {
this(file, mode, false);
}
// 私有方法
private RandomAccessFile(File file, String mode, boolean openAndDelete) throws FileNotFoundException{
// 省略大部分代码
}

原理

RandomAccessFile 内部维护了一个文件指针,用来表示下一个将要被写入或者读取的字节所处的位置。可以通过 seek(long pos) 方法设置指针的偏移量。

应用 - 断点续传

RandomAccessFile 的一个比较常见的应用就是实现文件的断点续传,分片(先将文件切分成多个文件分片)上传是断点续传的基础。RandomAccessFile 可以实现文件的分片:

img

字符流

字节流不能处理如 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)