刚开始学IO流,会经常感到奇怪,为什么read方法返回的都是int类型,而且,在包装流中经常会弄混淆,看了传智播客老师的视频,加上API文档的了解,总算是完全弄懂了。
下面贴出四个包装流的代码,分别包装了inputstream、outputstream、reader、writer:
public class MyBufferedInputStream extends InputStream {
private InputStream in;
private byte[] buf = new byte[1024]; //自定义字节数组作为缓冲区
private int len = 0; //定义一个变量,用于记住缓冲区可用字节数量
private int pos = 0; //定义一个变量,用于记住缓冲区当前可用字节的角标
//通过构造函数组合被包装的流
public MyBufferedInputStream(InputStream in) {
this.in = in;
}
@Override
public int read() throws IOException {
//判断缓冲区是否还有可用字节
if(len==0) {
//一次性填充缓冲区
len = in.read(buf);
//pos要置零
pos = 0;
}
//判断是否到了流的末尾
if(len==-1) {
return -1;
}
//返回一个字节,可用字节就少一个,所以len到减1
len--;
//返回当前可用字节,升级为int,前面加24个0
//11111111 & 00000000 00000000 00000000 11111111 0xff
return buf[pos++]&0xff;
}
public void close() throws IOException {
//关闭底层被包装的流
in.close();
}
}
public class MyBufferedOutputStream extends OutputStream {
private OutputStream out;
private byte[] buf = new byte[1024]; //自定义缓冲区
private int pos = 0; //定义一个变量,记住当前缓冲区可存储的位置
//组合被包装的流
public MyBufferedOutputStream(OutputStream out) {
this.out = out;
}
@Override
public void write(int b) throws IOException {
//判断缓冲区是否已满
if(pos==1024) {
//刷新缓冲区
flush();
//pos置零
pos = 0;
}
//将当前字节存入缓冲区对应的空位
buf[pos++] = (byte) b;
}
public void flush() throws IOException {
//将缓冲区有用的字节写入底层流
out.write(buf, 0, pos);
}
public void close() throws IOException {
//刷心缓冲区
flush();
//关闭底层的流
out.close();
}
}
public class MyBufferedReader extends Reader {
private Reader r;
private char[] buf = new char[1024];
private int pos;
private int len;
public MyBufferedReader(Reader r) {
this.r = r;
}
/* 实现read方法,实现缓冲的功能
* 分析:
* 1.当别人第一次调用次方法时,一次性填充缓冲区
* 2.定义一个变量len记住缓冲区的可用字符数量
* 3.定义一个变量pos用于记住当前可用字符的角标
* 3.将数组的第一个元素返回,
* 4.当别人第二次调用时,将数组的第二个元素返回,
* 5.每次返回一个元素,len--
* 6.当len为0时,说明数组没有可用字符了,再次填充缓冲区,直到读到-1,
*
* 思路:
* 1.定义两个成员变量,len记住可用字符数量,pos记住当前可用字符的位置
* 2.判断len,如果len为0,填充缓冲区,pos要置零
* 3.判断len,如果为-1,我们也返回-1
* 4.将pos位置的元素返回,pos要++
*/
public int read() throws IOException {
if(len==0) {
len = r.read(buf);
pos = 0;
}
if(len==-1)
return -1;
len--;
return buf[pos++];
}
/* 实现readLine方法,实现读一行的功能
* 1.循环调用自己的read方法,读取字符
* 2.直到遇上回车换行,方法结束
*/
public String readLine() throws IOException {
StringBuilder sb = new StringBuilder();
int ch;
while((ch=read())!=-1) {
if(ch=='\r')
continue;
if(ch=='\n')
break;
sb.append((char)ch);
}
if(ch==-1&&sb.length()==0)
return null;
return sb.toString();
}
public void close() throws IOException {
r.close();
}
@Override
public int read(char[] buf, int offset, int len) throws IOException {
int count = 0;
for(int i=offset; i<offset+len; i++) {
int ch = read();
if(ch==-1)
break;
count++;
buf[i] = (char) ch;
}
return count;
}
}
public class MyBufferedWriter extends Writer {
private Writer w;
private char[] buf = new char[1024];
private int pos = 0;
public MyBufferedWriter(Writer w) {
this.w = w;
}
/*
* 定义write方法,实现写入一个字符,实现缓冲的功能
* 1.定义一个变量pos记住当前的写入位置,
* 2.每次调用者调用此方法都会传入一个字符,将字符存入缓冲数组的pos位置,
* 3.pos++;
* 4.每次一上来都要判断,如果pos==1024,刷新缓冲区,pos=0;
*/
public void write(int ch) throws IOException {
if(pos==1024) {
flush();
pos = 0;
}
buf[pos++] = (char) ch;
}
@Override
public void close() throws IOException {
flush();
w.close();
}
@Override
public void flush() throws IOException {
// 刷新缓冲区,将当前缓冲区有用字符一次性写入底层被包装的流
w.write(buf, 0, pos);
}
public void write(String data) throws IOException {
char[] chars = data.toCharArray();
for(char c : chars)
write(c);
}
public void newLine() throws IOException {
write("\r\n");
}
@Override
public void write(char[] buf, int offset, int len) throws IOException {
for(int i=offset; i<offset+len; i++)
write(buf[i]);
}
}
所有这些包装流中,无参的read()方法,返回的int都是字符或字节的ASCII码,参数为数组的read(buf)返回的int是数组中的字符或字节个数,用来确定读取到的字符或字节在数组中的位置,两个read方法返回-1时都代表到了流的末尾。