上一節,我們分析了常見的節點流(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 數據類型”。
重點:
樣例操作如下: 有如下的訂單數據

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));
備注:部分資料來源於網絡,表示感謝.