通过JDK源码学习InputStream详解

简介: 通过JDK源码学习InputStream详解

InputStream抽象类是所有字节输入流的类的超类。这篇文章主要给大家介绍了关于通过JDK源码学习InputStream的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。

概况

本文主要给大家介绍了通过JDK源码学习InputStream的相关内容,JDK 给我们提供了很多实用的输入流 xxxInputStream,而 InputStream 是所有字节输入流的抽象。包括 ByteArrayInputStream 、FilterInputStream 、BufferedInputStream 、DataInputStream 和 PushbackInputStream 等等。下面话不多说了,来一起看看详细的介绍吧。

如何阅读JDK源码。

以看核心虚拟机(hotspot)code为例介绍。

1)熟悉虚拟机原理。调bug可以不懂原理,但是看code必须懂原理,从code里面看原理,基本不可能。hotspot的code写的挺乱的,想直接通过code以及code中的注释看明白还是很困难的。所以先熟悉虚拟机的原理,再去看code,会针对性比较强。

2)分模块阅读code。hotspot包括的模块确实太多,我们需要分成不同的模块各个击破。以GC为例,hotspot中的gc算法有很多种,parallel scavenge,cms,g1…等等,先弄懂这些算法的原理,再去看code会比较快。不要看二手资料,不要看翻译资料,推荐R大的hllvm论坛以及周志明的深入java虚拟机,hotspot源码阅读这本书写的也还可以。

继承结构

--java.lang.Object
 --java.io.InputStream

类定义

public abstract class InputStream implements Closeable

InputStream 被定为 public 且 abstract 的类,实现了Closeable接口。

Closeable 接口表示 InputStream 可以被close,接口定义如下:

public interface Closeable extends AutoCloseable {
  public void close() throws IOException;
}

主要属性

private static final int MAX_SKIP_BUFFER_SIZE = 2048;
 
private static final int DEFAULT_BUFFER_SIZE = 8192;
 
private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
MAX_SKIP_BUFFER_SIZE 表示输入流每次最多能跳过的字节数。
DEFAULT_BUFFER_SIZE 默认的缓冲大小。
MAX_BUFFER_SIZE 表示最大的缓冲数组大小,这里设置为 Integer.MAX_VALUE - 8 这里也是考虑到 JVM 能支持的大小,超过这个值就会导致 OutOfMemoryError。

主要方法

read方法

一共有三个 read 方法,其中有一个抽象的 read 方法,其余两个 read 方法都会调用这个抽象方法,该方法用于从输入流读取下一个字节,返回一个0到255范围的值。如果已经到达输入流结尾处而导致无可读字节则返回-1,同时,此方法为阻塞方法,解除阻塞的条件:

 1. 有可读的字节。

 2. 检测到已经是输入流的结尾了。

 3. 抛出异常。

主要看第三个 read 方法即可,它传入的三个参数,byte数组、偏移量和数组长度。该方法主要是从输入流中读取指定长度的字节数据到字节数组中,需要注意的是这里只是尝试去读取长度为 len 的数组,但真正读取到的数组长度不一定为 len,返回值才是真正读取到的长度。

public abstract int read() throws IOException;
 
public int read(byte b[]) throws IOException {
  return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
  if (b == null) {
    throw new NullPointerException();
  } else if (off < 0 || len < 0 || len > b.length - off) {
    throw new IndexOutOfBoundsException();
  } else if (len == 0) {
    return 0;
  }
 
  int c = read();
  if (c == -1) {
    return -1;
  }
  b[off] = (byte)c;
 
  int i = 1;
  try {
    for (; i < len ; i++) {
      c = read();
      if (c == -1) {
        break;
      }
      b[off + i] = (byte)c;
    }
  } catch (IOException ee) {
  }
  return i;
}

看看它的逻辑,数组为null则抛空指针,偏移量和长度超过边界也抛异常,长度为0则什么都不敢直接返回0。接着调用 read() 读取一个字节,如果为-1则说明结束,直接返回-1。否则继续根据数组长度循环调用 read() 方法读取字节,并且填充到传入的数组对象中,最后返回读取的字节数。

readAllBytes方法

该方法从输入流读取所有剩余的字节,在此过程是阻塞的,直到所有剩余字节都被读取或到达流的结尾或发生异常。

逻辑是用一个 for 循环内嵌一个 while 循环,while 循环不断调用 read 方法尝试将 DEFAULT_BUFFER_SIZE 长度的字节数组填满,一旦填满则需要将数组容量扩容一倍,再将原字节数组复制到新数组中,然后再通过 while 循环继续读取,直到达到尾部才跳出 for 循环,最后返回读取到的所有字节数组。

public byte[] readAllBytes() throws IOException {
  byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
  int capacity = buf.length;
  int nread = 0;
  int n;
  for (;;) {
    while ((n = read(buf, nread, capacity - nread)) > 0)
      nread += n;
    if (n < 0)
      break;
    if (capacity <= MAX_BUFFER_SIZE - capacity) {
      capacity = capacity << 1;
    } else {
      if (capacity == MAX_BUFFER_SIZE)
        throw new OutOfMemoryError("Required array size too large");
      capacity = MAX_BUFFER_SIZE;
    }
    buf = Arrays.copyOf(buf, capacity);
  }
  return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
}

readNBytes方法

从输入流中读取指定长度的字节,而且它能保证一定能读取到指定的长度,它属于阻塞方式,用一个 while 循环不断调用 read 读取字节,直到读取到指定长度才结束读取。

public int readNBytes(byte[] b, int off, int len) throws IOException {
  Objects.requireNonNull(b);
  if (off < 0 || len < 0 || len > b.length - off)
    throw new IndexOutOfBoundsException();
  int n = 0;
  while (n < len) {
    int count = read(b, off + n, len - n);
    if (count < 0)
      break;
    n += count;
  }
  return n;
}

available方法

返回从该输入流能进行非阻塞读取的剩余字节数,当调用 read 读取的字节数一般会小于该值,有一些InputStream的子实现类会通过该方法返回流的剩余总字节数,但有些并不会,所以使用时要注意点。

这里抽象类直接返回0,子类中重写该方法。

public int available() throws IOException {
    return 0;
  }

skip方法

从输入流中跳过指定个数字节,返回值为真正跳过的个数。这里的实现是简单通过不断调用 read 方法来实现跳过逻辑,但这是较低效的,子类可用更高效的方式重写此方法。

下面看看逻辑,最大的跳过长度不能超过 MAX_SKIP_BUFFER_SIZE ,并且用一个 while 循环调用 read 方法,如果遇到返回为-1,即已经到达结尾了,则跳出循环。可以看到 skipBuffer 其实是没有什么作用,直接让其被 GC 即可,最后返回真正跳过的字节数。

public long skip(long n) throws IOException {
 
  long remaining = n;
  int nr;
 
  if (n <= 0) {
    return 0;
  }
 
  int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
  byte[] skipBuffer = new byte[size];
  while (remaining > 0) {
    nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
    if (nr < 0) {
      break;
    }
    remaining -= nr;
  }
 
  return n - remaining;
}

close方法

此方法用于关闭输入流,并且释放相关资源 。

public void close() throws IOException {}

transferTo方法

从输入流中按顺序读取全部字节并且写入到指定的输出流中,返回值为转移的字节数。转移过程中可能会发生不确定次的阻塞,阻塞可能发生在 read 操作或 write 操作。

主要逻辑是用 while 循环不断调用 read 方法操作读取字节,然后调用输出流的 write 方法写入,直到读取返回-1,即达到结尾。最后返回转移的字节数。

public long transferTo(OutputStream out) throws IOException {
  Objects.requireNonNull(out, "out");
  long transferred = 0;
  byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
  int read;
  while ((read = this.read(buffer, 0, DEFAULT_BUFFER_SIZE)) >= 0) {
    out.write(buffer, 0, read);
    transferred += read;
  }
  return transferred;
}

markSupported方法

是否支持 mark 和 reset 操作,这里直接返回 false,子类根据实际重写该方法。

public boolean markSupported() {
  return false;
}

mark方法

标记输入流当前位置,与之对应的是 reset 方法,通过他们之间的组合能实现重复读取操作。另外它会传入 readlimit 参数,它用于表示该输入流中在执行 mark 操作后最多可以读 readlimit 个字节后才使 mark 的位置失效。

可以看到 InputStream 的 mark 方法是什么都不做的,子类中再具体实现。

public synchronized void mark(int readlimit) {}

reset方法

与 mark 方法对应,它可以重置输入流的位置到上次被 mark 操作标识的位置。InputStream 的 reset 方法直接抛出一个 IOException,子类中根据实际情况实现。

public synchronized void reset() throws IOException {
  throw new IOException("mark/reset not supported");
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对"java开发全栈"的支持。

相关文章
|
8月前
|
安全 前端开发 Java
JDK源码级别彻底剖析JVM类加载机制
JDK源码级别彻底剖析JVM类加载机制
|
8月前
|
缓存 Dubbo Java
趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了
趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了
|
8月前
|
Oracle Java 编译器
基本概念【入门、 发展简史、核心优势、各版本的含义、特性和优势、JVM、JRE 和 JDK 】(二)-全面详解(学习总结---从入门到深化)
基本概念【入门、 发展简史、核心优势、各版本的含义、特性和优势、JVM、JRE 和 JDK 】(二)-全面详解(学习总结---从入门到深化)
105 1
|
5月前
|
算法 安全 Java
深入JDK源码:揭开ConcurrentHashMap底层结构的神秘面纱
【8月更文挑战第24天】`ConcurrentHashMap`是Java并发编程中不可或缺的线程安全哈希表实现。它通过精巧的锁机制和无锁算法显著提升了并发性能。本文首先介绍了早期版本中使用的“段”结构,每个段是一个带有独立锁的小型哈希表,能够减少线程间竞争并支持动态扩容以应对高并发场景。随后探讨了JDK 8的重大改进:取消段的概念,采用更细粒度的锁控制,并引入`Node`等内部类以及CAS操作,有效解决了哈希冲突并实现了高性能的并发访问。这些设计使得`ConcurrentHashMap`成为构建高效多线程应用的强大工具。
69 2
|
7月前
|
Java Spring
深入解析Spring源码,揭示JDK动态代理的工作原理。
深入解析Spring源码,揭示JDK动态代理的工作原理。
81 0
|
8月前
|
设计模式 Java
根据JDK源码Calendar来看工厂模式和建造者模式
根据JDK源码Calendar来看工厂模式和建造者模式
109 3
|
8月前
|
算法 Java 索引
【数据结构与算法】4、双向链表(学习 jdk 的 LinkedList 部分源码)
【数据结构与算法】4、双向链表(学习 jdk 的 LinkedList 部分源码)
80 0
|
8月前
|
Oracle IDE Java
基本概念【入门、 发展简史、核心优势、各版本的含义、特性和优势、JVM、JRE 和 JDK 】(二)-全面详解(学习总结---从入门到深化)(下)
基本概念【入门、 发展简史、核心优势、各版本的含义、特性和优势、JVM、JRE 和 JDK 】(二)-全面详解(学习总结---从入门到深化)
76 1
|
8月前
|
消息中间件 Oracle Dubbo
Netty 源码共读(一)如何阅读JDK下sun包的源码
Netty 源码共读(一)如何阅读JDK下sun包的源码
165 1
|
8月前
|
Java Linux iOS开发
Spring5源码(27)-静态代理模式和JDK、CGLIB动态代理
Spring5源码(27)-静态代理模式和JDK、CGLIB动态代理
72 0