zoukankan      html  css  js  c++  java
  • JAVA IO分析二:字节数组流、基本数据&对象类型的数据流、打印流

    上一节,我们分析了常见的节点流(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)

    字节数组流对象的方法:

    序号方法及描述
    1 public int read()
    从此输入流中读取下一个数据字节。
    2 public int read(byte[] r, int off, int len)
    将最多 len 个数据字节从此输入流读入字节数组。
    3 public int available()
    返回可不发生阻塞地从此输入流读取的字节数。
    4 public void mark(int read)
    设置流中的当前标记位置。
    5 public long skip(long n)
    从此输入流中跳过 n 个输入字节。

    输入流样例:

      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(
                                src
                            )
                    );
            //操作
            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);

    字节数组输出流对象的方法:

    序号方法及描述
    1 public void reset()
    将此字节数组输出流的 count 字段重置为零,从而丢弃输出流中目前已累积的所有数据输出。
    2 public byte[] toByteArray()
    创建一个新分配的字节数组。数组的大小和当前输出流的大小,内容是当前输出流的拷贝。
    3 public String toString()
    将缓冲区的内容转换为字符串,根据平台的默认字符编码将字节转换成字符。
    4 public void write(int w)
    将指定的字节写入此字节数组输出流。
    5 public void write(byte []b, int off, int len)
    将指定字节数组中从偏移量 off 开始的 len 个字节写入此字节数组输出流。
    6 public void writeTo(OutputStream outSt)
    将此字节数组输出流的全部内容写入到指定的输出流参数中。

    说明:

    输出流: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写入数据的话,则必须指定好数据的输出格式。
    数据的写入格式:
    以上每条数据之间使用" "分隔,每条数据中的每个内容之间使用" "分隔。如下图所示
    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('	') ;   // 写入分隔符  
                dos.writeFloat(prices[i]) ; // 写入价格  
                dos.writeChar('	') ;   // 写入分隔符  
                dos.writeInt(nums[i]) ; // 写入数量  
                dos.writeChar('
    ') ;   // 换行  
            }  
            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())!='	'){    // 接收内容  
                        temp[len] = c ;  
                        len ++ ;    // 读取长度加1  
                    }  
                    name = new String(temp,0,len) ; // 将字符数组变为String  
                    price = dis.readFloat() ;   // 读取价格  
                    dis.readChar() ;    // 读取	  
                    num = dis.readInt() ;   // 读取int  
                    dis.readChar() ;    // 读取
      
                    System.out.printf("名称:%s;价格:%5.2f;数量:%d
    ",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(
                                bos
                                )
                    );
            //操作 写出的顺序 为读取准备
            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();
    5 }

    执行结果会抛出我们想要的错误日志信息。

    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));

    备注:部分资料来源于网络,表示感谢.

  • 相关阅读:
    ural 1080 Map Coloring DFS染色
    hdu 4287 Intelligent IME
    hdu 4268 Alice and Bob 区域赛 1002 (STL、SBT实现)
    SBT专题训练
    hdu 4276 The Ghost Blows Light 区域网络赛 1010 树上背包+spfa
    hdu 4278 Faulty Odometer
    hdu 4279 Number
    VIM 插件(转)
    Linux环境变量的设置(转)
    福昕PDF阅读器 v3.3 破解
  • 原文地址:https://www.cnblogs.com/pony1223/p/8064592.html
Copyright © 2011-2022 走看看