IuputStream分析

字节输入流的基础类InputStream和OutputStream有许多相同的地方,也有许多不同点,需注意区分。
概要

与OutputStream相比,这个抽象类定义的方法要多一些。读入数据有些时候的确比较麻烦。读入数据的时候,需要判断数据源是否有数据,是否结束等等一些情况,相对复杂。这个抽象类定义了一个抽象方法read(),实现了Closeable接口。先看一下定义的一些方法:
首先是三个读方法:
public abstract int read() throws IOException;

public int read(byte b[], int off, int len) throws IOException

public int read(byte b[]) throws IOException
接着是一个用于检查当前可获取的字节数:
public int available() throws IOException

一个跳过当前位置的方法:
public long skip(long n) throws IOException

还有三个与标记相关的方法:
public synchronized void mark(int readlimit)

public synchronized void reset() throws IOException

public boolean markSupported()

最后是close方法。
public void close() throws IOException

方法分析

分析一个方法,出了修饰符等一些基本的东西以外,重点要关注参数、返回值以及可能抛出的异常。在输出流中,write方法的返回类型都是void,将参数中的内容写出到输出流。但是在输入流中,情况有些不一样。返回的都是int类型(代表的意义有些不一样),参数则是空或者存储读入字节的数组。一定要注意区分。
int read ()

这是一个抽象方法,子类必须提供具体实现。它试图从数据源读入一个字节,如果有数据可读,则返回该字节代表的无符号【输入输出流中,字节所代表的都是无符号整数,必须跟byte类型区分清楚】整数(范围为0-255),如果由于数据源结束而无法获取数据,则返回-1。这个方法阻塞直到有数据可读取、或者遇到流结尾、或者发生异常。注意与write方法比较参数与返回值。例如下面的代码段:
int[] data = new int[10];
for (int i = 0; i < data.length; i++) {
data[i] = System.in.read( );
}

注意这个过程中可能有异常抛出,必须抛出或者处理。注意方法返回的是整数,范围为0-255.可以将其转化成字节,例如
b[i] = (byte) System.in.read( );

需要注意的是,原来整数的范围是0-255,转换之后的byte类型范围为-128-127.
另外一点,在理解下面的mark方法时,可能需要用到当前位置这个概念,当read方法读取一个字节后,后把当前位置自动往下一个字节移动。(可以想象一个位置指示针)
int read (byte b[] , int off, int len)

与输出流相同,这个方法也是用于一次读入多个字节。这个方法读入一些(不确定),并将读入的数据存到字节b当中,从off位开始。返回的是实际读入的字节数。这个方法阻塞直到有数据可读取、或者遇到流结尾、或者发生异常.
特别要注意,该方法试图读入len个字节,但实际并不一定读入这么多个字节。如果len为0,则什么都不读取。如果因为遇到流结束而没有字节可以读取,返回-1.第一个字节(如果有读入的话)存放在b[off],第二个字节存放在b[off+1],等等。假设实际上读入了k个字节,则依次存入b[off]到b[off+k-1],而b[off+k]到b[off+len-1]不受影响。除此以外的字节数组元素不受影响。看一些默认的实现代码:
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;
}

注意到,在这个实现中,该方法会一直阻塞,直到len个字节全部读取完毕(for语句),或者遇到流结束,或者异常出现。这不是有效的实现方式,子类大多提供了更加高效的实现方式。而且,从代码可以看到,这个实现知识重复调用read()方法,当第一次调用read()(第10行)方法出现异常时,异常从read(byte b[] ,int off, int len)返回。从第一次调用read()开始,如果出现异常,则捕获异常,就像遇到流结束一样(return i),将发生异常之前读入的数据存入数组。可能抛出的异常有IOException、IndexOutofBoundsException、NullPointerException(如果字节数组b为null).这个方法有点复杂,需注意理解。
int read(byte b[])

可以把这个方法当中是上一个方法的特例,当off=0, len=b.length的时候,就变成这一方法,不再多说。
int available()

InputStream中的方法都是比较难以理解的,available方法也一样。这个方法返回的是一个估计值,代表在下一次调用这个输入流不阻塞的情况下,有多少字节可以使用。这个调用可能是不同的线程来完成。也就是说,下一次调用该输入流方法,读入或者跳过的字节数如果不多于这个值,就不会阻塞。【英文原文:Returns an estimate of the number of bytes that can be read (or skipped over) from this input stream without blocking by the next invocation of a method for this input stream. The next invocation might be the same thread or another thread. A single read or skip of this many bytes will not block, but may read or skip fewer bytes.】遇到流结尾的时候,返回0.这个方法看起来很不靠谱的样子。默认的实现返回的是0,如下:
public int available() throws IOException {
return 0;
}
以下是一个实例:
try {
byte[] b = new byte[System.in.available( )];
System.in.read(b);
}
catch (IOException ex) {
System.err.println("Couldn't read from System.in!");
}


long skip(long n)

这个方法尝试跳过当前位置开始的n个字节。注意是尝试,实际跳过的字节可能少于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;
}

看起来很复杂的样子,这个实现中,使用一个字节一个字节读入值来跳过,显然是很低效的,子类鼓励用高效的方式实现。在读入的时候,尝试着一次读入2048(MAX_SKIP_BUFFER_SIZE)字节。这个方法看起来也很不靠谱,都不知道实际上要跳过多少字节,可以用以下代码段来跳过固定字节(在例子中为80):
try {
long bytesSkipped = 0;
long bytesToSkip = 80;
while (bytesSkipped < bytesToSkip) {
long n = in.skip(bytesToSkip - bytesSkipped);
if (n == -1) break;
bytesSkipped += n;
}
}
catch (IOException ex) {
System.err.println(ex);
}

mark(int readlimit)

这个方法用于在输入流的当前位置作个标记,此后可以利用reset方法重新读取这些值。具体地,做了标记之后继续读入的字节数小于等于readlimit,则这个流会记住从标记位置开始到当前位置的所有值,以便重读。但是,如果标记之后继续读入的字节数超过readlimit,则这个流不再记住这些值,也就是该标记失效了。看下图:

[img]http://dl.iteye.com/upload/attachment/0080/0791/8caab176-3001-31c2-a39e-a6c144b69a11.png[/img]


假设我们最后一次调用mark方法(多次调用时会覆盖),图中绿色部分代表readlimit的大小,那么如果当前位置位于分界线之前,比如说A的位置,则这个标记依然有效。想法,B位置则无效。
默认的实现中,什么都不干。如果markSupported返回false,该方法什么都不做。
void reset()

这个方法将流的当前位置重新设置到最后一次调用mark的位置。具体地说:
如果markSupported方法返回true,
如果当前流的mark方法从未被调用过,或者mark已经无效了,则抛出IOException
如果没有抛出IO异常,则当前位置被设置为上次调用mark的位置或者文件的开始。
如果markSupported返回false:
调用reset方法抛出IOException异常
如果没有抛出异常,则被定为到某种固定的状态(位置),这种状态根据流的类型和流创建的方式的不同而不同。
默认的实现中也是什么没干,值抛出IO异常。
boolean markSupported()

用于测试该输入流是否支持mark和reset方法。没啥可说。默认实现返回false。
void close()

关闭输入流。一样可能抛出异常。

一个自定义子类(摘自Java I/O)

import java.util.*;
import java.io.*;
public class RandomInputStream extends InputStream {
private Random generator = new Random( );
private boolean closed = false;
public int read( ) throws IOException {
checkOpen( );
int result = generator.nextInt( ) % 256;
if (result < 0) result = -result;
return result;
}
public int read(byte[] data, int offset, int length) throws IOException {
checkOpen( );
byte[] temp = new byte[length];
generator.nextBytes(temp);
System.arraycopy(temp, 0, data, offset, length);
return length;
}
public int read(byte[] data) throws IOException {
checkOpen( );
generator.nextBytes(data);
return data.length;
}
public long skip(long bytesToSkip) throws IOException {
checkOpen( );
// It's all random so skipping has no effect.
return bytesToSkip;
}
public void close( ) {
this.closed = true;
}
private void checkOpen( ) throws IOException {
if (closed) throw new IOException("Input stream closed");
}
public int available( ) {
// Limited only by available memory and the size of an array.
return Integer.MAX_VALUE;
}
}

总算结束。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值