上一节,我们分析了常见的节点流(FileInputStream/FileOutputStream FileReader/FileWrite)和常见的处理流(BufferedInputStream/BufferedOutputStream BufferedReader/BufferedWrite),经常而言我们都是针对文件的操作,然后带上缓冲的节点流进行处理,但有时候为了提升效率,我们发现频繁的读写文件并不是太好,那么于是出现了字节数组流,即存放在内存中,因此有称之为内存流;其中字节数组流也一种节点流;除了节点流外,我们也将学习另外一种处理流,即数据流。数据处理流是用于针对数据类型传输处理的,是一种处理流,即是在节点流之上的增强处理,一般用于序列化和反序列化的时候用到。下面我们言归正传,进入学习:
一、字节数组流【节点流】
字节数组流对象分为输入流和输出流。分别是:ByteArrayInputStream和ByteArrayOutputStream。
1.ByteArrayInputStream类
字节数组输入流在内存创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中。创建字节数组输入流对象有以下方式:
//方法 1
ByteArrayInputStream bArray = new ByteArrayInputStream( byte [] a);
//方法 2
ByteArrayInputStream bArray = new ByteArrayInputStream( byte []a, int off, int len)
字节数组流对象的方法:
序号方法及描述 public int read()ByteArrayInputStream bis=new ByteArrayInputStream(destByte); InputStream bis=new BufferedInputStream(new ByteArrayInputStream(destByte))
说明,因为ByteArrayInputStream 是一种节点流,BufferedInputStream 一种处理流,因此可以装饰增强处理,且字节数组输入流没有新增方法,因此可以使用多态性。
* 输入流 操作与 文件输入流操作一致 * 读取字节数组 * @throws IOException public static void read( byte [] src) throws IOException{ // 数据源传入 // 选择流 InputStream is = new BufferedInputStream( new ByteArrayInputStream( // 操作 byte [] flush = new byte [1024 ]; int len =0 ; while (-1!=(len= is.read(flush))){ System.out.println( new String(flush,0 ,len)); // 释放资源 is.close();2.ByteArrayOutputStream类
字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中。
创建方式:
//方法 1
OutputStream bOut = new ByteArrayOutputStream();
//方法 2
OutputStream bOut = new ByteArrayOutputStream( int a);
字节数组输出流对象的方法:
序号方法及描述 public void reset()输出流:ByteArrayOutputStream bos=new ByteArrayOutputStream();
由于输出流有新增方法,所以这里不可以使用多态,所以没法直接采用OutputStream来进行。
* 输出流 操作与文件输出流 有些不同, 有新增方法,不能使用多态 * @throws IOException public static byte [] write() throws IOException{ // 目的地 byte [] dest; // 选择流 不同点 ByteArrayOutputStream bos = new ByteArrayOutputStream(); // 操作 写出 String msg ="操作与 文件输入流操作一致" ; byte [] info = msg.getBytes(); bos.write(info, 0 , info.length); // 获取数据 dest = bos.toByteArray(); // 释放资源 bos.close(); return dest;之前使用节点流中的字节流进行文件的拷贝,利用字符流进行纯文本文件的拷贝,也可以使用处理流中的字节缓冲流与字符缓冲流进行文件/文本文件的拷贝, 为了将字节数组流与之前的节点流联系在一起,这里利用字节数组流做中转站,实现文件的拷贝。
步骤一:利用文件输入流读取到被拷贝文件的数据,利用字节数组输出流保存在字节数组中
步骤二:利用字节数组输入流以及文件输出流,将数据写出到目的文件中。
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; *1、文件 --程序->字节数组 *1)、文件输入流 * 字节数组输出流 * 2、字节数组 --程序->文件 * 1)、字节数组输入流 * 文件输出流 * @author Administrator public class ByteArrayDemo02 { * @param args * @throws IOException public static void main(String[] args) throws IOException { byte[] data =getBytesFromFile("e:/xp/test/1.jpg"); toFileFromByteArray(data,"e:/xp/test/arr.jpg"); * 2、字节数组 --程序->文件 public static void toFileFromByteArray(byte[] src,String destPath) throws IOException{ //创建源 //目的地 File dest=new File(destPath); //选择流 //字节数组输入流 InputStream is =new BufferedInputStream(new ByteArrayInputStream(src)); //文件输出流 OutputStream os =new BufferedOutputStream(new FileOutputStream(dest)); //操作 不断读取字节数组 byte[] flush =new byte[1]; int len =0; while(-1!=(len =is.read(flush))){ //写出到文件中 os.write(flush, 0, len); os.flush(); //释放资源 os.close(); is.close(); * 1、文件 --程序->字节数组 * @return * @throws IOException public static byte[] getBytesFromFile(String srcPath) throws IOException{ //创建文件源 File src =new File(srcPath); //创建字节数组目的地 byte[] dest =null; //选择流 //文件输入流 InputStream is =new BufferedInputStream(new FileInputStream(src)); //字节数组输出流 不能使用多态 ByteArrayOutputStream bos =new ByteArrayOutputStream(); //操作 不断读取文件 写出到字节数组流中 byte[] flush =new byte[1024]; int len =0; while(-1!=(len =is.read(flush))){ //写出到字节数组流中 bos.write(flush, 0, len); bos.flush(); //获取数据 dest =bos.toByteArray(); bos.close(); is.close(); return dest;二、数据流【处理流】
1.DataInputStream
DataInputStream 是数据输入流。它继承于FilterInputStream。
DataInputStream 是用来装饰其它输入流,它“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”。应用程序可以使用DataOutputStream(数据输出流)写入由DataInputStream(数据输入流)读取的数据。[一句话,是给机器看的]2.DataOutputStream
DataOutputStream 是数据输出流。它继承于FilterOutputStream。
DataOutputStream 是用来装饰其它输出流,将DataOutputStream和DataInputStream输入流配合使用,“允许应用程序以与机器无关方式从底层输入流中读写基本 Java 数据类型”。样例操作如下: 有如下的订单数据
如果要想使用数据操作流,则肯定要由用户自己制定数据的保存格式,必须按指定好的格式保存数据,才可以使用数据输入流将数据读取进来。DataOutputStream:DataOutputStream是OutputStream的子类,此类的定义如下:public class DataOutputStream extends FilterOutputStream implements DataOutput此类继承自FilterOutputStream类(FilterOutputStream是OutputStream的子类)同时实现了DataOutput接口,在DataOutput接口中定义了一系列的写入各种数据的方法。writeXxx()要想使用DataOutputStream写入数据的话,则必须指定好数据的输出格式。数据的写入格式:以上每条数据之间使用"\n"分隔,每条数据中的每个内容之间使用"\t"分隔。如下图所示import java.io.DataOutputStream ; import java.io.File ; import java.io.FileOutputStream ; public class DataOutputStreamDemo{ public static void main(String args[]) throws Exception{ // 所有异常抛出 DataOutputStream dos = null ; // 声明数据输出流对象 File f = new File("d:" + File.separator + "order.txt") ; // 文件的保存路径 dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f))) ; // 实例化数据输出流对象 String names[] = {"衬衣","手套","围巾"} ; // 商品名称 float prices[] = {98.3f,30.3f,50.5f} ; // 商品价格 int nums[] = {3,2,1} ; // 商品数量 for(int i=0;i<names.length;i++){ // 循环输出 dos.writeChars(names[i]) ; // 写入字符串 dos.writeChar('\t') ; // 写入分隔符 dos.writeFloat(prices[i]) ; // 写入价格 dos.writeChar('\t') ; // 写入分隔符 dos.writeInt(nums[i]) ; // 写入数量 dos.writeChar('\n') ; // 换行 dos.close() ; // 关闭输出流使用DataOutputStream写入的数据要使用DataInputStream读取进来。前面说过,是给机器看的,人类看不懂.
import java.io.DataInputStream ; import java.io.File ; import java.io.FileInputStream ; public class DataInputStreamDemo{ public static void main(String args[]) throws Exception{ // 所有异常抛出 DataInputStream dis = null ; // 声明数据输入流对象 File f = new File("d:" + File.separator + "order.txt") ; // 文件的保存路径 dis = new DataInputStream(new BufferedInputStream(new FileInputStream(f)) ); // 实例化数据输入流对象 String name = null ; // 接收名称 float price = 0.0f ; // 接收价格 int num = 0 ; // 接收数量 char temp[] = null ; // 接收商品名称 int len = 0 ; // 保存读取数据的个数 char c = 0 ; // '\u0000' try{ while(true){ temp = new char[200] ; // 开辟空间 len = 0 ; while((c=dis.readChar())!='\t'){ // 接收内容 temp[len] = c ; len ++ ; // 读取长度加1 name = new String(temp,0,len) ; // 将字符数组变为String price = dis.readFloat() ; // 读取价格 dis.readChar() ; // 读取\t num = dis.readInt() ; // 读取int dis.readChar() ; // 读取\n System.out.printf("名称:%s;价格:%5.2f;数量:%d\n",name,price,num) ; }catch(Exception e){} dis.close() ;5.2f 表示的是 总共的数字长度为5位,其中2位表示小数,3位表示整数。
下面我们再看一个例子,即回顾到我们开始说的,一般DataInputStream 和 DataOutputStream 这种处理流,和对应的节点流ByteArrayInputStream ByteArrayOutputStream 关联在一起使用,即我们说的将字节数组中内存中存放当做一个小文件对待,例子如下:
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; * 数据类型(基本+String)处理流 * 1、输入流 DataInputStream readXxx() * 2、输出流 DataOutputStream writeXxx() * 新增方法不能使用多态 * java.io.EOFException :没有读取到相关的内容 * @author Administrator public class DataDemo02 { * @param args public static void main(String[] args) { try { byte[] data=write(); read(data); System.out.println(data.length); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); * 从字节数组读取数据+类型 * @throws IOException public static void read(byte[] src) throws IOException{ //选择流 DataInputStream dis =new DataInputStream( new BufferedInputStream( new ByteArrayInputStream(src) //操作 读取的顺序与写出一致 必须存在才能读取 double num1 =dis.readDouble(); long num2 =dis.readLong(); String str =dis.readUTF(); dis.close(); System.out.println(num1+"-->"+num2+"-->"+str); * 数据+类型输出到字节数组中 * @throws IOException public static byte[] write() throws IOException{ //目标数组 byte[] dest =null; double point =2.5; long num=100L; String str ="数据类型"; //选择流 ByteArrayOutputStream DataOutputStream ByteArrayOutputStream bos =new ByteArrayOutputStream(); DataOutputStream dos =new DataOutputStream( new BufferedOutputStream( //操作 写出的顺序 为读取准备 dos.writeDouble(point); dos.writeLong(num); dos.writeUTF(str); dos.flush(); dest =bos.toByteArray(); //释放资源 dos.close(); return dest;三、对象流【处理流】
因为前面的是针对于基本的数据类型的操作,那么针对对象,于是就有了对象流;ObjectInputStream 和 ObjectOutputStream 的作用是,对基本数据和对象进行序列化操作支持。
创建“文件输出流”对应的ObjectOutputStream对象,该ObjectOutputStream对象能提供对“基本数据或对象”的持久存储;当我们需要读取这些存储的“基本数据或对象”时,可以创建“文件输入流”对应的ObjectInputStream,进而读取出这些“基本数据或对象”。
注意: 只有支持 java.io.Serializable 或 java.io.Externalizable 接口的对象才能被ObjectInputStream/ObjectOutputStream所操作!主要的作用是用于写入对象信息与读取对象信息。 对象信息一旦写到文件上那么对象的信息就可以做到持久化了
使用:
对象的输出流将指定的对象写入到文件的过程,就是将对象序列化的过程,对象的输入流将指定序列化好的文件读出来的过程,就是对象反序列化的过程。既然对象的输出流将对象写入到文件中称之为对象的序列化,那么可想而知对象所对应的class必须要实现Serializable接口。(查看源码可得知:Serializable接口没有任何的方法,只是作为一个标识接口存在)。1、将User类的对象序列化
class User implements Serializable{//必须实现Serializable接口 String uid; String pwd; public User(String _uid,String _pwd){ this.uid = _uid; this.pwd = _pwd; @Override public String toString() { return "账号:"+this.uid+" 密码:"+this.pwd; public class Demo1 { public static void main(String[] args) throws IOException { //假设将对象信息写入到obj.txt文件中,事先已经在硬盘中建立了一个obj.txt文件 File f = new File("F:\\obj.txt"); writeObjec(f); System.out.println("OK"); //定义方法把对象的信息写到硬盘上------>对象的序列化。 public static void writeObjec(File f) throws IOException{ FileOutputStream outputStream = new FileOutputStream(f);//创建文件字节输出流对象 ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject( new User("酒香逢","123")); //最后记得关闭资源,objectOutputStream.close()内部已经将outputStream对象资源释放了,所以只需要关闭objectOutputStream即可 objectOutputStream.close();运行程序得到记事本中存入的信息:可见已经序列化到记事本中
2、将序列化到记事本的内容反序列化
public class Demo1 { public static void main(String[] args) throws IOException, ClassNotFoundException { //假设将对象信息写入到obj.txt文件中,事先已经在硬盘中建立了一个obj.txt文件 File f = new File("F:\\obj.txt"); //writeObjec(f); readObject(f); System.out.println("OK"); //定义方法把对象的信息写到硬盘上------>对象的序列化。 public static void writeObjec(File f) throws IOException{ FileOutputStream outputStream = new FileOutputStream(f);//创建文件字节输出流对象 ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(new User("酒香逢","123")); //最后记得关闭资源,objectOutputStream.close()内部已经将outputStream对象资源释放了,所以只需要关闭objectOutputStream即可 objectOutputStream.close(); //把文件中的对象信息读取出来-------->对象的反序列化 public static void readObject(File f) throws IOException, ClassNotFoundException{ FileInputStream inputStream = new FileInputStream(f);//创建文件字节输出流对象 ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); User user = (User)objectInputStream.readObject(); System.out.println(user);运行代码得到的结果:
账号:酒香逢 密码:123
OK但是,如果这时候这个obj.txt是我们项目中一个文件,而项目到后期在原来User类的基础上添加成员变量String userName;
class User implements Serializable{//必须实现Serializable接口 String uid; String pwd; String userName="名字";//新添加的成员变量 public User(String _uid,String _pwd){ this.uid = _uid; this.pwd = _pwd; @Override public String toString() { return "账号:"+this.uid+" 密码:"+this.pwd;这时候如果我们再反序列化,则会引发下面的异常:
Exception in thread "main" java.io.InvalidClassException: com.User; local class incompatible: stream classdesc serialVersionUID = 2161776237447595412, local class serialVersionUID = -3634244984882257127 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:604) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1601) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1514) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369) 。。。。。异常信息解读:
serialVersionUID 是用于记录class文件的版本信息的,serialVersionUID这个数字是JVM(JAVA虚拟界)通过一个类的类名、成员、包名、工程名算出的一个数字。而这时候序列化文件中记录的serialVersionUID与项目中的不一致,即找不到对应的类来反序列化。
3、如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始就给这个类指定一个serialVersionUID,如果一类已经指定的serialVersionUID,然后
在序列化与反序列化的时候,jvm都不会再自己算这个 class的serialVersionUID了。去掉刚才添加的成员变量userName;,并且在User类中指定一个serialVersionUID
class User implements Serializable{//必须实现Serializable接口 private static final long serialVersionUID = 1L; String uid; String pwd; //String userName="名字";//新添加的成员变量 public User(String _uid,String _pwd){ this.uid = _uid; this.pwd = _pwd; @Override public String toString() { return "账号:"+this.uid+" 密码:"+this.pwd;重新序列化到obj.txt文件中,然后再类中再将userName添加回来(将上面User类中userName字段解注释),再一次执行反序列化操作,执行的结果跟之前反序列化的结果是一致的。可见这样解决后我们后期修改类也是可行的。
4、如果在User类中再添加成员变量,而这个变量为一个class ,如Address,那么Address类也必须要实现Serializable接口。
class Address implements Serializable{ String country; String city; class User implements Serializable{//必须实现Serializable接口 private static final long serialVersionUID = 1L; String uid; String pwd; String userName="名字";//新添加的成员变量 Address address;//成员变量为Address public User(String _uid,String _pwd){ this.uid = _uid; this.pwd = _pwd; @Override public String toString() { return "账号:"+this.uid+" 密码:"+this.pwd;5、最后再提一下关键字transient关键字,当你不想要某些字段序列化时候,可以用transient关键字修饰
class User implements Serializable{//必须实现Serializable接口 private static final long serialVersionUID = 1L; String uid; String pwd; transient String userName="名字";//新添加的成员变量//添加关键字transient后,序列化时忽略 Address address;//成员变量为Address public User(String _uid,String _pwd){ this.uid = _uid; this.pwd = _pwd; @Override public String toString() { return "账号:"+this.uid+" 密码:"+this.pwd;上面我们演示的文件的操作,如果换成字节数组流也是一样的方式。最后总结下:
1. 如果对象需要被写出到文件上,那么对象所属的类必须要实现Serializable接口。 Serializable接口没有任何的方法,是一个标识接口而已。
2. 对象的反序列化创建对象的时候并不会调用到构造方法的、(这点文中没有说到,想要验证的同学在构造方法后面加一句System.out.println("构造方法执行吗?");,实际上构造方法是不执行的,自然这句话也没有输出了)
3. serialVersionUID 是用于记录class文件的版本信息的,serialVersionUID这个数字是通过一个类的类名、成员、包名、工程名算出的一个数字。
4. 使用ObjectInputStream反序列化的时候,ObjeectInputStream会先读取文件中的serialVersionUID,然后与本地的class文件的serialVersionUID
进行对比,如果这两个id不一致,反序列则失败。
5. 如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始就给这个类指定一个serialVersionUID,如果一类已经指定的serialVersionUID,然后
在序列化与反序列化的时候,jvm都不会再自己算这个 class的serialVersionUID了。
6. 如果一个对象某个数据不想被序列化到硬盘上,可以使用关键字transient修饰。
7. 如果一个类维护了另外一个类的引用,则另外一个类也需要实现Serializable接口。四、打印流【处理流】
PrintStream 用于向文本输出流打印对象的格式化表示形式。它实现在PrintStream 中的所有 print 方法。它不包含用于写入原始字节的方法,对于这些字节,程序应该使用未编码的字节流进行写入。
在理解PrintStream如何使用之前,先了解一下System类中三个字段:
往控制台输出时 ,使用System.out.println();
其中System.out这个字段返回的就是打印流,PrintStream
PrintStream ps=System.out; ps.print("hello"); 就等同于 System.out.println("hello");
err和out其实是一样的,只不过在控制台输出时,err输出内容是红色的
Scanner也是一个处理流,创建一个Scanner对象使用到的就是in字段
Scanner console=new Scanner(System.in);
Scanner类其实就是一个输入流,那么我们可以从控制台输入,怎样从文件中输入呢?
InputStream is=System.in; File file=new File("F:/Picture/test/test2.txt"); is=new BufferedInputStream(new FileInputStream(file)); Scanner sc=new Scanner(is); System.out.println(sc.nextLine());从上面也可以看出Scanner 其实就一个处理流,用于增强节点流。
使用打印流输出内容到文件中,也是很容易的
这是PrintStream的构造方法
File file=new File("F:/Picture/test/test.txt"); PrintStream ps=new PrintStream(new BufferedOutputStream(new FileOutputStream(file))); ps.append("hellohahaha"); ps.close();最后总结一句话:对于标准的输入和输出,JDK 中封装好了比较好的操作类,输入的Scanner 输出PrintStream
最后一个小问题:如何将system.out 的输出不是输出到控制台,而是记录到文件中呢?即记录日志利用打印流来实现的。
文本信息中的内容为String类型。而像文件中写入数据,我们经常用到的还有文件输出流对象FileOutputStream.
1 File file = new File("F:\\a.txt"); 2 FileOutputStream outputStream = new FileOutputStream(file,true);//第二个参数为追加文本 3 outputStream.write(97);上面的代码执行完之后,a.txt中的内容存的是a,因为write方法接收的为byte类型的数据,97对应的ASCII码为a。
假设我就想将97写入到文件中呢?那么得将第三行代码改为
outputStream.write("97".getBytes());//先将97作为字符串再转换为byte数组而PrintStream得出现,是的我们写数据入文件变得十分方便,你传入的是什么,就会给你写入什么数据。原因是他内部帮我们转换了
File file = new File("F:\\a.txt"); PrintStream printStream = new PrintStream(file); printStream.println(97); printStream.println('a'); printStream.println("hello world"); printStream.println(true); printStream.println(3.14); printStream.println(new Student("酒香逢"));上面这段代码得到的结果为:
可见不管什么数据类型,都会转换为字符串,甚至是对象也不例外。
这里再说一下学习java时少不了用到的一句代码:System.out.println();代码中的out,为System类中的一个PrintStream对象,称之为标准输出流对象。标准输出流对象会将数据打印到控制台上。查阅API可知有如下方法,
static void setOut(PrintStream out) //重新分配“标准”输出流。
可以重新指定输出流对象,即将System.out.println();的输出内容打印到我们想打印到的地方。
1 File file = new File("F:\\a.txt"); 2 PrintStream printStream = new PrintStream(file); 3 System.setOut(printStream); 4 System.out.println("打印到F:\\a.txt中");这时候内容回写入到文件a.txt中去,而不是打印在控制台中。
最后回归本文重点,日志信息的保存。
假设有代码:
1 try{ 2 int n = 5/0; 3 }catch(Exception e){ 4 e.printStackTrace();执行结果会抛出我们想要的错误日志信息。
java.lang.ArithmeticException: / by zero
at log.DemoLog.main(DemoLog.java:26)这时候想将日志信息保存起来怎么办呢?
看到Exception类中的这3个重载方法,我们不难得知,只要给他指定一个打印输出流对象当中,即可将日志信息保存到我们想要的地方。
File file = new File("F:\\a.log"); PrintStream printStream = new PrintStream(file); try{ int n = 5/0;//除数为零异常 }catch(Exception e){ e.printStackTrace(printStream);上面这段代码执行重复执行多次,
但是记录的日志信息永远只会记录一条。这明显不是我们想得到的,日志信息,总不能只记录一条吧?那么它存在又有什么用?
其实,追加文本信息的决定者不是e.printStackTrace(printStream);方法,关键点在于流对象,
可见打印流对象是存在一个OutputStream接口作为参数的传入对象。既然是接口,那么就无法new出OutputStream的对象了,可以用他的子类FileOutputStream对象作为参数传入。并且,FileOutputStream流是可以追加的,
new FileOutputStream(file,true);//第二个参数为追加文本
此时将其作为参数传入,PrintStream流自然也就可以追加内容了。
File file = new File("F:\\a.log"); PrintStream printStream = new PrintStream(new FileOutputStream(file,true),true); try{ int n = 5/0;//除数为零异常 }catch(Exception e){ e.printStackTrace(printStream);将代码执行3次后:
可以看到日志信息是保存有3条的,日志信息记录保存目的达成!
最后如果我们如果想回来呢?
改为控制台输出需要借助FileDescript这个类,之后的输出就会显示在控制台了
System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out),true));
备注:部分资料来源于网络,表示感谢.