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

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

原文地址:https://www.cnblogs.com/pony1223/p/8064592.html