zoukankan      html  css  js  c++  java
  • 走进JDK(四)------InputStream、OutputStream、Reader、Writer

    InputStream

    InputStream是java中的输入流,下面基于java8来分析下InputStream源码

    一、类定义

    public abstract class InputStream implements Closeable

    Closeable接口定义了close()方法,流在使用完之后需要关闭,并且放在finally块中操作比较好。

    二、变量

    // 该变量用于确定在skip方法中使用的最大缓存数组大小。
    private static final int MAX_SKIP_BUFFER_SIZE = 2048;

    三、主要方法

    1、read()

    //从输入流中读取下一字节数据。返回的字节值为一个范围在0-255之间的int数。若由于到达流的尾部而没有字节可获取,则返回-1.直到数据可达,检测到流的末尾或者抛出一个异常,该方法才停止。由每个子类自己实现。
    public abstract int read() throws IOException;
    //将数据放入到字节数组中,内部调用的也是read(b, 0, b.length)
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }
    //byte[] b代表存放数据的字节数组,off代表将读取的数据写入数组b的起始偏移地址。len默认则是b的长度
    public int read(byte b[], int off, int len) throws IOException {
            if (b == null) {
                throw new NullPointerException();
            } else if (off < 0 || len < 0 || len > b.length - off) {
                throw new IndexOutOfBoundsException();
            } else if (len == 0) {
                return 0;
            }
    
    //这地方有个很有意思的问题,为啥不用byte作为接收值,需要int,此方法的返回值的范围为0-255,如果使用byte,则byte会将255转成-1,这样就会与read()方法的约定混淆,因为返回-1时,会认为流中已经没有数据,但此时是有数据的。
    //那么java内部byte是怎么转成int的呢?例如对于-1这个值,在java中,负数都是以补码的形式存在(可以参考本人另外一篇文章)。-1的补码就是11111111,因为byte是1个字节8位。而int是4个字节32位,当byte->int时,所有的高位都会补0,
    //-1对应的int则为00000000 00000000 00000000 11111111,这个数在int中则为255,也就是说byte的-1转成就是int的255.所以在下面(byte)c就可以将数据还原成byte,这种做法既可以保证读取到数据为128-255时不会出错,也能保证byte->int->byte
    //读取的是原始数据
            int c = read();
            if (c == -1) {
                return -1;
            }
            b[off] = (byte)c;
    
            int i = 1;
            try {
                for (; i < len ; i++) {
                    c = read();
                    if (c == -1) {
                        break;
                    }
                    b[off + i] = (byte)c;
                }
            } catch (IOException ee) {
            }
            return i;
    }

     2、available()

    可以在读写操作前先得知数据流里有多少个字节可以读取。需要注意的是,如果这个方法用在从本地文件读取数据时,一般不会遇到问题,但如果是用于网络操作,就经常会遇到一些麻烦。比如,Socket通讯时,对方明明发来了1000个字节,
    但是自己的程序调用available()方法却只得到900,或者100,甚至是0,感觉有点莫名其妙,怎么也找不到原因。其实,这是因为网络通讯往往是间断性的,一串字节往往分几批进行发送。
    本地程序调用available()方法有时得到0,这可能是对方还没有响应,也可能是对方已经响应了,但是数据还没有送达本地。对方发送了1000个字节给你,也许分成3批到达,这你就要调用3次available()方法才能将数据总数全部得到。
    InputStream中的此方法一直返回的0,能否使用取决于实现了InputStream这个抽象类的具体子类中有没有实现available这个方法。

    public int available() throws IOException {
            return 0;
    }

    很多小伙伴在读取流之前喜欢使用available()方法来判断有多少字节,写法如下:

    int count = in.available(); 
    byte[] b = new byte[count]; 
    in.read(b);

    这样在网络延迟的情况下就会有问题,应改成如下这种(不过这种也有问题,当流没数据一直循环):

    int count = 0; 
    while (count == 0) { 
        count = in.available(); 
    } 
    byte[] b = new byte[count]; 
    in.read(b);

    3、close()

    //关闭输入流并释放与其相关的系统资源。一般放在finally中操作
    public void close() throws IOException {}

    OutputStream

     一、类定义

    public abstract class OutputStream implements Closeable, Flushable

    flushable接口则定义flush()

    二、主要方法

    1、write()

    //将指定的一个字节写入此输出流。int 值为 4 个字节,此方法丢弃 int 类型高位的 3 个字节,只保留低位的 1 个字节写入(对字节来说,转为 int 其 3 个高位字节都是全 0 的,所以可以丢弃)。此方法是抽象方法,子类必须要进行实现
    public abstract void write(int b) throws IOException;
    //将 b.length 个字节从指定的 byte 数组写入此输出流。调用下边的 write 方法。
    public void write(byte b[]) throws IOException {
            write(b, 0, b.length);
    }
    //将指定 byte 数组中从偏移量 off 开始的 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]);
            }
    }

    2、flush()

    //刷新此输出流并强制写出所有缓冲的输出字节。此类未实现具体行为,子类应该复写此方法。
    public void flush() throws IOException {}

    3、close()

    //关闭此输出流并释放与此流有关的所有系统资源,此类未实现具体行为,子类应该复写此方法。
    public void close() throws IOException {}

    Reader

    一、类定义

    public abstract class Reader implements Readable, Closeable

    Readable:Readable 接口表示尝试将字符读取到指定的缓冲中。

    Closeable:Closeable 接口表示 Reader 可以被close。

    二、变量

    //最大跳过缓冲的大小
    private static final int maxSkipBufferSize = 8192;
    //是一个 char[] 类型,表示跳过缓冲
    private char skipBuffer[] = null;
    //是 Reader 的锁,用于实现同步
    protected Object lock;

    三、构造函数

    protected Reader() {
        this.lock = this;
    }
    protected Reader(Object lock) {
        if (lock == null) {
            throw new NullPointerException();
        }
        this.lock = lock;
    }

    四、主要方法

    1、read()

    //所有 read 方法最终都会调用这个抽象方法,提供给子类处理逻辑的实现。它传入的三个参数,字符数组cbuf、偏移量off和数组长度。
    public abstract int read(char cbuf[], int off, int len) throws IOException;
    //无参的 read 方法其实是默认读一个字符,new 一个 char 对象然后调用子类实现进行读取,最后返回读到的字符
    public int read() throws IOException {
        char cb[] = new char[1];
        if (read(cb, 0, 1) == -1)
            return -1;
        else
            return cb[0];
    }
    //假如 read 方法传入的参数为 char 数组时,则直接调用子类实现进行读取
    public int read(char cbuf[]) throws IOException {
        return read(cbuf, 0, cbuf.length);
    }
    //最后一个 read 方法其实是 Readable 接口定义的方法,用于读取字符到指定的 CharBuffer 对象中,逻辑是先得到 CharBuffer 对象剩余长度,根据该长度实例化 char 数组,然后调用子类实现完成读取,最后将读取到的字符放进 CharBuffer 对象
    public int read(java.nio.CharBuffer target) throws IOException {
        int len = target.remaining();
        char[] cbuf = new char[len];
        int n = read(cbuf, 0, len);
        if (n > 0)
            target.put(cbuf, 0, n);
        return n;
    }

    2、close()

    abstract public void close() throws IOException;

    Writer

     一、类定义

    public abstract class Writer implements Appendable, Closeable, Flushable

    二、变量

    //字符缓存数组。用于临时存放要写入字符输出流中的字符
    private char[] writeBuffer;
    //字符缓存数组的默认大小
    private static final int WRITE_BUFFER_SIZE = 1024;
    //用于同步针对此流的操作的对象
    protected Object lock;

    三、构造函数

    protected Writer() {
        this.lock = this;
    }
    protected Writer(Object lock) {
        if (lock == null) {
            throw new NullPointerException();
        }
        this.lock = lock;
    }

    四、主要方法

    1、write()

    //写入单个字符。要写入的字符包含在给定整数值的16个低位中,16高位被忽略。
        public void write(int c) throws IOException {
            synchronized (lock) {
                if (writeBuffer == null){
                    writeBuffer = new char[WRITE_BUFFER_SIZE];
                }
                writeBuffer[0] = (char) c;
                write(writeBuffer, 0, 1);
            }
        }
    //将一个字符数组写入到writerBuffer中
        public void write(char cbuf[]) throws IOException {
            write(cbuf, 0, cbuf.length);
        }
    //试图将字符数组中从off开始的len个字符写入输出流中。尽量写入len个字符,但写入的字节数可能少于len个,也可能为零。
    abstract public void write(char cbuf[], int off, int len) throws IOException;
    //写入字符串
    public void write(String str) throws IOException {
            write(str, 0, str.length());
        }
    //试图将字符串中从off开始的len个字符写入输出流中。尽量写入len个字符,但写入的字节数可能少于len个,也可能为零。
    public void write(String str, int off, int len) throws IOException {
            synchronized (lock) {
                char cbuf[];
                if (len <= WRITE_BUFFER_SIZE) {
                    if (writeBuffer == null) {
                        writeBuffer = new char[WRITE_BUFFER_SIZE];
                    }
                    cbuf = writeBuffer;
                } else {    // Don't permanently allocate very large buffers.
                    cbuf = new char[len];
                }
                str.getChars(off, (off + len), cbuf, 0);
                write(cbuf, 0, len);
            }
        }

    2、append()

        /**
         * 添加字符序列
         */
        public Writer append(CharSequence csq) throws IOException {
            if (csq == null)
                write("null");
            else
                write(csq.toString());
            return this;
        }
    
        /**
         * 添加字符序列的一部分
         */
        public Writer append(CharSequence csq, int start, int end) throws IOException {
            CharSequence cs = (csq == null ? "null" : csq);
            write(cs.subSequence(start, end).toString());
            return this;
        }
    
        /**
         * 添加指定字符
         */
        public Writer append(char c) throws IOException {
            write(c);
            return this;
        }

    3、flush()

        /**
         * 刷新该流的缓冲。
         */
        abstract public void flush() throws IOException;

    4、close()

        /**
         * 关闭此流,但要先刷新它。
         */
        abstract public void close() throws IOException;
  • 相关阅读:
    从读者角度来看Blog
    NDuiker项目第3天
    IssueVision的List控件源码分析
    测试一个网站的想法
    IssueVision的PaneCaption控件源码分析
    技术研究的时候不要忘了“集成创新”
    人脸识别活体检测之张张嘴和眨眨眼
    jsp>Session 小强斋
    jsp>Request对象 小强斋
    jsp>四种作用域 小强斋
  • 原文地址:https://www.cnblogs.com/alimayun/p/10693177.html
Copyright © 2011-2022 走看看