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

本文详细介绍了Java中InputStream和OutputStream的使用,包括核心的read和write方法,以及异常情况处理。通过实例展示了字节流的读写过程,并提到了skip方法在流操作中的作用。同时,文章提及了子类必须实现的抽象方法及其重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值