1. 程式人生 > >第四十七講 I/O流——常用IO流(列印流、合併流、序列流、隨機訪問流以及管道流)

第四十七講 I/O流——常用IO流(列印流、合併流、序列流、隨機訪問流以及管道流)

列印流

列印流即輸出流,分為位元組列印流PrintStream和字元列印流PrintWriter。下面分別對它們進行介紹。

位元組列印流

概述

PrintStream為其他輸出流添加了功能,使它們能夠方便地列印各種資料值表示形式。它還提供其他兩項功能。與其他輸出流不同,PrintStream永遠不會丟擲IOException;而是,異常情況僅設定可通過checkError方法測試的內部標誌。另外,為了自動重新整理,可以建立一個PrintStream;這意味著可在寫入byte陣列之後自動呼叫flush方法,可呼叫其中一個println方法,或寫入一個換行符或位元組 (’\n’)。
PrintStream列印的所有字元都使用平臺的預設字元編碼轉換為位元組。在需要寫入字元而不是寫入位元組的情況下,應該使用PrintWriter類。

列印的目的地

通過查閱API幫助文件可知,PrintStream類的構造方法如下:
在這裡插入圖片描述
所以,位元組列印流PrintStream的列印目的地有File物件,字串路徑,位元組輸出流。

解決的問題

位元組列印流PrintStream可以方便地列印各種資料值表示形式。它的列印方法可以保證數值的表現形式不變,即寫的是什麼樣子,目的地就是什麼樣子。

package cn.liayun.otherio.print;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;

public class
PrintStreamDemo { public static void main(String[] args) throws IOException { File dir = new File("tempfile"); if (!dir.exists()) { dir.mkdir(); } //演示PrintStream的特有方法 //1,建立PrintStream物件,目的就定為檔案 PrintStream out = new PrintStream("tempfile\\print2.txt"); //將資料列印到檔案中 // out.write(353);//位元組流的write方法一次只寫出一個位元組,也就是將一個整數的最低8位寫出
// out.write("353".getBytes());//麻煩 out.print(97);//保證數值的表現形式不變,其實原理就是將數值都轉成了字串。 out.close(); } }

注意,位元組流一次寫出一個位元組的write方法,就是將一個整數的最低8位寫出。

字元列印流

概述

向文字輸出流列印物件的格式化表示形式。此類實現在PrintStream中的所有print方法。它不包含用於寫入原始位元組的方法,對於這些位元組,程式應該使用未編碼的位元組流進行寫入。
與PrintStream類不同,如果啟用了自動重新整理,則只有在呼叫println、printf或format的其中一個方法時才可能完成此操作,而不是每當正好輸出換行符時才完成。這些方法使用平臺自有的行分隔符概念,而不是換行符。
此類中的方法不會丟擲I/O異常,儘管其某些構造方法可能丟擲異常。客戶端可能會查詢呼叫checkError()是否出現錯誤。

列印的目的地

通過查閱API幫助文件可知,PrintWriter類的構造方法如下:
在這裡插入圖片描述
所以,字元列印流PrintWriter的列印目的地有File物件,字串路徑,位元組輸出流、字元輸出流。

案例

讀取鍵盤錄入,將資料轉成大寫,顯示在螢幕上。

package cn.liayun.otherio.print;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class PrintWriterDemo {

	public static void main(String[] args) throws IOException {
		/**
		 * 演示一個小例子
		 * 讀取鍵盤入錄入。將資料轉成大寫,顯示在螢幕上。
		 */
		
		//1,鍵盤錄入
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
		
		//2,
		//BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
		PrintWriter pw = new PrintWriter(System.out, true);//對println方法可以實現自動重新整理
		
		//改變目的為檔案,我還想自動重新整理。
//		pw = new PrintWriter(new BufferedWriter(new FileWriter("1.txt")), true);
		
		//3,讀一行,寫一行,鍵盤錄入一定要定義結束標記
		String line = null;
		while ((line = bufr.readLine()) != null) {
			if ("over".equals(line)) {
				break;
			}
			pw.println(line.toUpperCase());
//			pw.flush();
		}
		pw.close();
//		bufr.close();//不需要關閉鍵盤錄入這種標準輸入流,一旦關閉,後面獲取不到。
	}

}

合併流

概述

SequenceInputStream表示其他輸入流的邏輯串聯。它從輸入流的有序集合開始,並從第一個輸入流開始讀取,直到到達檔案末尾,接著從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的檔案末尾為止。

解決的問題

將多個輸入流合併成了一個輸入流,將多個源合併成了一個源,對於多個源的操作會變的簡單。

功能(構造方法)

通過查閱API幫助文件可知,SequenceInputStream類的構造方法如下:
在這裡插入圖片描述
特殊之處在建構函式上,一初始化就合併了多個流進來。

應用場景

對多個檔案進行資料的合併,多個源對應一個目的。

package cn.liayun.otherio.sequence;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;

public class SequenceInputStreamDemo {

	public static void main(String[] args) throws IOException {
		/*
		 * 演示序列流,SequenceInputStream。
		 */
		//如何獲取一個Enumeration呢?Vector有,但是效率低,使用ArrayList。
		ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
		//新增三個輸入流物件,和指定的具體檔案關聯
		for (int x = 1; x <= 3; x++) {
			al.add(new FileInputStream("tempfile\\" + x + ".txt"));
		}
		
		//怎麼通過ArrayList獲取列舉介面,可以使用Collections工具類中的方法。
		Enumeration<FileInputStream> en = Collections.enumeration(al);
		
		//建立一個序列流物件。需要傳遞引數Enumeration。
		SequenceInputStream sis = new SequenceInputStream(en);
		
		//建立目的(檔案)
		FileOutputStream fos = new FileOutputStream("tempfile\\4.txt");
		
		//頻繁的讀寫操作
		//1,建立緩衝區
		byte[] buf = new byte[1024];
		int len = 0;
		while ((len = sis.read(buf)) != -1) {
			fos.write(buf, 0, len);
		}
		
		//關閉流
		fos.close();
		sis.close();
	}

}

序列流

序列流分為序列化流ObjectOutputStream和反序列化流ObjectInputStream。下面分別對它們進行介紹。

序列化流ObjectOutputStream

概述

ObjectOutputStream將Java物件的基本資料型別和圖形寫入OutputStream。可以使用 ObjectInputStream讀取(重構)物件。通過在流中使用檔案可以實現物件的持久儲存。如果流是網路套接字流,則可以在另一臺主機上或另一個程序中重構物件。

解決的問題

可以對物件進行序列化(又叫物件的持久化或物件的序列化)。注意,物件序列化一定要實現Serializable介面,為了給類定義一個serialVersionUID。

功能(特殊方法)

方法 描述
public final void writeObject(Object obj) 將指定的物件寫入ObjectOutputStream。物件的類、類的簽名,以及類及其所有超型別的非瞬態和非靜態欄位的值都將被寫入

Serializable介面

序列化流ObjectOutputStream只能將支援java.io.Serializable介面的物件寫入流中,也即物件要想序列化一定要實現Serializable介面。否則會報未序列化異常NotSerializableException,諸如下圖所示。
在這裡插入圖片描述
序列化資料後,再次修改類檔案,讀取資料會出問題,如何解決呢?每次修改java檔案的內容的時候,class檔案的id值都會發生改變。而讀取檔案的時候,會和class檔案中的id值進行匹配,所以,就會出問題。可以讓這個id值在java檔案中是一個固定的值,這樣,當你修改檔案的時候,這個id值就不會發生改變。看到類實現了序列化介面的時候,要想解決黃色警告線問題,就可以自動產生一個序列化id值。
在這裡插入圖片描述
而且產生這個值以後,我們對類進行任何改動,它讀取以前的資料是沒有問題的。

transient關鍵字

當使用Serializable介面實現序列化操作時,如果一個物件中的某個屬性不希望被序列化,則可以使用transient關鍵字進行宣告。當然了,static修飾的靜態屬性也不能被序列化,序列化的只是堆記憶體中物件的屬性。

案例

將一個物件儲存到持久化(硬碟)的裝置上。要序列化的Person類:

package cn.liayun.domain;

import java.io.Serializable;

/*
 * Person類的物件如果需要序列化,就需要實現Serializable標記介面。
 * 該介面給需要序列化的類,提供了一個序列版本號---serialVersionUID
 * 該版本號的目的在於用於驗證序列化的物件和對應的類是否版本匹配。
 */
public class Person implements Serializable {
	/**
	 * 給類顯示宣告一個序列版本號
	 */
	private static final long serialVersionUID = 1L;

	private /*static*/ String name;
	private transient/*瞬態*/ int age;

	public Person() {
		super();
		// TODO Auto-generated constructor stub
	}

	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}

}

序列化一個Person物件,即將物件內容儲存到檔案中:

package cn.liayun.otherio.objectstream;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

import cn.liayun.domain.Person;

public class ObjectStreamDemo {

	public static void main(String[] args) throws IOException, ClassNotFoundException {
		/*
		 * 將一個物件儲存到持久化(硬碟)的裝置上。
		 */
		writeObj();//物件的序列化
	}

	public static void writeObj() throws IOException {
		//1,明確儲存物件的檔案
		FileOutputStream fos = new FileOutputStream("tempfile\\obj.object");
		//2,給操作檔案物件加入寫入物件的功能
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		//3,呼叫寫入物件的方法
		oos.writeObject(new Person("wangcai", 20));
		//關閉資源
		oos.close();
	}

}

反序列化流ObjectInputStream

概述

ObjectInputStream對以前使用ObjectOutputStream寫入的基本資料和物件進行反序列化。

功能(特殊方法)

方法 描述
public final Object readObject() 從ObjectInputStream讀取物件。物件的類、類的簽名和類及所有其超型別的非瞬態和非靜態欄位的值都將被讀取

readObject方法用於從流讀取物件。應該使用Java的安全強制轉換來獲取所需的型別。在Java中,字串和陣列都是物件,所以在序列化期間將其視為物件。讀取時,需要將其強制轉換為期望的型別。

案例

反序列化一個Person物件,即從檔案中讀取到一個物件:

package cn.liayun.otherio.objectstream;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

import cn.liayun.domain.Person;

public class ObjectStreamDemo {

	public static void main(String[] args) throws IOException, ClassNotFoundException {
		readObj();//物件的反序列化
	}

	public static void readObj() throws IOException, ClassNotFoundException {
		//1,定義一個流物件關聯了儲存了物件的檔案
		FileInputStream fis = new FileInputStream("tempfile\\obj.object");
		//2,建立用於讀取物件的功能物件
		ObjectInputStream ois = new ObjectInputStream(fis);
		Person obj = (Person) ois.readObject();
		System.out.println(obj.toString());
	}

}

隨機訪問流

概述

RandomAccessFile類不屬於流,是Object類的子類,但它融合了InputStream和OutputStream的功能。
此類的例項支援對隨機訪問檔案的讀取和寫入。隨機訪問檔案的行為類似儲存在檔案系統中的一個大型 byte陣列。存在指向該隱含陣列的游標或索引,稱為檔案指標;輸入操作從檔案指標開始讀取位元組,並隨著對位元組的讀取而前移此檔案指標。如果隨機訪問檔案以讀取/寫入模式建立,則輸出操作也可用;輸出操作從檔案指標開始寫入位元組,並隨著對位元組的寫入而前移此檔案指標。寫入隱含陣列的當前末尾之後的輸出操作導致該陣列擴充套件。該檔案指標可以通過getFilePointer方法讀取,並通過seek方法設定。

特點

隨機訪問流有如下幾個特點:

  1. 只能操作檔案;
  2. 既能讀,又能寫;
  3. 維護了一個大型byte陣列,內部定義了位元組流的讀取和寫入;
  4. 通過對指標的操作,可以實現對檔案的任意位置的讀取和寫入。

功能(構造方法)

通過查閱API幫助文件可知,RandomAccessFile類的構造方法如下:
在這裡插入圖片描述
通過建構函式可以看出,該類只能操作檔案,而且操作檔案還有模式,而第二個引數就是操作檔案的模式,模式有四種,我們最常用的兩種叫只讀r,讀寫rw。

  • 如果模式為只讀r,不會建立檔案,會去讀取一個已存在的檔案,如果該檔案不存在,則會出現異常;
  • 如果模式為rw,操作的檔案不存在,會自動建立,如果存在則不建立也不會覆蓋(會修改檔案)。

案例

對隨機訪問檔案的讀取和寫入。

package cn.liayun.otherio.randomaccess;

import java.io.IOException;
import java.io.RandomAccessFile;

public class RandomAccessFileDemo {

	public static void main(String[] args) throws IOException {
		/*
		 * RandomAccessFile:支援對隨機訪問檔案的讀取和寫入。
		 * 特點:
		 * 1,只能操作檔案
		 * 2,既能讀,有能寫
		 * 3,維護了一個大型byte陣列,內部定義了位元組流的讀取和寫入
		 * 4,通過對指標的操作,可以實現對檔案的任意位置的讀取和寫入
		 */
		
//		writeFile();
		readFile();
	}		

	public static void readFile() throws IOException {
		RandomAccessFile raf = new RandomAccessFile("tempfile\\random.txt", "r");
		
		//隨機讀取,只要通過設定指標的位置即可
		raf.seek(8 * 1);
		
		byte[] buf = new byte[4];
		raf.read(buf);
		String name = new String(buf);
		
		int age = raf.readInt();
		System.out.println(name + ":" + age);
		raf.close();
	}

	public static void writeFile() throws IOException {
		//1,建立一個隨機訪問檔案的物件。檔案不存在,則建立;存在,則不建立也不覆蓋。
		RandomAccessFile raf = new RandomAccessFile("tempfile\\random.txt", "rw");
		
		//2,寫入人的姓名和年齡
//		raf.write("張三".getBytes());
//		raf.writeInt(97);//它可以保證整數的位元組原樣性。
//		raf.write("李四".getBytes());
//		raf.writeInt(99);//它可以保證整數的位元組原樣性。
		
		//3,隨機寫入
		raf.seek(8);//設定指標的位置
		System.out.println(raf.getFilePointer());
		raf.write("王五".getBytes());
		raf.writeInt(100);
		System.out.println(raf.getFilePointer());
		
		raf.close();
	}

}

管道流

概述

管道流分為管道輸入流PipedInputStream和管道輸出流PipedOutputStream,由於實際開發中用到的並不多,在這裡我就不打算細講了。但要知道管道流的主要作用是可以進行兩個執行緒間的通訊。反正管道流結合的是多執行緒技術,可以檢視API幫助文件。

特點

管道流具有如下兩個特點:

  1. 讀取管道和寫入管道可以連線;
  2. 需要使用多執行緒技術,單執行緒容易死鎖。

案例

package cn.liayun.otherio.piped;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

public class PipedStreamDemo {

	public static void main(String[] args) throws IOException {
		//建立管道物件
		PipedInputStream pis = new PipedInputStream();
		PipedOutputStream pos = new PipedOutputStream();
		//將兩個流連線上
		pis.connect(pos);
		
		//開啟兩個執行緒
		new Thread(new Input(pis)).start();
		new Thread(new Output(pos)).start();
	}

}

// 定義輸入任務
class Input implements Runnable {
	private PipedInputStream pis;

	public Input(PipedInputStream pis) {
		super();
		this.pis = pis;
	}

	@Override
	public void run() {
		byte[] buf = new byte[1024];
		int len;
		try {
			len = pis.read(buf);
			String str = new String(buf, 0, len);
			System.out.println(str);
			pis.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

// 定義輸出任務
class Output implements Runnable {
	private PipedOutputStream pos;

	public Output(PipedOutputStream pos) {
		super();
		this.pos = pos;
	}

	@Override
	public void run() {
		//通過寫方法完成
		try {
			pos.write("hi,管道來了!".getBytes());
			
			pos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

執行結果:
在這裡插入圖片描述