JDK 1.7 java.io 源码学习之InputStream和OutputStream

2/22/2017来源:ASP.NET技巧人气:3103

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() 方法,这两个方法具体均需要子类去实现

这两个类暂时 看到这里了。