A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

BufferedInputStream是缓冲输入流,作用是为另一个输入流添加一些功能,比如缓冲输入功能以及支持mark和reset方法的能力。在创建BufferedInputStream时,会在内存中创建一个内部缓冲区数组。在读取或跳过流中的字节时,可根据需要从包含的输入流一次性填充多个字节到该内部缓冲区。当程序需要读取字节时,直接从内部缓冲区中读取。当内部缓冲区中数据被读完后,会再次从包含的输入流一次性填充多个字节到该内部缓冲区。mark操作会记录输入流中的某个点,reset操作使得在从输入流中获取新字节之前,再次读取自最后一次mark操作后读取的所有字节。

BufferedOutputStream是缓冲输出流,通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。

下面先介绍下BufferedInputStream。

BufferedInputStreampublic class BufferedInputStream extends FilterInputStream {    //缓冲区默认的默认大小    private static int DEFAULT_BUFFER_SIZE = 8192;    /**     * 分派给arrays的最大容量     * 为什么要减去8呢?     * 因为某些VM会在数组中保留一些头字,尝试分配这个最大存储容量,     * 可能会导致array容量大于VM的limit,最终导致OutOfMemoryError。     */    private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;    /**     * 存放数据的内部缓冲数组。     * 当有必要时,可能会被另一个不同容量的数组替代。     */    protected volatile byte buf[];    /**     * 为缓冲区提供compareAndSet的原子更新器。     * 这是很有必要的,因为关闭操作可以使异步的。我们使用非空的缓冲区数组作为流被关闭的指示器。     * 该成员变量与buf数组的volatile关键字共同作用,实现了当在多线程环境中操作BufferedInputStream对象时,buf和bufUpdater都具有原子性。     */    private static final        AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =        AtomicReferenceFieldUpdater.newUpdater        (BufferedInputStream.class,  byte[].class, "buf");    /**     * 缓冲区中的字节数。     */    protected int count;    /**     * 缓冲区当前位置的索引     */    protected int pos;    /**     * 最后一次调用mark方法时pos字段的值。     */    protected int markpos = -1;    /**     * 调用mark方法后,在后续调用reset方法失败之前所允许的最大提前读取量。     * markpos的最大值     */    protected int marklimit;    /**     * 获取输入流。     * 判断输入流是否为null,如果为null,抛出异常,否则返回输入流。     */    private InputStream getInIfOpen() throws IOException {        InputStream input = in;        if (input == null)            throw new IOException("Stream closed");        return input;    }    /**     * 获取缓冲区数组。     * 检查缓冲区数组是否为null,如果为null,抛出异常,否则返回缓冲区数组。     */    private byte[] getBufIfOpen() throws IOException {        byte[] buffer = buf;        if (buffer == null)            throw new IOException("Stream closed");        return buffer;    }    /**     * 构造方法之一     * 创建一个缓冲区大小为DEFAULT_BUFFER_SIZE的BufferedInputStream。     */    public BufferedInputStream(InputStream in) {        this(in, DEFAULT_BUFFER_SIZE);    }    /**     * 构造方法之一。     * 创建一个缓冲区大小为size的BufferedInputStream。     *     */    public BufferedInputStream(InputStream in, int size) {        super(in);        if (size <= 0) {            throw new IllegalArgumentException("Buffer size <= 0");        }        buf = new byte[size];    }    /**     * 填充缓冲区。     */    private void fill() throws IOException {        //获取缓冲区数组        byte[] buffer = getBufIfOpen();        if (markpos < 0)// case1:缓冲区没有被标记。如果缓冲区被标记,那么markpos肯定大于等于0            pos = 0;:        else if (pos >= buffer.length)  //缓冲区被标记,且缓冲器已满。pos >= buffer.length说明缓冲区已满。            if (markpos > 0) {  //case2:缓冲区被标记且标记位置大于0,且缓冲器已满。/* can throw away early part of the buffer */                //获取被标记位置和缓冲区末尾之间的长度                int sz = pos - markpos;                //将buffer中从markpos开始的数据拷贝到buffer中(从位置0开始填充,填充长度是sz)                System.arraycopy(buffer, markpos, buffer, 0, sz);                //将缓存区当前位置定位为sz                pos = sz;                //将标记位置定位为0                markpos = 0;            } else if (buffer.length >= marklimit) {//case3:缓冲区被标记且标记位置等于0,且缓冲器已满,且缓冲区太大导致标记无效                //将标记位置定位为-1                markpos = -1;                   //将当前位置定位为0                pos = 0;                    } else if (buffer.length >= MAX_BUFFER_SIZE) {//case4:缓冲区被标记且标记位置等于0,且缓冲器已满,且缓冲区超出允许范围                //抛出异常                throw new OutOfMemoryError("Required array size too large");            } else {//case5:不确定这是什么情况,但从下面的代码中可以知道这种情况下需要对缓冲区进行扩容/* grow buffer */                int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?//                        pos * 2 : MAX_BUFFER_SIZE;                if (nsz > marklimit)                    nsz = marklimit;                byte nbuf[] = new byte[nsz];                System.arraycopy(buffer, 0, nbuf, 0, pos);                if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {                    throw new IOException("Stream closed");                }                buffer = nbuf;            }        count = pos;        //从输入流中读取数据,将缓冲区填满        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);        if (n > 0)            count = n + pos;    }    /**     * 从缓冲区中读取下个字节     *     * 如果已到输入流末尾,返回-1。     */    public synchronized int read() throws IOException {        //pos >= count,说明已经读到了缓冲区末尾,这时应该从输入轮流中读取一批数据来填充缓冲区        if (pos >= count) {            fill();            //已经执行了填充数据的方法,缓冲区中还是没有数据可读,说明根本就没有数据被填充到缓冲区,这种情况一般是输入流已经没有数据可读,返回-1            if (pos >= count)                return -1;        }        //获取缓冲区,从缓冲区中读取下个字节        return getBufIfOpen()[pos++] & 0xff;    }    /**     * 从此字节输入流中给定偏移量off处开始将len个字节读取到指定的byte数组中。     *      * @param   b     目标字节数组     * @param   off   起始偏移量     * @param   len   读取的最大字节数。     * @return  读入b的总字节数,如果由于已到达流末尾而不再有数据,则返回-1。     */    private int read1(byte[] b, int off, int len) throws IOException {        //计算缓冲区中剩余可读的字节数        int avail = count - pos;        //如果缓冲区中没有可读的字节        if (avail <= 0) {            /* If the requested length is at least as large as the buffer, and               if there is no mark/reset activity, do not bother to copy the               bytes into the local buffer.  In this way buffered streams will               cascade harmlessly. */            //如果读取的长度大于缓冲区长度且没有做标记            if (len >= getBufIfOpen().length && markpos < 0) {                //直接从输入流中读数据到b中                return getInIfOpen().read(b, off, len);            }            //填充缓冲区            fill();            //再次计算缓冲区中可读字节数            avail = count - pos;            //如果缓冲区中依然没有可读字节数,说明已经到达流末尾,返回-1            if (avail <= 0) return -1;        }        //计算实际要读取的字节数        int cnt = (avail < len) ? avail : len;        //从缓冲区的pos位置将cnt个字节读取到b中        System.arraycopy(getBufIfOpen(), pos, b, off, cnt);        //缓冲区当前位置+cnt        pos += cnt;        //返回实际读取的字节数        return cnt;    }    /**     * 从此字节输入流中给定偏移量处开始将各字节读取到指定的 byte 数组中。     *     * @param   b     目标字节数组     * @param   off   起始偏移量     * @param   len   读取的最大字节数。     * @return  读入b的总字节数,如果由于已到达流末尾而不再有数据,则返回-1。     */    public synchronized int read(byte b[], int off, int len)        throws IOException    {        //检查输入流是否关闭        getBufIfOpen();         //检查参数是否合法        if ((off | len | (off + len) | (b.length - (off + len))) < 0) {            throw new IndexOutOfBoundsException();        } else if (len == 0) {            return 0;        }        //读取到指定长度的数据才返回        int n = 0;        for (;;) {            int nread = read1(b, off + n, len - n);            if (nread <= 0)                return (n == 0) ? nread : n;            n += nread;            if (n >= len)                return n;            // if not closed but no bytes available, return            InputStream input = in;            if (input != null && input.available() <= 0)                return n;        }    }    /**     * 跳过和丢弃此输入流或缓冲区中的n个字节。     * 返回实际跳过的字节数     */    public synchronized long skip(long n) throws IOException {        //检查输入流是否关闭        getBufIfOpen();        //如果参数不合法,返回0        if (n <= 0) {            return 0;        }        //缓冲区中可读的字节数        long avail = count - pos;        //如果avail<=0,即已经到达缓冲区末尾        if (avail <= 0) {            // 如果没有调用过mark方法,直接在输入流中跳过n个字节            if (markpos <0)                return getInIfOpen().skip(n);            // 填充缓冲区            fill();            //重新计算可以读取的字节数            avail = count - pos;            //如果依然小于0,说明已经到了输入流末尾,没数据可读了,返回0            if (avail <= 0)                return 0;        }        //计算实际跳过的字节数        long skipped = (avail < n) ? avail : n;        //在缓冲区中跳过skipped个字节        pos += skipped;        //返回实际跳过的字节        return skipped;    }    /**     * 返回可以从此输入流读取(或跳过)、且不受此输入流接下来的方法调用阻塞的估计字节数。     */    public synchronized int available() throws IOException {        //计算缓冲区可读的字节数        int n = count - pos;        //获取输入流中可读的字节数        int avail = getInIfOpen().available();        //?        return n > (Integer.MAX_VALUE - avail)                    ? Integer.MAX_VALUE                    : n + avail;    }    /**     * 在此缓冲区中标记当前的位置。对reset方法的后续调用会在最后标记的位置重新定位此缓冲区,以便后续读取重新读取相同的字节。     *     * readlimit告知此缓冲区在标记位置失效之前允许读取的字节数。     *     * mark的一般约定是:如果方法markSupported返回true,那么缓冲区总是在调用mark之后记录所有读取的字节,并时刻准备在调用方法reset时(无论何时),再次提供这些相同的字节。但是,如果在调用reset之前可以从流中读取多于readlimit的字节,则不需要该流记录任何数据。     *     * @param   readlimit   在标记位置失效前可以读取字节的最大限制。     * @see     java.io.BufferedInputStream#reset()     */    public synchronized void mark(int readlimit) {        marklimit = readlimit;        markpos = pos;    }    /**     * 将此缓冲区重新定位到最后一次对此输入流调用mark方法时的位置。     *     * 如果创建流以后未调用方法mark,或最后调用mark以后从该流读取的     * 字节数大于最后调用mark时的参数,则可能抛出IOException。     *      * 如果未抛出这样的IOException,将此缓冲区重新定位到最后一次对     * 此缓冲区调用mark方法时的位置。     *     * @exception  IOException  如果未标记或该标记失效。     * @see     java.io.BufferedInputStream#mark(int)     * @see     java.io.IOException     */    public synchronized void reset() throws IOException {        getBufIfOpen(); // Cause exception if closed        if (markpos < 0)            throw new IOException("Resetting to invalid mark");        pos = markpos;    }    /**     * 测试此输入流是否支持mark和reset方法。     * 是否支持mark和reset是特定输入流实例的不变属性。      */    public boolean markSupported() {        return true;    }    /**     * 关闭此输入流并释放与该流关联的所有系统资源。     * 关闭了该流之后,后续的read()、available()、reset()或 skip()调用都将抛出IOException。     * 关闭之前已关闭的流不会产生任何效果。     */    public void close() throws IOException {        byte[] buffer;        while ( (buffer = buf) != null) {            if (bufUpdater.compareAndSet(this, buffer, null)) {                InputStream input = in;                in = null;                if (input != null)                    input.close();                return;            }            // Else retry in case a new buf was CASed in fill()        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345

思考

  • 阅读完代码后,能不能讲下marklimit的作用是什么?
demoimport java.io.BufferedInputStream;import java.io.FileInputStream;import java.io.IOException;/** * BufferedInputStream demo */public class BufferedInputStreamTest {    public static void main(String[] args) {        test();    }    /**     * BufferedInputStream的API测试函数     */    private static void test() {        try {            BufferedInputStream in = new BufferedInputStream(new FileInputStream("test.txt"), 10);            int avail = in.available();            System.out.println("可读字节数:" + avail);            System.out.println("除最后十个字节外,读出所有字节");            for (int i = 0; i < avail - 10; i++) {                System.out.print(in.read() + ",");            }            System.out.println("\n可读字节数:" + in.available() + "\n");            if (!in.markSupported()) {                System.out.println("make/reset not supported!\n");                return;            } else                System.out.println("make/reset supported!\n");            in.mark(1024);            System.out.println("使用skip方法跳过两个字节");            in.skip(2);            System.out.println("可读字节数:" + in.available());            in.reset();            System.out.println("执行reset方法后,可读字节数:" + in.available() + "\n");            byte[] buf = new byte[5];            in.read(buf, 0, 5);            // 将buf转换为String字符串            String str1 = new String(buf);            System.out.println("读取的5个字节为" + str1);            in.close();        } catch (IOException e) {            e.printStackTrace();        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

test.txt内容为

1233424253sfasfsafasj
  • 1
  • 2

运行结果为

可读字节数:25除最后十个字节外,读出所有字节49,50,51,51,52,50,52,50,53,51,13,10,115,102,97,可读字节数:10make/reset supported!使用skip方法跳过两个字节可读字节数:8执行reset方法后,可读字节数:10读取的5个字节为sfsaf
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
BufferedOutputStreampublicclass BufferedOutputStream extends FilterOutputStream {    //用于存储数据的内部缓冲区    protected byte buf[];    /**     * 缓冲区中的有效字节数。     */    protected int count;    /**     * 构造函数之一。     * 创建缓冲区大小为8192的新的缓冲输出流     */    public BufferedOutputStream(OutputStream out) {        this(out, 8192);    }    /**     * 构造函数之一。     * 创建缓冲区大小为size的新的缓冲输出流。     *     * @param   out    指定输出流     * @param   size   缓冲区大小     * @exception IllegalArgumentException 如果size<=0     */    public BufferedOutputStream(OutputStream out, int size) {        super(out);        if (size <= 0) {            throw new IllegalArgumentException("Buffer size <= 0");        }        buf = new byte[size];    }    /**      * 刷新缓冲区。     * 将缓冲区中所有字节写入此缓冲输出流,并清空缓冲区     */    private void flushBuffer() throws IOException {        if (count > 0) {            //将缓冲区中所有字节写入此输出流。            out.write(buf, 0, count);            //清空缓冲区            count = 0;        }    }    /**     * 将指定数据字节写入到此缓冲输出流中     */    public synchronized void write(int b) throws IOException {        //如果缓冲区已满,先将缓冲区中数据写入输出流        if (count >= buf.length) {            flushBuffer();        }        //将数据b转化为字节写入到缓冲区中        buf[count++] = (byte)b;    }    /**     * 将指定byte数组中从偏移量off开始的len个字节写入此缓冲的输出流。     *     * 1.若写入长度大于缓冲区大小,则先将缓冲区中的数据写入到输出流     * ,然后直接将数组b写入到输出流中     * 2.如果写入长度大于缓冲区中的剩余空间,     * 则先将缓冲区中数据写入输出流,然后将数据写入到缓冲区中     *     * @param      b     要写入的数据     * @param      off   偏移量     * @param      len   写入数据字节的长度     * @exception  IOException  if an I/O error occurs.     */    public synchronized void write(byte b[], int off, int len) throws IOException {        //若写入长度大于缓冲区大小,则先将缓冲区中的数据写入到输出流,然后直接将数组b写入到输出流中        if (len >= buf.length) {            flushBuffer();            out.write(b, off, len);            return;        }        //如果写入长度大于缓冲区中的剩余空间,则先将缓冲区中数据写入输出流        if (len > buf.length - count) {            flushBuffer();        }        //将数据写入到缓冲区中        System.arraycopy(b, off, buf, count, len);        //缓冲区有效字节数+len        count += len;    }    /**     * 将缓冲区中数据写入到输出流中     */    public synchronized void flush() throws IOException {        flushBuffer();        out.flush();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
总结
  • BufferedInputStream是缓冲输入流,作用是为另一个输入流添加一些功能,比如缓冲输入功能以及支持mark和reset方法的能力。
  • BufferedOutputStream是缓冲输出流,通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。
  • BufferedInputStream与BufferedOutputStream是如何提供装饰功能的请参考Java8 I/O源码-FilterInputStream、FilterOutputStream与装饰模式设计模式(9)-装饰模式

关于BufferedInputStream与BufferedOutputStream就讲到这里,想了解更多内容请参考

版权声明


1 个回复

倒序浏览

很不错,受教了
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马