这篇来说说字节流.
两个顶级父类
字节流有两个顶级父类:输入流InputStream与输出流OutputStream,两者均为抽象类.
InputStream
这是字节输入流的顶级父类,下面来分析一下它的源码.
构造器
没有显式地写出构造器,证明只有一个默认构造器.
方法
一共有三个read():
有一个无参的抽象方法read(),是留给子类去实现的.
public abstract int read() throws IOException;
第二个read()传入一个字节数组,是利用第三个read()实现的,这里直接看看第三read()的代码:
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
//没有byte[]就抛出空指针异常.
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
//偏移量或要读取的长度小于0,或要数组的长度减偏移量大于要读取的长度就抛出下标越界异常.
} else if (len == 0) {
return 0;
//要读取的长度为0就返回0.
}
int c = read(); //调用子类实现的read().
if (c == -1) {
return -1; //子类read()返回-1(即没有读取到数据),这里也返回-1.
}
b[off] = (byte)c;
//把读取到的第一个字节存入byte[]的下标为off(偏移量)处.
int i = 1;
try {
for (; i < len ; i++) {
c = read(); //读取
if (c == -1) {
break; //返回-1就停止.
}
b[off + i] = (byte)c; //存入.
}
} catch (IOException ee) {
}
return i; //返回读取了几个字节.
}
三个read()的共同特点是,每次都是读取一个字节,利用for循环进行连续读取.当发现没有读取到字节的时候,就返回-1,里面for循环中的i,每读取一个字节就循环一次,自己也就+1,所以最终返回的是读取到的字节数.
然后是skip(),补上方法内涉及的那个常量,作用是跳过字节数(n)进行读取文件,返回值根据测试就是传入的参数.
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
public long skip(long n) throws IOException {
long remaining = n;
int nr;
if (n <= 0) {
return 0; //传入的数值小于0就返回0.
}
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining); //传入的参数与常量取最小值,最大值是2048.
byte[] skipBuffer = new byte[size]; //创建size大小的字节数组,最大就是常量大小.
while (remaining > 0) {
nr = read(skipBuffer, 0, (int)Math.min(size, remaining)); //read()
if (nr < 0) {
break; //返回值小于0(也许只有-1?)就跳出循环.
}
remaining -= nr;
}
return n - remaining; //最后返回的实际上就是传入的字节数
}
写了下面的方法测试skip()的返回值:,文件大小32767字节:
public static void main(String[] args) {
InputStream in = null;
int nums = 0;
try {
in = new FileInputStream("1.txt");
System.out.println("skip()返回值:" + in.skip(16384));
for (int i = 0; i < 1000; i++) {
in.read();
nums ++;
}
System.out.println("read实际读取的字节数:" + nums);
nums = 0;
System.out.println("skip()返回值:" + in.skip(-16384));
while (in.read() > 0) {
nums ++;
}
System.out.println("read实际读取的字节数:" + nums);
} catch (IOException e) {
e.getStackTrace();
}
}
运行结果:
skip()返回值:16384
read实际读取的字节数:1000
skip()返回值:-16384
read实际读取的字节数:31767
最开始调用skip()并传入16384,代表跳过了16384字节,for循环1000次,就读取到了1000字节.然后调用skip()并传入负数,发现实际的读取结果是32768-16384-1000+16384 = 31767.
就这些代码来说,skip()相当于输入流中的一个"指针",传入正数就往前跳正数个字节,然后开始读取1000个字节,读取的时候指针向后移动1000,,然后skip()传入负值,指针往回跳负数的绝对值个字节,继续读取到最后,就得到了31767这个返回值.
剩下的方法都没有实际的逻辑或逻辑很简单,下面简单介绍一下它们的含义:
//这个字节流中还剩多少个字节.
public int available() throws IOException {
return 0;
}
//关闭这个流,实现Closeable接口的方法.
public void close() throws IOException {}
//在流中标记readLimit位置
public synchronized void mark(int readlimit) {}
//抛出IO异常:不支持mark或reset操作
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
//测试这个输入流是否支持 mark()和 reset().
public boolean markSupported() {
return false;
}
OutputStream
字节输出流的顶级父类,这个类里面的方法比InputStream中的少.
构造器
依旧是没有显式提供构造器.
方法
三个write(),其中有一个抽象方法,依旧是留给子类重写的,不过相比输入流参数多变成了一个int值:
public abstract void write(int b) throws IOException;
第二个是调用本类中给出具体实现的write(),直接来看给出具体实现的那个write():
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
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();
//偏移量小于0,或大于字节数组容量,要写入的长度小于0,
//偏移量加要写入的长度大于字节数组容量或小于0,就抛出下标越界异常.
} else if (len == 0) {
return; //数据全部写完,退出方法.
}
for (int i = 0 ; i < len ; i++) {
write(b[off + i]); //利用子类实现的write()开始写入.
}
}
可以看到,与read()返回int不同,write()没有返回值,这点需要注意.
还剩下两个没有具体实现的方法:
//关闭这个流,实现Closeable接口的方法.
public void flush() throws IOException {}
//将剩余的数据冲刷出去,实现Flushable的方法.
public void close() throws IOException {}