|
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运行结果为 可读字节数:25除最后十个字节外,读出所有字节49,50,51,51,52,50,52,50,53,51,13,10,115,102,97,可读字节数:10make/reset supported!使用skip方法跳过两个字节可读字节数:8执行reset方法后,可读字节数:10读取的5个字节为sfsafBufferedOutputStreampublicclass 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与BufferedOutputStream就讲到这里,想了解更多内容请参考 版权声明
|