阅读 BufferedOutputStream 和 DataInputStream 的源代码 (openjdk), 理解这两个类的继承关系, 每个方法的具体实现, 提交两个代码的逐行注释 (对 DataInputStream, 可以忽略 readUTF() 函数).源文件可以在这里下载BufferedOutputStream.java, DataInputStream.java.
一、BufferedOutputStream类:
package java.io; //该类属于java.io包
public class BufferedOutputStream extends FilterOutputStream { //该类继承了FilterOutputStream类
protected byte buf[]; //字节型的缓冲区,该数据成员只能被同属于java.io包的类访问
protected int count; //缓冲区buf中有效数据的个数,即buf[0]~buf[count-1]是有效数据
public BufferedOutputStream(OutputStream out) { //带有参数为OutputStream类型的构造函数
this(out, 8192); //为输出流out构造一个大小为8KB的缓冲区
}
public BufferedOutputStream(OutputStream out, int size) { //带有参数为OutputStream类型和大小size的构造函数
super(out); //为输出流out构造一个过滤输出流对象FilterOutputStream
if (size <= 0) { // 如果申请缓冲区的大小为负值
throw new IllegalArgumentException("Buffer size <= 0"); // 抛出参数非法的异常,异常信息为"Buffer size <= 0"
}
buf = new byte[size]; // 对于合法的参数size,创建一个大小为size的字节型buf
}
private void flushBuffer() throws IOException { //私有方法清空缓冲区函数,因为涉及对输出流的写,需抛出IO异常
if (count > 0) { // 如果有效数据个数大于0
out.write(buf, 0, count); // 将缓冲区buf中的count个自己写为0(清空)
count = 0; // 将有效数据个数清零
}
}
public synchronized void write(int b) throws IOException { // 公有方法写入数据b,参数为int型,只有低八位有效。
//此方法保证任何BufferedOutputStream对象在写的时候其他同类对象不能进行写操作,同样,所有BufferedOutputStream类的同步代码块都将被阻塞访问
if (count >= buf.length) { // 如果当前有效字节数已经填满buf
flushBuffer(); // 将buf清空
}
buf[count++] = (byte)b; // 将数据b的低八个字节写入buf[count],计数器加一
}
public synchronized void write(byte b[], int off, int len) throws IOException { // 参数重载写方法,从字节数组b的第off个字节开始拷贝len个字节的数据放入buff中
if (len >= buf.length) { // 如果要拷贝的数据量大于buf的长度
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
flushBuffer(); // 清空buff
out.write(b, off, len); // 直接调用输出流的write方法,将b从off开始的len个数据写入buf中
return;
}
if (len > buf.length - count) { // 如果buf中剩余空间不足以写入len个字节
flushBuffer(); // 清空缓冲区
}
System.arraycopy(b, off, buf, count, len); // 调用系统方法,将b数组中的b[off]~b[off+len-1]这些字节写入缓冲区的buf[count]~buf[count+len-1]
count += len; // 有效数据计数器加len
}
public synchronized void flush() throws IOException { // 公有同步方法清空缓冲区和输出流
flushBuffer(); // 将缓冲区清空
out.flush(); // 将输出流清空,实际上是什么都不做,OutputStream中是一个空方法
}
}
二、DataInputStream类:
package java.io; //该类属于java.io包
public class DataInputStream extends FilterInputStream implements DataInput { // DataInputStream 为 FilterInputStream的子类,实现了 DataInput接口
public DataInputStream(InputStream in) { // 带有输入流参数的构造函数
super(in); // 为输入流创建一个FilterInputStream对象
}
private byte bytearr[] = new byte[80]; // 私有数据成员
private char chararr[] = new char[80]; // 私有数据成员
public final int read(byte b[]) throws IOException { // 公有不可继承读方法
return in.read(b, 0, b.length); // 从输入流in中读取字节数组b容量的字节数据放入b[0]~b[b.length-1],返回所读取的字节数
}
public final int read(byte b[], int off, int len) throws IOException { // 公有不可继承读方法,参数重载
return in.read(b, off, len); // 从输入流in中读取len个字节的数据放入数组b[off]~b[off+len-1],返回所读取的字节数
}
public final void readFully(byte b[]) throws IOException { // 公有不可继承,读取数据填充满字节数组b
readFully(b, 0, b.length); // 从输入流in中读取数据并填满字节数组b,如果没有填满数组b则一直读取,直到填满位置,从字节数组b的位置0开始存储,并且读取的字节个数等于b的长度
}
public final void readFully(byte b[], int off, int len) throws IOException { // 公有不可继承,读取数据从b[off]~b[off+len-1]填充字节数组b
if (len < 0) // 如果长度小于0,抛出越界异常?为什么要抛出越界异常?off+len可能合法啊
throw new IndexOutOfBoundsException();
int n = 0; // n为在off基础上的偏移量,也可以看做读入的字节数
while (n < len) { // 读入字节数小于目标字节数,继续读
int count = in.read(b, off + n, len - n); // 从输入流in中读取len-n个数据,放入b[off+n]~b[off+len-1],注意,有可能读取不到那么多,读取到的字节数为count个
if (count < 0) // 如果读取的字节数小于0
throw new EOFException(); // 抛出读入异常,虽说是异常,但也是正常运行结束的标志,表示读到了输入流尾
n += count; // 读入了count个字节,总体读入了n个字节
}
}
public final int skipBytes(int n) throws IOException { // 公有不可继承,跳过n个字节
int total = 0; // total记录总共跳过了多少个字节
int cur = 0; // cur为每次跳过的字节数
while ((total<n) && ((cur = (int) in.skip(n-total)) > 0)) { // 跳过的总字节数不足n且当前能从输入流跳过的字节数大于0,期望从输入流中跳过n-total个字节
total += cur; // 总跳过字节数 = 之前跳过的字节数+本次跳过的字节数
}
return total; // 返回真正跳过的字节数
}
public final boolean readBoolean() throws IOException { // 公有不可继承,从输入流中读入boolean型的数据
int ch = in.read(); // 从输入流中读取一个字节
if (ch < 0) // 如果读取的字节小于0
throw new EOFException(); // 抛出EOF异常
return (ch != 0); // 如果读到的数据为0,返回false;如果读到的数据不为0,返回true
}
public final byte readByte() throws IOException { // 公有不可继承,从输入流中读入byte型的数据
int ch = in.read(); // 从输入流中读取一个字节
if (ch < 0) // 如果读取的字节小于0
throw new EOFException(); // 抛出EOF异常
return (byte)(ch); // 取读取到的数据的低八个字节返回
}
public final int readUnsignedByte() throws IOException { // 公有不可继承,从输入流中读入无符号byte型的数据,即读取值为正数的byte值
int ch = in.read(); // 从输入流中读取一个字节
if (ch < 0) // 如果读取的字节小于0
throw new EOFException(); // 抛出EOF异常
return ch; // 读取到的字节以int形式返回
}
public final short readShort() throws IOException { // 公有不可继承,从输入流中读入short型的数据
int ch1 = in.read(); // 从输入流中读取一个字节
int ch2 = in.read(); // 从输入流中读取一个字节
if ((ch1 | ch2) < 0) // 如果两个字节任意一个为负
throw new EOFException(); // 抛出EOF异常
return (short)((ch1 << 8) + (ch2 << 0)); // 将第一个字节作为short型的高八位,第二个字节作为short行的低八位,并以short数据类型返回
}
public final int readUnsignedShort() throws IOException { // 公有不可继承,从输入流中读入无符号short型的数据
int ch1 = in.read(); // 从输入流中读取一个字节
int ch2 = in.read(); // 从输入流中读取一个字节
if ((ch1 | ch2) < 0) // 如果两个字节任意一个为负
throw new EOFException(); // 抛出EOF异常
return (ch1 << 8) + (ch2 << 0); // 将第一个字节作为无符号short型的高八位,第二个字节作为无符号short型的低八位,并以int数据类型返回
}
public final char readChar() throws IOException { // 公有不可继承,从输入流中读入char型的数据
int ch1 = in.read(); // 从输入流中读取一个字节
int ch2 = in.read(); // 从输入流中读取一个字节
if ((ch1 | ch2) < 0) // 如果两个字节任意一个为负
throw new EOFException(); // 抛出EOF异常
return (char)((ch1 << 8) + (ch2 << 0)); // 将第一个字节作为char型的高八位,第二个字节作为char型的低八位,并以char数据类型返回
}
public final int readInt() throws IOException { // 公有不可继承,从输入流中读入int型的数据
int ch1 = in.read(); // 从输入流中读取第一个字节
int ch2 = in.read(); // 从输入流中读取第二个字节
int ch3 = in.read(); // 从输入流中读取第三个字节
int ch4 = in.read(); // 从输入流中读取第四个字节
if ((ch1 | ch2 | ch3 | ch4) < 0) // 如果四个字节任意一个为负
throw new EOFException(); // 抛出EOF异常
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); // 将第一个字节作为int型的24~31位,第二个字节作为int型的16~23位,第三个字节作为int型的8~15位,第四个字节作为int型的0~7位,并以int数据类型返回
}
private byte readBuffer[] = new byte[8]; // 私有数据成员读缓冲区,大小为8个字节
public final long readLong() throws IOException { // 公有不可继承,从输入流中读入long型的数据
readFully(readBuffer, 0, 8); // 从输入流中读取8个字节放入readBuffer中
return (((long)readBuffer[0] << 56) +
((long)(readBuffer[1] & 255) << 48) +
((long)(readBuffer[2] & 255) << 40) +
((long)(readBuffer[3] & 255) << 32) +
((long)(readBuffer[4] & 255) << 24) +
((readBuffer[5] & 255) << 16) +
((readBuffer[6] & 255) << 8) +
((readBuffer[7] & 255) << 0)); // 8个字节根据先后顺序依次放入long型数据的最高8位到最低8位
}
public final float readFloat() throws IOException { // 公有不可继承,从输入流中读入float型的数据
return Float.intBitsToFloat(readInt()); // 以int形式读取4个字节,将其转化为float,因为float类型的数据也是32位
}
public final double readDouble() throws IOException { // 公有不可继承,从输入流中读入double型的数据
return Double.longBitsToDouble(readLong());// 以long形式读取8个字节,将其转化为double,因为double类型的数据也是64位
}
private char lineBuffer[]; // 私有数据成员char型数组lineBuffer
@Deprecated //这个注释是一个标记注释。所谓标记注释,就是在源程序中加入这个标记后,并不影响程序的编译,但有时编译器会显示一些警告信息。
// 如果经常使用eclipse等IDE编写java程序时,可能会经常在属性或方法提示中看到这个词。
// 如果某个类成员的提示中出现了个词,就表示这个并不建议使用这个类成员。
// 因为这个类成员在未来的JDK版本中可能被删除。之所以在现在还保留,是因为给那些已经使用了这些类成员的程序一个缓冲期。
// 如果现在就去了,那么这些程序就无法在新的编译器中编译了。
public final String readLine() throws IOException { // 公有不可继承,从输入流中读入一行的数据
char buf[] = lineBuffer; // 局部变量char型数组buf,与lineBuffer有相同的引用
if (buf == null) { // 如果buf为空
buf = lineBuffer = new char[128]; // buf和lineBuffer均置为128个char型的大小
}
int room = buf.length; // 空间为buf的长度
int offset = 0; // 偏移量初始化为0
int c;
loop: while (true) { // 循环读取操作
switch (c = in.read()) {
case -1: // 如果读入的数据为-1
case '\n': // 或者读入了换行符
break loop; //说明该行已读完,跳出循环读取操作
case '\r': // 如果读入了\r,即将光标移到行首
int c2 = in.read(); // 再读一个字节
if ((c2 != '\n') && (c2 != -1)) { // 如果该字节既不是结束符也不是换行符,即正常字符
if (!(in instanceof PushbackInputStream)) { // 如果in不是PushbackInputStream类的实例
this.in = new PushbackInputStream(in); // 则将in创建为PushbackInputStream的实例
}
((PushbackInputStream)in).unread(c2); // PushbackInputStream拥有一个PushBack缓冲区,从PushbackInputStream读出数据后,只要PushBack缓冲区没有满,就可以使用unread()将数据推回流的前端
}
break loop; // 跳出循环读取操作
default: // 如果读入的是其他字符
if (--room < 0) { // buf已经读够了所能放下的数据量
buf = new char[offset + 128]; // 将buf的大小扩大为offset+128
room = buf.length - offset - 1; // 剩余空间即为新的大小减去偏移量+1
System.arraycopy(lineBuffer, 0, buf, 0, offset); // 将lineBuffer中的数据,即原有数据放入buf中
lineBuffer = buf; // lineBuffer与buf重新指向相同的引用
}
buf[offset++] = (char) c; // 当前读入的字符放入buf中,计数器offset加一
break;
}
}
if ((c == -1) && (offset == 0)) { // 如果读入的为-1且无偏移量,说明这是空的一行
return null; // 返回null
}
return String.copyValueOf(buf, 0, offset); // 将字符数组转化为String对象返回
}
}