InputStream和OutputStream是Java IO API 中所有字节输入/输出流的基类,是一个抽象类,实现了Cloaseable接口
InputStream 最核心的是三个read方法:
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException;
public int read(byte b[], int off, int len) throws IOException;
第一个是抽象的read方法是后两个方法的基础,后两个方法均调用了该方法,该方法必须由子类实现。该方法表示一次读取一个字节的数据,如果已经到达流末尾则返回-1
第二个方法等效于第三个方法read(b, 0, b.length)
第三个方法表示一次可以最多读取len个数据字节缓存在byte数组b中,但读取的字节也可能小于该值,以整数的形式返回实际读取的字节数,如果已经到达流末尾则返回-1
public int read(byte b[], int off, int len) throws IOException;
/*
* b[]是用于缓冲数据的字节数组
* len是最大读取的字节数,但读取的字节也可能小于该值
* off是b[]写入数据时的初始偏移量
*/
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
// 缓存字节数组 未实例化 抛出NullPointException
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
/* off偏移量小于0
* 或 最大读取字节数小于0
* 或 最大读取字节数超出了b[]数组长度减去偏移长度
* (即b[]缓冲数组无法保存本次读取的字节数据)
* 抛出IndexOutOfBoundsException
*/
throw new IndexOutOfBoundsException();
} else if (len == 0) {
//最大读取字节数是0 直接返回0
return 0;
}
// 读取第一个字节的数据
int c = read();
// 如果到达流末尾了则直接返回-1, 即该文件是个空文件
if (c == -1) {
return -1;
}
//将读取的第一个字节数据存入缓冲数组b[off]中
b[off] = (byte)c;
/*
* 将读取的下一个字节数据存入缓冲数据b[off+1]
* 循环读取下一次字节数据
* 任何情况下,b[0]-b[off] 和 b[off+len]-b[b.length-1] 都不会受影响
*/
int i = 1;
try {
for (; i < len ; i++) {
c = read();
// 若读取到流的末尾, 则结束读取
if (c == -1) {
break;
}
//将读取的字节缓冲的缓冲字节数组内
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
// 最终读取的字节数
return i;
}
现结合图示演示该方法的执行情况:
FileInputStream fis = new FileInputStream(new File("my.txt"));
byte[] b = "123456789".getBytes();
int off = 0;
int len = 3;
fis.read(b, off, len);
fis.close();
System.out.println(new String(b));
先假设my.txt中的内容如下:
这边UTF-8编码集,一个中文占3个字节,数字和英文以及空格占1个字节
my.txt内文件内容的字节数组示意如上图。
byte[] b 是准备用于缓冲读取的字节数据的字节数组,预先放置了一部分数据,方便于比对前后结果,示意如下图:
现假设off=0,len=3, 即最多读取3个字节的数据缓存在缓冲字节数组0-2下标处,实际结果示意如下图:
缓冲字节数组前三个字节已经被替换为汉字“流”
若off=0,len = 10,则将抛出IndexOutOfBoundsException
因为此时b.length = 9, b.length - off = 9,len > b.length - off ,即缓冲字节数组已经无法缓存一次性读取的字节内容了,放不下
若off=3,len = 3, 则实际结果示意如下图:
因为off = 3, 所以其实保存的数组下标需要进行偏移
同理若off=3,len = 8,则将抛出IndexOutOfBoundsException
因为此时b.length = 9, b.length - off = 6,len > b.length - off ,即缓冲字节数组已经无法缓存一次性读取的字节内容了,放不下
skip 方法
/**
* 跳过多少字节的数据内容
*/
public long skip(long n) throws IOException {
// 重新保存该变量
long remaining = n;
int nr;
// 如果跳过的字节数小于等于0, 直接返回0
if (n <= 0) {
return 0;
}
// 跳过的字节数不能超过2048, 取2048 和 传入的跳过字节数的较小值
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变量的值, 减去读取返回的字节数, 用于控制循环
remaining -= nr;
}
//返回实际跳过的字节数
return n - remaining;
}
还是结合上述例子,只是中间执行一次skip方法:
FileInputStream fis = new FileInputStream(new File("my.txt"));
byte[] b = "123456789".getBytes();
int off = 0;
int len = 3;
fis.skip(3);
fis.read(b, off, len);
fis.close();
System.out.println(new String(b));
实际结果如下图示意:
因为my.txt 的字节内容,被跳过了3个字节的数据,直接从其第4个字节的数据开始读取了
InputStream还有其他几个扩展方法,后续再具体详述
OutputSteam 对应的则有三个核心的write方法:
public abstract void write(int b) throws IOException;
public void write(byte b[]) throws IOException;
public void write(byte b[], int off, int len) throws IOException;
同理第一个是抽象的write方法是后两个方法的基础,后两个方法均调用了该方法,该方法必须由子类实现。该方法表示一次读取一个字节的数据,如果已经到达流末尾则返回-1
第二个方法等效于第三个方法write(b, 0, b.length)
第三个方法表示一次可以最多写出byte字节数组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]);
}
}
首先还是一些异常情况的检查判断:
字节数组未实例化、off小于0、off大于字节数组长度、写入流的字节数小于0、off+len 大于字节数组长度、off+len 小于0 等情况
最后循环调用write(int b),一次一个字节的写入流中
还是结合图示说明:
OutputStream os = new FileOutputStream("my.txt");
String content = "流.write 的常规";
byte[] b = content.getBytes("UTF-8");
os.write(b, 3, 13);
os.flush();
os.close();
实际写入流的是如下内容:
OutputStream 内还有close() 和 flush() 方法,这两个方法具体均需要子类去实现
这两个类暂时 看到这里了。