JavaIO流之字符流和字节流
字符流
- 简单知识介绍:一个字母和数字是1个字节,一个汉字是3个字节,中文标点占3个字节,英文标点是1个字节。(本介绍是按照utf-8字符集所示)其他字符集所占字节不同。
Reader
BufferedReader
- 简单介绍
- 读取文本的字符输入流,通过缓冲字符从而提供对字符、数组和行的高效读取。
- Reader构成的对象是字符对象,每次的读取请求都会涉及到字节读取解码字符的过程,而BufferedReader类中有设计减少这样的解码次数的方法,进而提高转换效率。
- 常用方法
public static void main(String[] args) throws IOException { BufferedReader bufferedReader = new BufferedReader(new FileReader("D:\\file\\test.txt")); char[] chars = new char[1024]; bufferedReader.read(chars); System.out.println(chars); bufferedReader.close(); }
- 源码解析
- 从构造方法中可看出BufferedReader有默认缓冲大小,默认为8192个字符
private static int defaultCharBufferSize = 8192; public BufferedReader(Reader in) { this(in, defaultCharBufferSize); }
InputStreamReader
- 简单介绍
- 该类读取字节,并用指定的字符集将其解码为字符,时从字节流到字符流的桥梁。
- 常用方法
- 两种构造方法展示
public static void main(String[] args) throws IOException { //InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("D:\\file\\test.txt")); InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("D:\\file\\test.txt"),"UTF-8"); char[] chars = new char[1024]; inputStreamReader.read(chars); System.out.println(chars); inputStreamReader.close(); }
- 控制台结果
a b c d
StringReader
- 简单介绍
- 将字符串转化成一个字符串流
- 常用方法
public static void main(String[] args) {
StringReader stringReader = new StringReader("哈哈哥");
System.out.println(stringReader.toString());
stringReader.close();
}
PipedReader
- 简单介绍
- 用于线程中通信的管道的输入流。
- 常用方法
- 创建发送者线程类
2.创建接收者线程类public class Sender extends Thread{ // 管道输出流对象。 // 它和“管道输入流(PipedReader)”对象绑定, // 从而可以将数据发送给“管道输入流”的数据,然后用户可以从“管道输入流”读取数据。 private PipedWriter out = new PipedWriter(); // 获得“管道输出流”对象 public PipedWriter getWriter() { return out; } @Override public void run() { writeShortMessage(); //writeLongMessage(); } // 向“管道输出流”中写入一则较简短的消息:"this is a short message" private void writeShortMessage() { String strInfo = "this is a short message"; try { out.write(strInfo.toCharArray()); out.close(); } catch (IOException e) { e.printStackTrace(); } } // 向“管道输出流”中写入一则较长的消息 private void writeLongMessage() { StringBuilder sb = new StringBuilder(); // 通过for循环写入1020个字符 for (int i = 0; i < 102; i++) sb.append("0123456789"); // 再写入26个字符。 sb.append("abcdefghijklmnopqrstuvwxyz"); // str的总长度是1020+26=1046个字符 String str = sb.toString(); try { // 将1046个字符写入到“管道输出流”中 out.write(str); out.close(); } catch (IOException e) { e.printStackTrace(); } } }
public class Receiver extends Thread{ // 管道输入流对象。 // 它和“管道输出流(PipedWriter)”对象绑定, // 从而可以接收“管道输出流”的数据,再让用户读取。 private PipedReader in = new PipedReader(); // 获得“管道输入流对象” public PipedReader getReader() { return in; } @Override public void run() { readMessageOnce(); //readMessageContinued() ; } // 从“管道输入流”中读取1次数据 public void readMessageOnce() { // 虽然buf的大小是2048个字符,但最多只会从“管道输入流”中读取1024个字符。 // 因为,“管道输入流”的缓冲区大小默认只有1024个字符。 char[] buf = new char[2048]; try { int len = in.read(buf); System.out.println(new String(buf, 0, len)); in.close(); } catch (IOException e) { e.printStackTrace(); } } // 从“管道输入流”读取>1024个字符时,就停止读取 public void readMessageContinued() { int total = 0; while (true) { char[] buf = new char[1024]; try { int len = in.read(buf); total += len; System.out.println(new String(buf, 0, len)); // 若读取的字符总数>1024,则退出循环。 if (total > 1024){ break; } } catch (IOException e) { e.printStackTrace(); } } try { in.close(); } catch (IOException e) { e.printStackTrace(); } } }
- 测试类
/** * 管道输入流和管道输出流的交互程序 */ public class PipeTest { public static void main(String[] args) { Sender t1 = new Sender(); Receiver t2 = new Receiver(); PipedWriter out = t1.getWriter(); PipedReader in = t2.getReader(); try { //管道连接。下面2句话的本质是一样。 //out.connect(in); in.connect(out); /** * Thread类的START方法: * 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 * 结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。 * 多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。 */ t1.start(); t2.start(); } catch (IOException e) { e.printStackTrace(); } } }
- 源码分析
- PipedWriter
package java.io; public class PipedWriter extends Writer { // 与PipedWriter通信的PipedReader对象 private PipedReader sink; // PipedWriter的关闭标记 private boolean closed = false; // 构造函数,指定配对的PipedReader public PipedWriter(PipedReader snk) throws IOException { connect(snk); } // 构造函数 public PipedWriter() { } // 将“PipedWriter” 和 “PipedReader”连接。 public synchronized void connect(PipedReader snk) throws IOException { if (snk == null) { throw new NullPointerException(); } else if (sink != null || snk.connected) { throw new IOException("Already connected"); } else if (snk.closedByReader || closed) { throw new IOException("Pipe closed"); } sink = snk; snk.in = -1; snk.out = 0; // 设置“PipedReader”和“PipedWriter”为已连接状态 // connected是PipedReader中定义的,用于表示“PipedReader和PipedWriter”是否已经连接 snk.connected = true; } // 将一个字符c写入“PipedWriter”中。 // 将c写入“PipedWriter”之后,它会将c传输给“PipedReader” public void write(int c) throws IOException { if (sink == null) { throw new IOException("Pipe not connected"); } sink.receive(c); } // 将字符数组b写入“PipedWriter”中。 // 将数组b写入“PipedWriter”之后,它会将其传输给“PipedReader” public void write(char cbuf[], int off, int len) throws IOException { if (sink == null) { throw new IOException("Pipe not connected"); } else if ((off | len | (off + len) | (cbuf.length - (off + len))) < 0) { throw new IndexOutOfBoundsException(); } sink.receive(cbuf, off, len); } // 清空“PipedWriter”。 // 这里会调用“PipedReader”的notifyAll(); // 目的是让“PipedReader”放弃对当前资源的占有,让其它的等待线程(等待读取PipedWriter的线程)读取“PipedWriter”的值。 public synchronized void flush() throws IOException { if (sink != null) { if (sink.closedByReader || closed) { throw new IOException("Pipe closed"); } synchronized (sink) { sink.notifyAll(); } } } // 关闭“PipedWriter”。 // 关闭之后,会调用receivedLast()通知“PipedReader”它已经关闭。 public void close() throws IOException { closed = true; if (sink != null) { sink.receivedLast(); } } }
- PipedReader
package java.io; public class PipedReader extends Reader { // “PipedWriter”是否关闭的标记 boolean closedByWriter = false; // “PipedReader”是否关闭的标记 boolean closedByReader = false; // “PipedReader”与“PipedWriter”是否连接的标记 // 它在PipedWriter的connect()连接函数中被设置为true boolean connected = false; Thread readSide; // 读取“管道”数据的线程 Thread writeSide; // 向“管道”写入数据的线程 // “管道”的默认大小 private static final int DEFAULT_PIPE_SIZE = 1024; // 缓冲区 char buffer[]; //下一个写入字符的位置。in==out代表满,说明“写入的数据”全部被读取了。 int in = -1; //下一个读取字符的位置。in==out代表满,说明“写入的数据”全部被读取了。 int out = 0; // 构造函数:指定与“PipedReader”关联的“PipedWriter” public PipedReader(PipedWriter src) throws IOException { this(src, DEFAULT_PIPE_SIZE); } // 构造函数:指定与“PipedReader”关联的“PipedWriter”,以及“缓冲区大小” public PipedReader(PipedWriter src, int pipeSize) throws IOException { initPipe(pipeSize); connect(src); } // 构造函数:默认缓冲区大小是1024字符 public PipedReader() { initPipe(DEFAULT_PIPE_SIZE); } // 构造函数:指定缓冲区大小是pipeSize public PipedReader(int pipeSize) { initPipe(pipeSize); } // 初始化“管道”:新建缓冲区大小 private void initPipe(int pipeSize) { if (pipeSize <= 0) { throw new IllegalArgumentException("Pipe size <= 0"); } buffer = new char[pipeSize]; } // 将“PipedReader”和“PipedWriter”绑定。 // 实际上,这里调用的是PipedWriter的connect()函数 public void connect(PipedWriter src) throws IOException { src.connect(this); } // 接收int类型的数据b。 // 它只会在PipedWriter的write(int b)中会被调用 synchronized void receive(int c) throws IOException { // 检查管道状态 if (!connected) { throw new IOException("Pipe not connected"); } else if (closedByWriter || closedByReader) { throw new IOException("Pipe closed"); } else if (readSide != null && !readSide.isAlive()) { throw new IOException("Read end dead"); } // 获取“写入管道”的线程 writeSide = Thread.currentThread(); // 如果“管道中被读取的数据,等于写入管道的数据”时, // 则每隔1000ms检查“管道状态”,并唤醒管道操作:若有“读取管道数据线程被阻塞”,则唤醒该线程。 while (in == out) { if ((readSide != null) && !readSide.isAlive()) { throw new IOException("Pipe broken"); } /* full: kick any waiting readers */ notifyAll(); try { wait(1000); } catch (InterruptedException ex) { throw new java.io.InterruptedIOException(); } } if (in < 0) { in = 0; out = 0; } buffer[in++] = (char) c; if (in >= buffer.length) { in = 0; } } // 接收字符数组b。 synchronized void receive(char c[], int off, int len) throws IOException { while (--len >= 0) { receive(c[off++]); } } // 当PipedWriter被关闭时,被调用 synchronized void receivedLast() { closedByWriter = true; notifyAll(); } // 从管道(的缓冲)中读取一个字符,并将其转换成int类型 public synchronized int read() throws IOException { if (!connected) { throw new IOException("Pipe not connected"); } else if (closedByReader) { throw new IOException("Pipe closed"); } else if (writeSide != null && !writeSide.isAlive() && !closedByWriter && (in < 0)) { throw new IOException("Write end dead"); } readSide = Thread.currentThread(); int trials = 2; while (in < 0) { if (closedByWriter) { /* closed by writer, return EOF */ return -1; } if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) { throw new IOException("Pipe broken"); } /* might be a writer waiting */ notifyAll(); try { wait(1000); } catch (InterruptedException ex) { throw new java.io.InterruptedIOException(); } } int ret = buffer[out++]; if (out >= buffer.length) { out = 0; } if (in == out) { /* now empty */ in = -1; } return ret; } // 从管道(的缓冲)中读取数据,并将其存入到数组b中 public synchronized int read(char cbuf[], int off, int len) throws IOException { if (!connected) { throw new IOException("Pipe not connected"); } else if (closedByReader) { throw new IOException("Pipe closed"); } else if (writeSide != null && !writeSide.isAlive() && !closedByWriter && (in < 0)) { throw new IOException("Write end dead"); } if ((off < 0) || (off > cbuf.length) || (len < 0) || ((off + len) > cbuf.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } /* possibly wait on the first character */ int c = read(); if (c < 0) { return -1; } cbuf[off] = (char)c; int rlen = 1; while ((in >= 0) && (--len > 0)) { cbuf[off + rlen] = buffer[out++]; rlen++; if (out >= buffer.length) { out = 0; } if (in == out) { /* now empty */ in = -1; } } return rlen; } // 是否能从管道中读取下一个数据 public synchronized boolean ready() throws IOException { if (!connected) { throw new IOException("Pipe not connected"); } else if (closedByReader) { throw new IOException("Pipe closed"); } else if (writeSide != null && !writeSide.isAlive() && !closedByWriter && (in < 0)) { throw new IOException("Write end dead"); } if (in < 0) { return false; } else { return true; } } // 关闭PipedReader public void close() throws IOException { in = -1; closedByReader = true; }
}
```
FilterReader实现类之PushBackInputStream
- 简单介绍
- PushBackInputStream有一个PushBack缓冲区,只不过PushbackReader所处理的是字符。从这个对象读出数据后,如果愿意的话,只要PushBack缓冲区没有满,就可以使用unread()将数据推回流的前端。
- 使用方法
public static void main(String[] args) throws IOException { PushbackReader pr = null; try { //创建一个PushbackReader对象,指定推回缓冲区的长度为64 pr = new PushbackReader(new FileReader("D:\\file\\test.txt"), 64); char[] buf = new char[65]; //用以保存上次读取的字符串内容 String lastContent = ""; int hasRead = 0; //循环读取文件内容 while ((hasRead = pr.read(buf)) > 0) { //将读取的内容转换成字符串 String content = new String(buf, 0, hasRead); int targetIndex = 0; //将上次读取的字符串和本次读取的字符串拼起来,查看是否包含目标字符串 //如果包含目标字符串 if ((targetIndex = (lastContent + content).indexOf("new PushbackReader")) > 0) { //将本次内容和上次内容一起推回缓冲区 pr.unread((lastContent + content).toCharArray()); //再次读取指定长度的内容(就是目标字符串之前的内容) pr.read(buf, 0, targetIndex); //打印读取的内容 System.out.print(new String(buf, 0, targetIndex)); int i = 0; System.out.println(++i); System.exit(0); } else { //打印上次读取的内容 System.out.print(lastContent); //将本次内容设为上次读取的内容 lastContent = content; } } } catch (IOException ioe) { ioe.printStackTrace(); } finally { try { if (pr != null) pr.close(); } catch (IOException ioe) { ioe.printStackTrace(); } } }
- 控制台内容
12312312312412341412312312312412341412312312312412341412312312312
- 文本内容
1231231231241234141231231231241234141231231231241234141231231231241234141231231231241234141231231231241234145
- 代码解读:以65个字符为一组,进行文本内容读取拼接,直到最后不为一组时进行打印.
Writer
BufferedWriter
- 简单介绍
- 字符缓冲输出流
- 常用方法
public static void main(String[] args) throws IOException { BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:\\file\\test.txt")); bufferedWriter.write("hello-felix"); // 相当于空格 bufferedWriter.write(2); bufferedWriter.write("hello-fei"); // 会在关闭之前进行 flush()刷新流,因为BufferedWriter是缓冲流会先把字符存放到缓冲中,进行刷新才会更新到目标文件中 bufferedWriter.close(); }
- 文本
hello-felix�hello-fei
OutputStreamWriter
- 简单介绍
- 转换流,是字符流到字节流的桥梁,它使用指定的字符集将字节编码为字节.
- 常用方法
public static void main(String[] args) throws IOException { OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("D:\\file\\test.txt"), "utf-8"); // 把字符转化为字节存储在缓冲区中(编码) outputStreamWriter.write("felix"); outputStreamWriter.write(2); outputStreamWriter.write("fei"); // 把内存缓冲区中的字节书刷新到文件中(使用字节流写字节的过程) outputStreamWriter.flush(); // 释放资源 outputStreamWriter.close(); }
- 文本内容
felix�fei
PrintWriter
- 简单介绍
- 向文件中打印带格式的内容,针对于字符流.
- 常用方法
public static void main(String[] args) throws IOException { PrintWriter printerWriter = new PrintWriter(new FileOutputStream("D:\\file\\test.txt")); printerWriter.println("felix"+"\n"); printerWriter.println("fei"+"\n"); printerWriter.close(); }
- 文本内容
felix fei
StringWriter
- 简单介绍
- 将字符串写入缓冲流.
- 常用方法
public static void main(String[] args) throws IOException { StringWriter sw = new StringWriter(); //write(int c)写入单个字符 sw.write('a'); //write(char[w] cbuf, int off, int len)写入字符数组的某一部分,偏移量和写入长度 char[] c = {'h', 'e', 'l', 'l', 'o'}; sw.write(c, 1, 3); //write(String str)写入一个字符串 sw.write("sunny day"); //write(String str, int off, int len)写入字符串的某一部分,偏移量和写入长度 String s = "hello world"; sw.write(s, 0, 3); //append(char c)将指定字符添加到此 writer sw.append('g'); //append(CharSequence csq)将指定的字符序列添加到此 writer。 sw.append("apple"); //append(CharSequence csq, int start, int end)将指定字符序列的子序列添加到此 writer。 sw.append("tulun", 0, 3); /*返回当前值:返回类型不同,一个是StringBuffer类型,一个是String类型*/ //getBuffer()返回该字符串缓冲区本身。 System.out.println(sw.getBuffer()); //toString()以字符串的形式返回该缓冲区的当前值。 System.out.printf(sw.toString()); //flush() 刷新该流的缓冲。 sw.flush(); //close() 关闭 StringWriter 无效。 sw.close(); }
- 控制台结果
aellsunny dayhelgappletul aellsunny dayhelgappletul
PipedWriter
- 简单介绍
- 用于线程之间通过管道内容写入,和PipedReader配合使用.
- 常用方法
- 可查看上面的PipedReader方法
FilterWriter
- 简单介绍
- 字符流过滤,由于该类是个抽象类,暂时没有实际用处.
CharArrayWriter
- 简单介绍
- 该类实现了一个可用作字符输出流的字符缓冲区,当数据写入流时,缓冲区自动增长
- 常用方法
public static void main(String[] args) throws IOException { CharArrayWriter charArrayWriter = new CharArrayWriter(); charArrayWriter.append("felix"); charArrayWriter.write(2); charArrayWriter.write("fei"); System.out.println(charArrayWriter.toCharArray()); System.out.println(charArrayWriter.toString()); }
- 控制台结果
felix�fei felix�fei
字节流
InputStream(接口)
FileInputStream
- 简单介绍
- 文件字节输入流,意思指对文件数据以字节的形式进行读取操作如读取图片视频等.
- 常用方法
public static void main(String[] args) throws IOException { FileInputStream fileInputStream = new FileInputStream("D:\\file\\test.txt"); byte[] bytes = new byte[1024]; fileInputStream.read(bytes); System.out.println(new String(bytes)); fileInputStream.close(); }
- 控制台结果
123456
- 文本内容
123456
- 注意:使用read(byte b[])方法时的数组长度至关重要,若长度小于流的字节长度,那么最后得出的内容会出现丢失。若大于流的字节长度,那么最后数组的内存就浪费了,那么就需要根据文件的字节长度来设置数组的长度
FilterInputStream
- 简单介绍
- 用来“封装其它的输入流,并为它们提供额外的功能”。它的常用的子类有BufferedInputStream和DataInputStream。
ObjectInputStream
- 简单介绍
- 反序列化流,将使用ObjectOutputStream序列化的原始数据恢复为对象,以流的方式读取对象.
- 常用方法
public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\file\\test.txt")); // 需要User对象实现Serializable(序列化)接口 User user1 = new User(); user1.setName("felix"); objectOutputStream.writeObject(user1); objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\file\\test.txt")); User user = (User)objectInputStream.readObject(); System.out.println(user.getName()); objectInputStream.close(); }
- 控制台结果
felix
- 文本内容
�sr �com.example.demo.test.User)?��)�� �L �namet �Ljava/lang/String;xpt �felix
PipedInputStream
- 简单介绍
- 管道输入流PipedInputStream与管道输出流PipedOutputStream实现了类似管道的功能,用于不同线程之间的相互通信.
SequenceInputStream
- 简单介绍
- SequenceInputStream 表示其他输入流的逻辑串联(合并流)
- 常用方法
public static void main(String[] args) throws IOException {
//封装源文件
InputStream in1 = new FileInputStream("StringDemo.java") ;
InputStream in2 = new FileInputStream("SystemInDemo.java") ;
//创建合并流对象
SequenceInputStream sis = new SequenceInputStream(in1, in2) ;
//创建一个字节缓冲输入流对象
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("Copy.java"));
//原来怎么读写,现在依然这样读写
byte[] bys = new byte[1024] ;
int len = 0 ;
while((len=sis.read(bys))!=-1) {
bos.write(bys, 0, len);
bos.flush(); //读图片文件的时候
}
//关闭资源
sis.close();
bos.close();
}
```
```
public static void main(String[] args) throws IOException {
//StringDemo.java+SystemInDemo.java+PrintWriterDemo.java--->Copy.java文件中
//定义一个集合Vector
Vector<InputStream> v = new Vector<InputStream>() ;
//使用InputStream封装文件
InputStream s1 = new FileInputStream("StringDemo.java") ;
InputStream s2 = new FileInputStream("SystemInDemo.java") ;
InputStream s3 = new FileInputStream("PrintWriterDemo.java") ;
//将流对象添加到集合中
v.add(s1) ;
v.add(s2) ;
v.add(s3) ;
//特有功能
Enumeration<InputStream> en = v.elements() ;
//创建合并输入流对象
SequenceInputStream sis = new SequenceInputStream(en) ;
//创建字节缓冲输出流对象
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("Copy.java")) ;
//一次读取一个字节数组
byte[] bys = new byte[1024] ;
int len = 0 ;
while((len=sis.read(bys))!=-1) {
bos.write(bys, 0, len);
bos.flush();
}
//关闭资源
bos.close();
sis.close();
}
```
StringBufferInputStream
- 简单介绍
- 创建字符串输入流以从指定字符串读取数据.
ByteArrayInputStream
- 简单介绍
- 包含一个内部缓冲区,其中包含可以从流中读取的字节。 内部计数器跟踪由read方法提供的下一个字节。关闭一个ByteArrayInputStream没有任何效果。 该流中的方法可以在流关闭后调用,而不生成IOException 。意思就是说,比如文件流的处理对象的外存中的文件,而它的处理对象是内存中的缓冲区。它是从一个内存读到另一个内存方式。
OutputStream
- 简单介绍
- 是一个抽象类,表示字节输出流的所有类的超类,将指定的字节信息写出到目的地.
FileOutputStream
- 简单介绍
- FileOutPutStream是文件字节输出流,用于将字节流数据输入到指定的文件中.
- 常用方法
public static void main(String[] args) throws IOException{ // FileOutputStream fileOutputStream = new FileOutputStream("D:\\file\\test.txt",true); // FileOutputStream fileOutputStream = new FileOutputStream("D:\\file\\test3.txt",false); FileOutputStream fileOutputStream = new FileOutputStream("D:\\file\\inner\\test3.txt",false); String content = "123"; fileOutputStream.write(content.getBytes()); fileOutputStream.close(); }
- 从以上方法可得知在使用FileOutputStream时如果文件不存在则会创建文件,如果文件夹不存在时则会抛出java.io.FileNotFoundException异常。
- 该构造方法第二个参数true表示数据写入方式为从尾部进行追加,参数为false表示数据写入方式为从头进行覆盖。
ObjectOutputStream
- 简单介绍
- 对象序列化流,把对象转成字节数据输出到文件中保存,对象的输出过程称为序列化,可实现对象的持久存储.
- 需要注意的点
- 需要序列化的对象必须实现Serializable接口
- 如果需要个别属性不被序列化,则该属性必须注明是瞬态的,使用transient关键字修饰
- 被static关键字修饰的不能被序列化
- 序列化一个对象时,要求它的属性要么是基本数据类型,如果是引用数据类型,这个引用数据类型必须实现Serializable接口。关于数据类型请查看 Java基本类型和引用类型
PipedOutputStream
- 简单介绍
- 被称为管道输出流,和PipedInputStream配合使用,用于线程之间的通讯.
ByteArrayOutputStream
- 简单介绍
- 此类实现一个字节输出流、其中数据被写入到字节数组中, 缓冲区在数据写入时会自动增长,关闭该流无效,关闭此流后调用方法不会有异常