OpenJDK 阅读源代码 Java 实现字节流输入类

OpenJDK 阅读源代码 Java 实现字节流输入类

大家好,又见面了,我是全栈君,今天给大家准备了Idea注册码。

Java 的输入输出总是给人一种非常混乱的感觉。要想把这个问题搞清楚。必须对各种与输入输出相关的类之间的关系有所了解。

仅仅有你了解了他们之间的关系。知道设计这个类的目的是什么。才干更从容的使用他们。

我们先对 Java I/O 的整体结构进行一个总结,再通过分析源码,给出把每一个类的关键功能是怎样实现的。

Java I/O 的主要结构

Java 的输入输出,主要分为下面几个部分:

  • 字节流
  • 字符流
  • 新 I/O

每一个部分,都包括了输入和输出两部分。

实现概要

这里仅仅给出每一个类的实现概要,详细每一个类的实现分析。能够參见我的 GitHub-SourceLearning-OpenJDK 页面。依据导航中的链接,进入 java.io ,就可以看到对每一个类的分析。

字节流输入

java_io_read_char

图1 Java 字节输入类

  • InputStream

InputStream 是全部字节输入类的基类,它有一个未实现的 read 方法。子类须要实现这个 read 方法, 它和数据的来源相关。它的各种不同子类,或者是加入了功能,或者指明了不同的数据来源。

public abstract int read() throws IOException;

  • ByteArrayInputStream

ByteArrayInputStream 有一个内部 buffer 。 包括从流中读取的字节,另一个内部 counter。 跟踪下一个要读入的字节。

protected byte buf[];
protected int pos;

这个类在初始化时。须要指定一个 byte[]。作为数据的来源,它的 read。就读入这个 byte[] 中所包括的数据。

public ByteArrayInputStream(byte buf[]) {
    this.buf = buf;
    this.pos = 0;
    this.count = buf.length;
}
public synchronized int read() {
    return (pos < count) ? (buf[pos++] & 0xff) : -1;
}

  • FileInputStream

FileInputStream 的数据来源是文件,即从文件里读取字节。

初始化时,须要指定一个文件:

public FileInputStream(File file) 
throws FileNotFoundException {
    String name = (file != null ?

file.getPath() : null); SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkRead(name); } if (name == null) { throw new NullPointerException(); } fd = new FileDescriptor(); fd.incrementAndGetUseCount(); open(name); }

以后读取的数据。都来自于这个文件。

这里的 read 方法是一个 native 方法。它的实现与操作系统相关。

public native int read() throws IOException;

  • FilterInputStream

FilterInputStream将其他输入流作为数据来源,其子类能够在它的基础上,对数据流加入新的功能。

我们常常看到流之间的嵌套。以加入新的功能。就是在这个类的基础上实现的。所以,它的初始化中,会指定一个字节输入流:

    protected volatile InputStream in;
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

读取操作。就依靠这个流实现:

public int read() throws IOException {
    return in.read();
}

  • BufferedInputStream

BufferedInputStream 是 FilterInputStream 的子类。所以,须要给它提供一个底层的流,用于读取,而它本身,则为此底层流添加功能。即缓冲功能。以降低读取操作的开销,提升效率。

protected volatile byte buf[];

内部缓冲区由一个 volatile byte 数组实现。大多线程环境下。一个线程向 volatile 数据类型中写入的数据,会马上被其他线程看到。

read 操作会先看一下缓冲区里的数据是否已经所有被读取了,假设是,就调用底层流,填充缓冲区,再从缓冲区中按要求读取指定的字节。

public synchronized int read() throws IOException {
    if (pos >= count) {
        fill();
        if (pos >= count)
            return -1;
    }
    return getBufIfOpen()[pos++] & 0xff;
}
private byte[] getBufIfOpen() throws IOException {
    byte[] buffer = buf;
    if (buffer == null)
        throw new IOException("Stream closed");
    return buffer;
}

  • DataInputStream

DataInputStream 也是 FilterInputStream 的子类。它提供的功能是:能够从底层的流中读取基本数据类型。比如 intchar等等。DataInputStream 是非线程安全的, 你必须自己保证处理线程安全相关的细节。

比如,readBoolean 会读入一个字节。然后依据是否为0,返回 true/false

public final boolean readBoolean() throws IOException {
    int ch = in.read();
    if (ch < 0)
        throw new EOFException();
    return (ch != 0);
}

readShort 会读入两个字节。然后拼接成一个 short 类型的数据。

public final short readShort() throws IOException {
    int ch1 = in.read();
    int ch2 = in.read();
    if ((ch1 | ch2) < 0)
        throw new EOFException();
    return (short)((ch1 << 8) + (ch2 << 0));
}

int 和 long 依此类推,分别读入4个字节,8个字节,然后进行拼接。

可是,浮点数就不能通过简单的拼接来攻克了,而要读入足够的字节数,然后再依照 IEEE 754 的标准进行解释:

public final float readFloat() throws IOException {
    return Float.intBitsToFloat(readInt());
}

  • PushbackInputstream

PushbackInputstream 类也是FilterInputStream的子类,它提供的功能是。能够将已经读入的字节。再放回输入流中,下次读取时,能够读取到这个放回的字节。这在某些情境下是很实用的。它的实现,就是依靠类似缓冲区的原理。被放回的字节,实际上是放在缓冲区里,读取时,先查看缓冲区里有没有字节,假设有就从这里读取,假设没有。就从底层流里读取。

缓冲区是一个字节数组:

protected byte[] buf;

读取时,优先从这里读取。读不到,再从底层流读取。

public int read() throws IOException {
    ensureOpen();
    if (pos < buf.length) {
        return buf[pos++] & 0xff;
    }
    return super.read();
}

  • PipedInputStream

PipedInputStream 与 PipedOutputStream 配合使用。它们通过 connect 函数相关联。

public void connect(PipedOutputStream src) throws IOException {
    src.connect(this);
}

它们共用一个缓冲区。一个从中读取,一个从中写入。

PipedInputStream内部有一个缓冲区,

protected byte buffer[];

读取时,就从这里读:

public synchronized int read()  throws IOException {
    if (!connected) {
        throw new IOException("Pipe not connected");
    } else if (closedByReader) {
        throw new IOException("Pipe closed");
    } else if (writeSide != null && !writeSide.isAlive()
               && !closedByWriter && (in < 0)) {
        throw new IOException("Write end dead");
    }

    readSide = Thread.currentThread();
    int trials = 2;
    while (in < 0) {
        if (closedByWriter) {
            /* closed by writer, return EOF */
            return -1;
        }
        if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {
            throw new IOException("Pipe broken");
        }
        /* might be a writer waiting */
        notifyAll();
        try {
            wait(1000);
        } catch (InterruptedException ex) {
            throw new java.io.InterruptedIOException();
        }
    }
    int ret = buffer[out++] & 0xFF;
    if (out >= buffer.length) {
        out = 0;
    }
    if (in == out) {
        /* now empty */
        in = -1;
    }

    return ret;
}

过程比我们想的要复杂,由于这涉及两个线程,须要相互配合,所以,须要检查非常多东西,才干终于从缓冲区中读到数据。

PipedOutputStream 类写入时,会调用 PipedInputStream 的receive功能。把数据写入 PipedInputStream 的缓冲区。

我们看一下 PipedOutputStream.write 函数:

public void write(int b)  throws IOException {
    if (sink == null) {
        throw new IOException("Pipe not connected");
    }
    sink.receive(b);
}

能够看出,调用了相关联的管道输入流的 receive 函数。

protected synchronized void receive(int b) throws IOException {
    checkStateForReceive();
    writeSide = Thread.currentThread();
    if (in == out)
        awaitSpace();
    if (in < 0) {
        in = 0;
        out = 0;
    }
    buffer[in++] = (byte)(b & 0xFF);
    if (in >= buffer.length) {
        in = 0;
    }
}

receive 的主要功能,就是把写入的数据放入缓冲区内。

注意注意的是。这两个类相互关联的对象。应该属于两个不同的线程。否则。easy造成死锁。

这个系列的第一部分到此结束,扩展阅读部分的文章很好,推荐阅读。

扩展阅读

版权声明:本文博主原创文章,博客,未经同意不得转载。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/116908.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • datagrip在线激活码[免费获取]

    (datagrip在线激活码)2021最新分享一个能用的的激活码出来,希望能帮到需要激活的朋友。目前这个是能用的,但是用的人多了之后也会失效,会不定时更新的,大家持续关注此网站~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html…

    2022年3月21日
    279
  • leetcode 颜色分类_LEETCODE

    leetcode 颜色分类_LEETCODE给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。示例 1:输入:nums = [2,0,2,1,1,0]输出:[0,0,1,1,2,2]示例 2:输入:nums = [2,0,1]输出:[0,1,2]示例 3:输入:nums = [0]输出:[0]示例 4:输入:nums = [1]输出:[1] 提示:n == num

    2022年8月9日
    9
  • SqlSession和Mapper使用

    SqlSession和Mapper使用这两个比较简单 就放在这里一起学习理解了 一 SqlSession 学习在 MyBatis 中 SqlSession 是其核心接口 在 MyBatis 中有两个实现类 DefaultSqlSe 和 SqlSessionMa DefaultSqlSe 是单线程使用的 而 SqlSessionMa 在多线程环境下使用 SqlSession 的作用类似于一个 JDBC 中的 Connect

    2025年6月25日
    2
  • 深度理解Java中的static

    深度理解Java中的static目录一、static的用法:二、static的误区(问题思考)三、问题思考:一、static的用法:static可以用来修饰类的成员方法、类的成员变量、类中的内部类(以及用static修饰的内部类中的变量、方法、内部类),另外可以编写static代码块来优化程序性能。作用:方便在没有创建对象的情况下来进行调用(方法/变量)。被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。①修饰类的成员变量:static.

    2022年7月17日
    15
  • 2021 pycharm 激活码_通用破解码

    2021 pycharm 激活码_通用破解码,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月16日
    54
  • 推荐5款开源报表工具下载_开源报表系统

    推荐5款开源报表工具下载_开源报表系统小编最近发现几款不错的开源报表,还提供源码,现在给大家分享一下,希望能给你带来帮助!1、项目名称:积木报表项目简介:积木报表,免费的企业级WEB报表工具。专注于“专业、易用、优质”的报表设计器和大屏设计器。支持打印设计、数据报表、图形报表、大屏设计器,重点是免费的。项目地址:http://www.jimureport.com/2、项目名称:UReport2项目简介:UReport2是一款高性能的Java报表引擎,提供完善的基于网页的报表设计器,可快速做出各种复杂的中式报表。在UR

    2022年10月20日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号