走进JDK(四)------InputStream、OutputStream、Reader、Writer

时间:2023-03-09 18:04:14
走进JDK(四)------InputStream、OutputStream、Reader、Writer

InputStream

InputStream是java中的输入流,下面基于java8来分析下InputStream源码

一、类定义

public abstract class InputStream implements Closeable

Closeable接口定义了close()方法,流在使用完之后需要关闭,并且放在finally块中操作比较好。

二、变量

// 该变量用于确定在skip方法中使用的最大缓存数组大小。
private static final int MAX_SKIP_BUFFER_SIZE = 2048;

三、主要方法

1、read()

//从输入流中读取下一字节数据。返回的字节值为一个范围在0-255之间的int数。若由于到达流的尾部而没有字节可获取,则返回-1.直到数据可达,检测到流的末尾或者抛出一个异常,该方法才停止。由每个子类自己实现。
public abstract int read() throws IOException;
//将数据放入到字节数组中,内部调用的也是read(b, 0, b.length)
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
//byte[] b代表存放数据的字节数组,off代表将读取的数据写入数组b的起始偏移地址。len默认则是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;
} //这地方有个很有意思的问题,为啥不用byte作为接收值,需要int,此方法的返回值的范围为0-255,如果使用byte,则byte会将255转成-1,这样就会与read()方法的约定混淆,因为返回-1时,会认为流中已经没有数据,但此时是有数据的。
//那么java内部byte是怎么转成int的呢?例如对于-1这个值,在java中,负数都是以补码的形式存在(可以参考本人另外一篇文章)。-1的补码就是11111111,因为byte是1个字节8位。而int是4个字节32位,当byte->int时,所有的高位都会补0,
//-1对应的int则为00000000 00000000 00000000 11111111,这个数在int中则为255,也就是说byte的-1转成就是int的255.所以在下面(byte)c就可以将数据还原成byte,这种做法既可以保证读取到数据为128-255时不会出错,也能保证byte->int->byte
//读取的是原始数据
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;
}

2、available()

可以在读写操作前先得知数据流里有多少个字节可以读取。需要注意的是,如果这个方法用在从本地文件读取数据时,一般不会遇到问题,但如果是用于网络操作,就经常会遇到一些麻烦。比如,Socket通讯时,对方明明发来了1000个字节,
但是自己的程序调用available()方法却只得到900,或者100,甚至是0,感觉有点莫名其妙,怎么也找不到原因。其实,这是因为网络通讯往往是间断性的,一串字节往往分几批进行发送。
本地程序调用available()方法有时得到0,这可能是对方还没有响应,也可能是对方已经响应了,但是数据还没有送达本地。对方发送了1000个字节给你,也许分成3批到达,这你就要调用3次available()方法才能将数据总数全部得到。
InputStream中的此方法一直返回的0,能否使用取决于实现了InputStream这个抽象类的具体子类中有没有实现available这个方法。

public int available() throws IOException {
return 0;
}

很多小伙伴在读取流之前喜欢使用available()方法来判断有多少字节,写法如下:

int count = in.available();
byte[] b = new byte[count];
in.read(b);

这样在网络延迟的情况下就会有问题,应改成如下这种(不过这种也有问题,当流没数据一直循环):

int count = 0;
while (count == 0) {
count = in.available();
}
byte[] b = new byte[count];
in.read(b);

3、close()

//关闭输入流并释放与其相关的系统资源。一般放在finally中操作
public void close() throws IOException {}

OutputStream

一、类定义

public abstract class OutputStream implements Closeable, Flushable

flushable接口则定义flush()

二、主要方法

1、write()

//将指定的一个字节写入此输出流。int 值为 4 个字节,此方法丢弃 int 类型高位的 3 个字节,只保留低位的 1 个字节写入(对字节来说,转为 int 其 3 个高位字节都是全 0 的,所以可以丢弃)。此方法是抽象方法,子类必须要进行实现
public abstract void write(int b) throws IOException;
//将 b.length 个字节从指定的 byte 数组写入此输出流。调用下边的 write 方法。
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
//将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
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]);
}
}

2、flush()

//刷新此输出流并强制写出所有缓冲的输出字节。此类未实现具体行为,子类应该复写此方法。
public void flush() throws IOException {}

3、close()

//关闭此输出流并释放与此流有关的所有系统资源,此类未实现具体行为,子类应该复写此方法。
public void close() throws IOException {}

Reader

一、类定义

public abstract class Reader implements Readable, Closeable

Readable:Readable 接口表示尝试将字符读取到指定的缓冲中。

Closeable:Closeable 接口表示 Reader 可以被close。

二、变量

//最大跳过缓冲的大小
private static final int maxSkipBufferSize = 8192;
//是一个 char[] 类型,表示跳过缓冲
private char skipBuffer[] = null;
//是 Reader 的锁,用于实现同步
protected Object lock;

三、构造函数

protected Reader() {
this.lock = this;
}
protected Reader(Object lock) {
if (lock == null) {
throw new NullPointerException();
}
this.lock = lock;
}

四、主要方法

1、read()

//所有 read 方法最终都会调用这个抽象方法,提供给子类处理逻辑的实现。它传入的三个参数,字符数组cbuf、偏移量off和数组长度。
public abstract int read(char cbuf[], int off, int len) throws IOException;
//无参的 read 方法其实是默认读一个字符,new 一个 char 对象然后调用子类实现进行读取,最后返回读到的字符
public int read() throws IOException {
char cb[] = new char[1];
if (read(cb, 0, 1) == -1)
return -1;
else
return cb[0];
}
//假如 read 方法传入的参数为 char 数组时,则直接调用子类实现进行读取
public int read(char cbuf[]) throws IOException {
return read(cbuf, 0, cbuf.length);
}
//最后一个 read 方法其实是 Readable 接口定义的方法,用于读取字符到指定的 CharBuffer 对象中,逻辑是先得到 CharBuffer 对象剩余长度,根据该长度实例化 char 数组,然后调用子类实现完成读取,最后将读取到的字符放进 CharBuffer 对象
public int read(java.nio.CharBuffer target) throws IOException {
int len = target.remaining();
char[] cbuf = new char[len];
int n = read(cbuf, 0, len);
if (n > 0)
target.put(cbuf, 0, n);
return n;
}

2、close()

abstract public void close() throws IOException;

Writer

一、类定义

public abstract class Writer implements Appendable, Closeable, Flushable

二、变量

//字符缓存数组。用于临时存放要写入字符输出流中的字符
private char[] writeBuffer;
//字符缓存数组的默认大小
private static final int WRITE_BUFFER_SIZE = 1024;
//用于同步针对此流的操作的对象
protected Object lock;

三、构造函数

protected Writer() {
this.lock = this;
}
protected Writer(Object lock) {
if (lock == null) {
throw new NullPointerException();
}
this.lock = lock;
}

四、主要方法

1、write()

//写入单个字符。要写入的字符包含在给定整数值的16个低位中,16高位被忽略。
public void write(int c) throws IOException {
synchronized (lock) {
if (writeBuffer == null){
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
writeBuffer[0] = (char) c;
write(writeBuffer, 0, 1);
}
}
//将一个字符数组写入到writerBuffer中
public void write(char cbuf[]) throws IOException {
write(cbuf, 0, cbuf.length);
}
//试图将字符数组中从off开始的len个字符写入输出流中。尽量写入len个字符,但写入的字节数可能少于len个,也可能为零。
abstract public void write(char cbuf[], int off, int len) throws IOException;
//写入字符串
public void write(String str) throws IOException {
write(str, 0, str.length());
}
//试图将字符串中从off开始的len个字符写入输出流中。尽量写入len个字符,但写入的字节数可能少于len个,也可能为零。
public void write(String str, int off, int len) throws IOException {
synchronized (lock) {
char cbuf[];
if (len <= WRITE_BUFFER_SIZE) {
if (writeBuffer == null) {
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
cbuf = writeBuffer;
} else { // Don't permanently allocate very large buffers.
cbuf = new char[len];
}
str.getChars(off, (off + len), cbuf, 0);
write(cbuf, 0, len);
}
}

2、append()

    /**
* 添加字符序列
*/
public Writer append(CharSequence csq) throws IOException {
if (csq == null)
write("null");
else
write(csq.toString());
return this;
} /**
* 添加字符序列的一部分
*/
public Writer append(CharSequence csq, int start, int end) throws IOException {
CharSequence cs = (csq == null ? "null" : csq);
write(cs.subSequence(start, end).toString());
return this;
} /**
* 添加指定字符
*/
public Writer append(char c) throws IOException {
write(c);
return this;
}

3、flush()

    /**
* 刷新该流的缓冲。
*/
abstract public void flush() throws IOException;

4、close()

    /**
* 关闭此流,但要先刷新它。
*/
abstract public void close() throws IOException;