上一篇介绍了关于字节输入输出流的Java类框架,同时也简单介绍了一下各个类的作用,下面就来具体看一下这些类是怎么实现这些功能的。

1、InputStream和OutputStream

InputStream类的源代码如下:

public abstract class InputStream implements Closeable {

    private static final int MAX_SKIP_BUFFER_SIZE = 2048;//最多可以跳过字节的数量

    // 获取下一个字节数据并返回int值(范围0~255),如果流结束,返回-1
public abstract int read() throws IOException; public int read(byte b[]) throws IOException {//读取一个字节,返回值为所读得字节
return read(b, 0, b.length);
}
//读取len个字节,放置到以下标off开始字节数组b中,返回值为实际 读取的字节的数量
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;
}
//读指针跳过n个字节不读,返回值为实际跳过的字节数量
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;
} // 返回值为流中尚未读取的字节的数量,这个方法应该被子类覆写
public int available() throws IOException {
return 0;
} public void close() throws IOException {}
// 纪录当前指针的所在位置.
// readlimit参数表示读指针读出的readlimit个字节后 所标记的指针位置才实效。
public synchronized void mark(int readlimit) {}
//把读指针重新指向用mark方法所记录的位置
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
//当前的流是否支持读指针的记录功能
public boolean markSupported() {
return false;
} }

来解释一下mark()和reset()方法,如下图所示。

讲到具体的类时会进行详细的解说。

下面来看InputStream的源代码,如下:

public abstract class OutputStream implements Closeable, Flushable {
/**
The byte to be written is the eight low-order bits of the argument b. The 24
* high-order bits of b are ignored.
*/
public abstract void write(int b) throws IOException;
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
} public void write(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
} public void flush() throws IOException { }
public void close() throws IOException { } }

如上主要就是将字节写入到输出流中,具体的写入方法write(int b)是一个抽象方法,取决于具体的实现类的实现。

2、PipedInputStream和PipedOutputStream

PipedInputStream类与PipedOutputStream类用于在应用程序中创建管道通信.一个PipedInputStream实例对象必须和一个PipedOutputStream实例对象进行连接而产生一个通信管道.PipedOutputStream可以向管道中写入数据,PipedIntputStream可以读取PipedOutputStream向管道中写入的数据.这两个类主要用来完成线程之间的通信.一个线程的PipedInputStream对象能够从另外一个线程的PipedOutputStream对象中读取数据.
         PipedInputStream和PipedOutputStream的实现原理类似于"生产者-消费者"原理,PipedOutputStream是生产者,PipedInputStream是消费者,在PipedInputStream中有一个buffer字节数组,默认大小为1024,作为缓冲区,存放"生产者"生产出来的东西.还有两个变量in和out。in是用来记录"生产者"生产了多少,out是用来记录"消费者"消费了多少,in为-1表示消费完了,in==out表示生产满了.当消费者没东西可消费的时候,也就是当in为-1的时候,消费者会一直等待,直到有东西可消费.
    在两者的构造函数中,都相互提供了连接的构造方法,分别用于接收对方的管道实例,然后调用各自的connect()方法进行连接,如PipedInputStream:

    //  PipedInputStream
public PipedInputStream(PipedOutputStream src, int pipeSize)
throws IOException {
initPipe(pipeSize);
connect(src);
}
private void initPipe(int pipeSize) {
if (pipeSize <= 0) {
throw new IllegalArgumentException("Pipe Size <= 0");
}
buffer = new byte[pipeSize];
}
public void connect(PipedOutputStream src) throws IOException {
src.connect(this);
}

同时还可以指定缓冲区的大小,看PipedOutputStream:

private PipedInputStream sink;
public PipedOutputStream(PipedInputStream snk) throws IOException {
connect(snk);
}
public PipedOutputStream() { }
// PipedOutputStream
public synchronized void connect(PipedInputStream snk) throws IOException {
if (snk == null) {
throw new NullPointerException();
} else if (sink != null || snk.connected) {
throw new IOException("Already connected");
}
sink = snk;
snk.in = -1;
snk.out = 0;
snk.connected = true;
}

没有谁连接谁的规定,只要连接上,效果是一样的。来看输出管道中的write()方法,如下:

 public void write(int b)  throws IOException {
if (sink == null) {
throw new IOException("Pipe not connected");
}
sink.receive(b);
}
public void write(byte b[], int off, int len) throws IOException {
if (sink == null) {
throw new IOException("Pipe not connected");
} else if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
sink.receive(b, off, len);
}

方法在写入到byte[]数组缓存区数据后,就会调用输出管道中的receive方法。输出管道中的receive()方法如下:

/**
* Receives a byte of data. This method will block if no input is
* available.
*/
protected synchronized void receive(int b) throws IOException {
checkStateForReceive();
writeSide = Thread.currentThread();
if (in == out)//in==out implies the buffer is full
awaitSpace();
if (in < 0) {//输入管道无数据
in = 0;
out = 0;
}
buffer[in++] = (byte)(b & 0xFF);
if (in >= buffer.length) {
in = 0;// 缓冲区已经满了,等待下一次从头写入
}
} /**
* Receives data into an array of bytes. This method will
* block until some input is available.
*/
synchronized void receive(byte b[], int off, int len) throws IOException {
checkStateForReceive();
writeSide = Thread.currentThread();
int bytesToTransfer = len;
while (bytesToTransfer > 0) {
if (in == out)
awaitSpace();
int nextTransferAmount = 0;
if (out < in) {
nextTransferAmount = buffer.length - in;
} else if (in < out) {
if (in == -1) {
in = out = 0;
nextTransferAmount = buffer.length - in;
} else {
nextTransferAmount = out - in;
}
}
if (nextTransferAmount > bytesToTransfer)
nextTransferAmount = bytesToTransfer;
assert(nextTransferAmount > 0);
System.arraycopy(b, off, buffer, in, nextTransferAmount);
bytesToTransfer -= nextTransferAmount;
off += nextTransferAmount;
in += nextTransferAmount;
if (in >= buffer.length) {
in = 0;
}
}
}

输入管理通过如上的对应方法接收到数据并保存到输入缓冲区后,下面就可以使用read()方法读出这些数据了,如下:

    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;
} /**
* Reads up to len bytes of data from this piped input stream into an array of bytes. Less than len bytes
* will be read if the end of the data stream is reached or if len exceeds the pipe's buffer size.
* If len is zero, then no bytes are read and 0 is returned;otherwise, the method blocks until
* at least 1 byte of input is available, end of the stream has been detected, or an exception is
*/
public synchronized 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;
}
/* possibly wait on the first character */
int c = read();
if (c < 0) {
return -1;
}
b[off] = (byte) c;
int rlen = 1;
while ((in >= 0) && (len > 1)) {
int available;
if (in > out) {
available = Math.min((buffer.length - out), (in - out));
} else {
available = buffer.length - out;
}
// A byte is read beforehand outside the loop
if (available > (len - 1)) {
available = len - 1;
}
System.arraycopy(buffer, out, b, off + rlen, available);
out += available;
rlen += available;
len -= available; if (out >= buffer.length) {
out = 0;
}
if (in == out) {
/* now empty */
in = -1;
}
}
return rlen;
}

下面来具体举一个例子,如下:

public class test04 {
public static void main(String [] args) {
Sender sender = new Sender();
Receiver receiver = new Receiver(); PipedOutputStream outStream = sender.getOutStream();
PipedInputStream inStream = receiver.getInStream();
try {
//inStream.connect(outStream); // 与下一句一样
outStream.connect(inStream);
} catch (Exception e) {
e.printStackTrace();
}
sender.start();
receiver.start();
}
} class Sender extends Thread {
private PipedOutputStream outStream = new PipedOutputStream();
public PipedOutputStream getOutStream() {
return outStream;
}
public void run() {
String info = "hello, receiver";
try {
outStream.write(info.getBytes());
outStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
} class Receiver extends Thread {
private PipedInputStream inStream = new PipedInputStream();
public PipedInputStream getInStream() {
return inStream;
}
public void run() {
byte[] buf = new byte[1024];
try {
int len = inStream.read(buf);
System.out.println("receive message from sender : " + new String(buf, 0, len));
inStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

最后运行后输出的结果如下:receive message from sender : hello, receiver

3、ByteArrayInputStream和ByteArrayOutputStream

先来看ByteArrayOutputStream类中write()方法:

 // Writes the specified byte to this byte array output stream.
public synchronized void write(int b) {
ensureCapacity(count + 1);
buf[count] = (byte) b;
count += 1;
}
/**
* Writes len bytes from the specified byte array
* starting at offset off to this byte array output stream.
*/
public synchronized void write(byte b[], int off, int len) {
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) - b.length > 0)) {
throw new IndexOutOfBoundsException();
}
ensureCapacity(count + len);
System.arraycopy(b, off, buf, count, len);
count += len;
}

这个类也是通过向数组中定入值来进行数值传递的,而字节数组的大小可以在创建ByteArrayOutputStream时通过构造函数指定,默认的大小为32.如果在调用write()方法时,都会确保字节数组的容量。如果过小,会自动进行扩容操作。这样就可以把需要的数据写到字节数组中去了。还可以通过调用writeTo()方法可以写入到其他输出流中,源代码如下:

 /**
* Writes the complete contents of this byte array output stream to
* the specified output stream argument, as if by calling the output
* stream's write method using out.write(buf, 0, count).
*/
public synchronized void writeTo(OutputStream out) throws IOException {
out.write(buf, 0, count);
}

继续来看ByteArrayInputStream类,这个类可以从字节数组中读出数据,具体的源代码如下:

 public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
} public synchronized int read(byte b[], int off, int len) {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} if (pos >= count) {
return -1;
} int avail = count - pos;
if (len > avail) {
len = avail;
}
if (len <= 0) {
return 0;
}
System.arraycopy(buf, pos, b, off, len);
pos += len;
return len;
}

同时也提供了其他的一些方法,如可以跳读字节的skip()方法、查看剩余有效字节的avaible()方法等等,有兴趣的可以自己去看。下面来举一个具体应用的例子,如下:

byte[] bytes = { 0,2, 3, 4, 5 };
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(bytes);){
out.write(bytes);
System.out.println(out.size());//5
System.out.println(in.read());//解
in.skip(1);//2
in.mark(4);
System.out.println(in.read());//3
in.reset();// 从索引为2的地方重新开始读
System.out.println(in.read());//3
System.out.println(in.read()); } catch (IOException e) {
e.printStackTrace();
}

4、StringBufferInputStream

这个类现在已经不提倡使用了,个人觉得是因为编码的原因吧。查看这个类后的源代码,如下:

 public synchronized int read() {
return (pos < count) ? (buffer.charAt(pos++) & 0xFF) : -1;
} public synchronized int read(byte b[], int off, int len) {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
}
if (pos >= count) {
return -1;
}
if (pos + len > count) {
len = count - pos;
}
if (len <= 0) {
return 0;
}
String s = buffer;
int cnt = len;
while (--cnt >= 0) {
b[off++] = (byte)s.charAt(pos++);
} return len;
}

发现,其实这个类是将字符串中的字符转换为字节进行读取的,如果这个字符串的字符全部为ISO-8859-1编码所能表示的,那肯定能正常读取。但是通常Java都是Unicode编码,两个字符,所以如果其中出现了Unicode字符的时候,例如中文,读取就会不准确。如下举例:

String str = "马智AB";
StringBufferInputStream st = new StringBufferInputStream(str);
byte[] j = new byte[16];
st.read(j);
System.out.println(new String(j)); //lzAB

原因可能大家也知道了,两个read()方法在获取到这个字符串的字符(s.charAt())后,强制转换为byte或与0xff相与,这样的结果只能导致取到低8位的编码,而对于两个字节编码的汉字来说,肯定会产生错误。

Java 7源码分析第13篇 - 字节输入输出流(1)的更多相关文章

  1. 鸿蒙内核源码分析(字符设备篇) | 字节为单位读写的设备 | 百篇博客分析OpenHarmony源码 | v67.01

    百篇博客系列篇.本篇为: v67.xx 鸿蒙内核源码分析(字符设备篇) | 字节为单位读写的设备 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一 ...

  2. 鸿蒙内核源码分析(挂载目录篇) | 为何文件系统需要挂载 | 百篇博客分析OpenHarmony源码 | v65.01

    百篇博客系列篇.本篇为: v65.xx 鸿蒙内核源码分析(挂载目录篇) | 为何文件系统需要挂载 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一 ...

  3. 鸿蒙内核源码分析(索引节点篇) | 谁是文件系统最重要的概念 | 百篇博客分析OpenHarmony源码 | v64.01

    百篇博客系列篇.本篇为: v64.xx 鸿蒙内核源码分析(索引节点篇) | 谁是文件系统最重要的概念 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么 ...

  4. 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 百篇博客分析OpenHarmony源码 | v62.01

    百篇博客系列篇.本篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 51.c.h.o 本篇开始说文件系统,它是内核五大模块之一,甚至有Linux的设计哲学是" ...

  5. java集合源码分析(六):HashMap

    概述 HashMap 是 Map 接口下一个线程不安全的,基于哈希表的实现类.由于他解决哈希冲突的方式是分离链表法,也就是拉链法,因此他的数据结构是数组+链表,在 JDK8 以后,当哈希冲突严重时,H ...

  6. Hadoop2源码分析-MapReduce篇

    1.概述 前面我们已经对Hadoop有了一个初步认识,接下来我们开始学习Hadoop的一些核心的功能,其中包含mapreduce,fs,hdfs,ipc,io,yarn,今天为大家分享的是mapred ...

  7. Hadoop2源码分析-准备篇

    1.概述 我们已经能够搭建一个高可用的Hadoop平台了,也熟悉并掌握了一个项目在Hadoop平台下的开发流程,基于Hadoop的一些套件我们也能够使用,并且能利用这些套件进行一些任务的开发.在Had ...

  8. JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor

    JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor.它主要用来在 ...

  9. JUC源码分析-线程池篇(二)FutureTask

    JUC源码分析-线程池篇(二)FutureTask JDK5 之后提供了 Callable 和 Future 接口,通过它们就可以在任务执行完毕之后得到任务的执行结果.本文从源代码角度分析下具体的实现 ...

随机推荐

  1. 操作Sql数据库帮助类

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.D ...

  2. C# 几种退出程序的方式

    C# WinForm程序退出的方法 1.this.Close(); 只是关闭当前窗口,若不是主窗体的话,是无法退出程序的,另外若有托管线程(非主线程),也无法干净地退出: 2.Application. ...

  3. HasMap

    您还未登录 ! 登录 注册 论坛首页 → Java企业应用论坛 → 深入理解HashMap 全部 Hibernate Spring Struts iBATIS 企业应用 Lucene SOA Java ...

  4. asp.net 获取IP地理位置的几个主要接口

    腾讯的IP地址API接口地址:http://fw.qq.com/ipaddress 新浪的IP地址查询接口:http://int.dpool.sina.com.cn/iplookup/iplookup ...

  5. Nodejs随笔(三):全局对象之global

    首先,进入node REPL: mesogene@mesogene-team:~$ node > 查看global对象,发现其他全局对象(包括Buffer.require对象)以及全局方法都包含 ...

  6. js:关于IE6/7下new Date(值)输出为NaN的解决方案

    不得不再次说,万恶的IE,你太守旧了吧,这里出错的原因是IE的时间格式,不是2012-01-23(很多人喜欢用这样的格式) 而是2012/01/23(怎么感觉像是在用VB6和access啊) 搞了好久 ...

  7. Ajax制作无刷新评论系统

    index.html <script src="jquery.min.js"></script> <script> $(function(){ ...

  8. 网络解析 get 和post

    //get同步 - (IBAction)getT:(id)sender { //准备一个Url NSURL *url=[NSURL URLWithString:BASE_URL]; //创建一个请求对 ...

  9. ORM和Core

    .net  core中有哪些被抛弃的类 1.DataTable DataRow SqlDataAdapter DataRow DataColumn DataColumn 虽然这些类不是我ORM核心功能 ...

  10. Dialog 顶部黑线问题

    Dialog 顶部黑线问题 样式如下: [java] view plaincopyprint? <style name="Transparent_Dialog"> &l ...