在《JAVA深入浅出流之一IO流》中,介绍了流安读取方式分字节流和字符流,那什么是字节流呢? 字节流的应用范围是什么?或者说什么时候用字节流?
本篇主要介绍字节流,其实如果研究字节流的类谱,它也算是个庞大家族,万事开头难,但故事总是从头说起,这里的“头”就是字节流的超类。
一:字节流(byte stream)
字节流是执行基于8位字节的输入和输出,它一次读写一字节的数据。字节流是I / O的最底层流技术,因此,如果你正在阅读或写入字符数据的最佳方法是使用字符流。其他流类型是建立在字节流之上的(如Java中的InputStream、OutputStream)。
API类关系- java.lang.Object
- java.io.InputStream
- java.lang.Object
- java.io.OutputStream
字节流类谱图:
通过上面两张关于流的类关系图,可以看到流的应用类都是由InputStream、OutputStream两类延伸(扩展)开来的。继承自InputStream/OutputStream的流都是用于向程序中输入/输出数据,且数据的单位都是字节(byte=8bit),如上图中,深色的为节点流,浅色的为处理流。(关于节点流、处理流的的介绍在《JAVA深入浅出流之一IO流》里讲过了
那就来看一下这两个超类到底是什么东东!
二:介绍InputStream、OutputStream
查看InputStream、OutputStream类的源码。
import java.io.Closeable;
import java.io.IOException;
public abstract class InputStream implements Closeable {
// SKIP_BUFFER_SIZE is used to determine the size of skipBuffer
private static final int SKIP_BUFFER_SIZE = 2048;
// skipBuffer is initialized in skip(long), if needed.
private static byte[] skipBuffer;
/**
* 方法: 从输入流中读取数据的下一个字节。返回 0到 255范围内的 int字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。 在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。 子类必须提供此方法的一个实现。
* @return 下一个数据字节;如果到达流的末尾,则返回 -1。
* @throws IOException 如果发生 I/O 错误。
*/
public abstract int read() throws IOException;
/**
* 方法: 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。以整数形式返回实际读取的字节数。在输入数据可用、检测到文件末尾或者抛出异常前此方法一直阻塞。 如果 b 的长度为 0,则不读取任何字节并返回 0;否则,尝试读取至少一个字节。如果因为流位于文件末尾而没有可用的字节,则返回值 -1;否则,至少读取一个字节并将其存储在 b中。 将读取的第一个字节存储在元素 b[0]中,下一个存储在 b[1]中,依次类推。读取的字节数最多等于 b 的长度。设 k 为实际读取的字节数;这些字节将存储在 b[0] 到 b[k-1] 的元素中,不影响 b[k] 到 b[b.length-1]的元素。
* 类 InputStream 的 read(b) 方法的效果等同于:read(b, 0, b.length)
* @param b 存储读入数据的缓冲区
* @return 读入缓冲区的总字节数;如果因为已经到达流末尾而不再有数据可用,则返回 -1。
* @throws IOException 如果不是因为流位于文件末尾而无法读取第一个字节;如果输入流已关闭;如果发生其他 I/O 错误。
*/
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
/**
* 方法: 将输入流中最多 len个数据字节读入 byte数组。尝试读取len个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。 如果 len为 0,则不读取任何字节并返回 0;否则,尝试读取至少一个字节。如果因为流位于文件末尾而没有可用的字节,则返回值 -1;否则,至少读取一个字节并将其存储在 b 中。 将读取的第一个字节存储在元素 b[off]中,下一个存储在 b[off+1]中,依次类推。读取的字节数最多等于 len。设 k 为实际读取的字节数;这些字节将存储在 b[off]到 b[off+k-1]的元素中不影响 b[off+k]到 b[off+len-1]的元素。 在任何情况下,b[0]到 b[off]的元素以及 b[off+len]到 b[b.length-1]的元素都不会受到影响。 类 InputStream的 read(b, off, len)方法重复调用方法 read()。如果第一次这样的调用导致 IOException,则从对 read(b, off, len)方法的调用中返回该异常。如果对 read() 的任何后续调用导致 IOException,则捕获该异常并将其视为到达文件末尾,到达该点时读取的字节存储在 b 中,并返回发生异常之前读取的字节数。在已读取输入数据 len 的请求数量、检测到文件结束标记、抛出异常前此方法的默认实现将一直阻塞。建议子类提供此方法更为有效的实现。
* @param b 读入数据的缓冲区(类型为byte数组即byte[])。
* @param off 数组 b中将写入数据的初始偏移量。
* @param len 要读取的最大字节数。
* @return 读入缓冲区的总字节数;如果因为已到达流末尾而不再有数据可用,则返回 -1。
* @throws IOException
*/
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 个字节。出于各种原因,skip 方法结束时跳过的字节数可能小于该数,也可能为 0。导致这种情况的原因很多,跳过 n 个字节之前已到达文件末尾只是其中一种可能。返回跳过的实际字节数。如果 n 为负,则不跳过任何字节。 此类的 skip 方法创建一个 byte 数组,然后重复将字节读入其中,直到读够 n 个字节或已到达流末尾为止。建议子类提供此方法更为
* 有效的实现。例如,可依赖搜索能力的实现。
* @param n 要跳过的字节数。
* @return 跳过的实际字节数。
* @throws IOException 如果流不支持搜索,或者发生其他 I/O 错误。
*/
public long skip(long n) throws IOException {
long remaining = n;
int nr;
if (skipBuffer == null)
skipBuffer = new byte[SKIP_BUFFER_SIZE];
byte[] localSkipBuffer = skipBuffer;
if (n <= 0) {
return 0;
}
while (remaining > 0) {
nr = read(localSkipBuffer, 0,
(int) Math.min(SKIP_BUFFER_SIZE, remaining));
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
/**方法: 返回该流在不被阻塞的情况下下一次可以读取到的数据长度。下一次调用可能是同一个线程, 也可能是另一个线程。一次读取或跳过此估计数个字节不会受阻塞,但读取或跳过的字节数可能小于该数。
* 说明: 类 InputStream 的 available 方法总是返回 0。 此方法应该由子类重写。
* @return
* @throws IOException
*/
public int available() throws IOException {
return 0;
}
/** 方法: 关闭此输入流并释放与该流关联的所有系统资源。
* 说明: InputStream 的 close 方法不执行任何操作。
*/
public void close() throws IOException {}
/** 方法:在此输入流中标记当前的位置。对 reset 方法的后续调用会在最后标记的位置重新定位此流,以便后续读取重新读取相同的字节。
* 参数:readlimit 告知此输入流在标记位置失效之前允许读取的字节数。 比如说mark(10),那么在read()10个以内的字符时,reset()操作后可以重新读取已经读出的数据,如果已经读取的数据超过10个,那reset()操作后,就不能正确读取以前的数据了, 因为此时mark标记已经失效。
*/
public synchronized void mark(int readlimit) {}
/**方法: 将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。
* @throws IOException
*/
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
/**方法: 测试此输入流是否支持 mark 和 reset 方法。是否支持 mark 和 reset 是特定输入流实例的不变属性。
* @return InputStream 的 markSupported 方法返回 false。
*/
public boolean markSupported() {
return false;
}
}
package io.bytestream;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
public abstract class OutputStream implements Closeable, Flushable {
/**
* 方法: 将指定的字节写入此输出流,write的常规协定是:向输出流写入一个字节。要写入的字节是参数 b的八个低位。b 的 24 个高位将被忽略。
* 说明: OutputStream 的子类必须提供此方法的实现。
* @param b 字节
* @exception 如果发生 I/O 错误。尤其是,如果已关闭输出流,则可能抛出 IOException
*/
public abstract void write(int b) throws IOException;
/**
* 方法: 如果发生 I/O 错误。尤其是,如果已关闭输出流,则可能抛出 IOException
* @param b 数据.
* @exception IOException 如果发生 I/O 错误。
*/
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
/**
* 方法: 将指定 byte数组中从偏移量 off开始的 len个字节写入此输出流。write(b, off, len)的常规协定是:将数组 b中的某些字节按顺序写入输出流;元素 b[off]是此操作写入的第一个字节,b[off+len-1]是此操作写入的最后一个字节。
* 说明: OutputStream 的 write 方法对每个要写出的字节调用一个参数的 write 方法。建议子类重写此方法并提供更有效的实现。
* @param b 数据.
* @param off 数据中的初始偏移量.
* @param len 要写入的字节数.
* @exception 如果 b 为 null,则抛出 NullPointerException。 如果 off为负,或 len 为负,或者 off+len大于数组 b的长度
* 则抛出 IndexOutOfBoundsException
*/
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]);
}
}
/**
* 方法: 刷新此输出流并强制写出所有缓冲的输出字节。flush 的常规协定是:如果此输出流的实现已经缓冲了以前写入的任何字节,则调用此方法指示应将这些字节立即写入它们预期的目标。 如果此流的预期目标是由基础操作系统提供的一个抽象(如一个文件),则刷新此流只能保证将以前写入到流的字节传递给操作系统进行写入,但不保证能将这些字节实际写入到物理设备(如磁盘驱动器)。
* @exception IOException - 如果发生 I/O 错误。
*/
public void flush() throws IOException {}
/**
* 方法: 关闭此输出流并释放与此流有关的所有系统资源。close 的常规协定是:该方法将关闭输出流。关闭的流不能执行输出操作,也不能重新打开。
* @exception IOException - 如果发生 I/O 错误。
*/
public void close() throws IOException {
}
}
总结:
(1)其中类声明部分为:
public abstract class InputStream implements Closeable
public abstract class OutputStream implements Closeable, Flushable
发现都有修饰符abstract,所以都是抽象类,都有实现Closeable接口,即都有实现close()方法, 这样可以关闭此流并释放与此流关联的所有系统资源。OutputStream有实现Flushable接口,即实现了flush()方法,通过将所有已缓冲输出写入底层流来刷新此流。
(2)其中方法部分,都含有实体方法和抽象方法(方法有修饰符abstract)。其中InputStream的read()方法,OutputStream的write()方法,都是抽象方法,需要子类实现自己的方法。
(3)总的来说,InputStream、OutputStream 是把输入流、输出流的概念抽象化,充分体现了面向对象编程的“抽象”特征(过程抽象),所以可以看到两个抽象方法read()和write(),表示输入流是用来读的,输出流是用来写的。具体怎么读写,类中并没有实现,交由子类去实现。
参考资料:
http://www.cnblogs.com/flyoung2008/p/3251826.html
http://weixiaolu.iteye.com/blog/1479656
http://blog.csdn.net/haoel/article/details/2224055
http://www.seas.upenn.edu/~cis1xx/resources/java/fileIO/introToFileIO.html
http://pirate.shu.edu/~wachsmut/Teaching/CSAS2214/Virtual/Lectures/lecture16.html
http://math.hws.edu/eck/cs124/javanotes6/c11/s1.html
http://bioinfo2.ugr.es/OReillyReferenceLibrary/java/exp/ch08_01.htm
http://www.cnblogs.com/shitouer/archive/2012/12/19/2823641.html
http://docs.oracle.com/javase/tutorial/essential/io/
http://bbs.itheima.com/thread-103851-1-1.html
http://www.molotang.com/articles/754.html