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

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

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

Java
I/O 的主要结构

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

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

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

实现概要

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

字节流输入

图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造成死锁。

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

扩展阅读

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

OpenJDK 阅读源代码 Java 实现字节流输入类的更多相关文章

  1. 设计一个 Java 程序,自定义异常类,从命令行(键盘)输入一个字符串,如果该字符串值为“XYZ”。。。

    设计一个 Java 程序,自定义异常类,从命令行(键盘)输入一个字符串,如果该字符串值为“XYZ”,则抛出一个异常信息“This is a XYZ”,如果从命令行输入 ABC,则没有抛出异常.(只有 ...

  2. Emacs和它的朋友们——阅读源代码篇(转)

    正如那本<Code Reading>一书中指出的那样,源代码阅读一直没有被很好的重 视:你上大学的时候有“代码阅读”这门课吗?相信没有. 1 Source Insight 谈到阅读源代码, ...

  3. Java的字节流,字符流和缓冲流对比探究

    目录 一.前言 二.字节操作和字符操作 三.两种方式的效率测试 3.1 测试代码 3.2 测试结果 3.3 结果分析 四.字节顺序endian 五.综合对比 六.总结 一.前言 所谓IO,也就是Inp ...

  4. 第27章 java I/O输入输出流

    java I/O输入输出流 1.编码问题 import java.io.UnsupportedEncodingException; /** * java涉及的编码 */ public class En ...

  5. java中字节流和字符流的区别

    流分类: 1.Java的字节流   InputStream是所有字节输入流的祖先,而OutputStream是所有字节输出流的祖先.2.Java的字符流  Reader是所有读取字符串输入流的祖先,而 ...

  6. Java复习7.输入输出流

    Java复习7.输入输出流 20131005 前言: Java中涉及数据的读写,都是基于流的,这一块的知识相当重要,而且在Java中的数据,char字符是16bit的,所以存在字节流和字符流的区别.如 ...

  7. Java学习:File类

    Java学习:File类 File类的概述 重点:记住这三个单词 绝对路径和相对路径 File类的构造方法 File类判断功能的方法 File类创建删除功能的方法 File类获取(文件夹)目录和文件夹 ...

  8. 2019.12.12 Java的多线程&匿名类

    Java基础(深入了解概念为主) 匿名类 定义 Java匿名类很像局部或内联系,只是没有明细.我们可以利用匿名类,同时定义并实例化一个类.只有局部类仅被使用一次时才应该这么做. 匿名类不能有显式定义的 ...

  9. java中的原子操作类AtomicInteger及其实现原理

    /** * 一,AtomicInteger 是如何实现原子操作的呢? * * 我们先来看一下getAndIncrement的源代码: * public final int getAndIncremen ...

随机推荐

  1. GIT的下载、安装、与使用

    一.下载: 网址:https://code.google.com/p/msysgit/ 进入这个网站以后,你会看到以下界面: 在这个界面中找到: 这时你便可以下载GIT 二.安装 安装过程比较简单,一 ...

  2. keil提示:missing ';' before 'XXX'但又找不到是哪里少了分号——已解决!

    今天写一个51程序,keil在编译时总提示丢失了一个分号,但怎么都找不到是哪里丢失了分号,搞了一下午才解决问题,现在把经验在此分享,以求让他人不要犯同样的错误!提示信息为: LCD12864.H(20 ...

  3. zoj 2822 Sum of Different Primes (01背包)

    ///给你n 求他能分解成多少个的不同的k个素数相加之和 ///01背包,素数打表 # include <stdio.h> # include <algorithm> # in ...

  4. php学习之道:WSDL具体解释(三)

    通过声明方式定义绑定(binding)属性 假设你在服务中採用SOAP binding.你能够使用JAX-WS来指定一定数量的属性binding. 这些属性指定相应你在WSDL中指定的属性.某些设置. ...

  5. 【瞎搞】 HDU 3101 The Heart of the Country

    比赛时愣是没读懂 题意:有N 个城市 每一个城市都有 val 个 士兵 , 有几条路连接 当敌方攻击你的某个城市时 该城市以及与该城市相连接的城市的士兵总数 要大于 K 不大于 K 该城市就被攻陷.士 ...

  6. HDOJ 4964 Emmet

    递归语法翻译... Emmet Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others ...

  7. .Net程序猿乐Android开发---(4)注册页面布局

    接下来我们介绍的登陆页面布局,在本节中,我们看一下注册页面布局,页面布局大同小异,来一起熟悉下基本控件的使用方法. 效果图: 1.加入注冊页面 右键选中layout目录,加入注冊页面.例如以下图 点击 ...

  8. poj2528(线段树)

    题目连接:http://poj.org/problem?id=2528 题意:在墙上贴海报,海报可以互相覆盖,问最后可以看见几张海报 分析:离散化+线段树,这题因为每个数字其实表示的是一个单位长度,因 ...

  9. Mac OS X在建筑Python科学计算环境

    经验(比如这篇日志:http://blog.csdn.net/waleking/article/details/7578517).他们推荐使用Mac Ports这种软件来管理和安装全部的安装包.依照这 ...

  10. Jsoup 抓取和数据页 认识HTTP头

    推荐一本书:黑客攻防技术宝典.Web实战篇  :       顺便留下一个疑问:能否通过jsoup大量并发訪问web或者小型域名server,使其瘫痪?其有用jsoup熟悉的朋友能够用它解析url来干 ...