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寫入數據的話,則必須指定好數據的輸出格式。
數據的寫入格式:
以上每條數據之間使用"\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(
                            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));

 

備注:部分資料來源於網絡,表示感謝.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM