Java的IO系统--字节流的两个顶级父类

本文详细探讨了Java中的字节流系统,重点分析了字节输入流的顶级父类InputStream和字节输出流的顶级父类OutputStream。InputStream的read()方法通过循环读取单个字节,skip()方法用于跳过指定数量的字节。OutputStream则包含write()方法,用于写入字节,其抽象方法需要子类实现。

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

这篇来说说字节流.

两个顶级父类

字节流有两个顶级父类:输入流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 {}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值