1. 程式人生 > >Java基礎--I/O流知識總結

Java基礎--I/O流知識總結

Java基礎–I/O流知識總結

引言

I/O(輸入/輸出)應該算是所有程式都必需的一部分,使用輸入機制,允許程式讀取外部的資料資源、接收使用者輸入;使用輸出機制,允許程式記錄允許狀態,並將資料輸出到外部裝置。Java的IO是通過java.io包下的類和介面來實現的,其下主要包括輸入、輸出兩種IO流,根據流中操作的資料單元的不同又分為位元組流(8位的位元組)和字元流(16位的字元)。

Java中IO的結構體系

在這裡插入圖片描述

位元組流(InputStream/OutputStream)

首先來看一看位元組輸入流InpuStream,此抽象類是表示位元組輸入流的所有類的超類,它有如下三個基本方法。

  1. int read()
    :從輸入流中讀取單個位元組,返回所讀取的世界資料。
  2. int read(byte[] b):從輸入流中最多讀取b.length個位元組的資料,並將其儲存在緩衝區陣列b中,返回實際讀取的位元組數。
  3. int read(byte[] b,int off,int len):將輸入流中最多len 個數據位元組讀入緩衝區陣列b中,off為陣列 b 中將寫入資料的初始偏移量,返回實際讀取的位元組數。

位元組輸出流OutputStream,此抽象類是表示位元組輸出流的所有類的超類,它有如下三個基本方法。

  1. void write(int c):將指定的位元組寫入此輸出流。
  2. void write(byte[] buf)
    :將 b.length個位元組從指定的 byte 陣列寫入此輸出流。
  3. void write(byte[] buf,int off,int len):將指定 byte陣列中從偏移量off開始的 len個位元組寫入此輸出流。

接下來逐個介紹位元組流的各個子類。

FileInputStream/FileOutputStream

這兩個類直接繼承自InputStreamOutputStream,可實現對檔案的讀取和資料寫入。如下程式碼即可實現對檔案的複製操作。

package cn.lincain.io;

import java.io.FileInputStream;
import java.io.FileOutputStream;

public class FileStreamTest {
	public static void main(String[] args) {
		try (
			// 建立一個位元組輸入流
			FileInputStream fis = new FileInputStream("FileStreamTest .java");
			// 建立一個位元組輸出流
			FileOutputStream fos = new FileOutputStream("copy.txt"))
		{
			int hasRead = 0;
			// 從ByteStreamTest.java檔案讀取資料到流中
			while ((hasRead = fis.read()) != -1) {
				// 將資料寫入到指定的檔案中
				fos.write(hasRead);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

我們將輸入流比作是一個水管,裡面裝的水就是檔案的資料,read()write(int c)方法相當於每次從中取一滴水,資料量不大的時候,效率還可以,但是當資料量非常大時,讀取的效率就很低。
下面我們採用int read(byte[] b)void write(byte[] b,int off,int len)方法來示範檔案複製的效果。

package cn.lincain.io;

import java.io.FileInputStream;
import java.io.FileOutputStream;

public class FileStreamTest {
	public static void main(String[] args) {
		try (
				// 建立一個位元組輸入流
				FileInputStream fis = new FileInputStream("FileStreamTest .java");
				// 建立一個位元組輸出流
				FileOutputStream fos = new FileOutputStream("copy.txt")) 
		{
			long start = System.currentTimeMillis();
			byte[] buff = new byte[12];
			int hasRead = 0;
			// 從ByteStreamTest.java檔案讀取資料到流中
			while ((hasRead = fis.read(buff)) > 0) {
				// 將資料寫入到指定的檔案中
				fos.write(buff, 0, hasRead);
			}
			long end = System.currentTimeMillis();
			System.out.println("檔案複製總共花了:" + (end - start) + "ms");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

同樣的案例,當我們採用陣列作為read()write()的引數時,該陣列就相當如一個“水桶”,每次將水桶的水接滿了或者水管的水接完了才輸出,這樣就極大的提高了讀取效率,其中第一個程式複製檔案消耗了21ms,而第二個程式則只消耗了1ms,由此可見二者的差距。
後面對其他位元組流流我們都採取就用緩衝效果的方法進行示範。

ByteArrayInputStream/ByteArrayOutputStream

以上兩個位元組流從字面意思可知,就是位元組陣列與位元組輸入輸出流之間的各種轉換。
ByteArrayInputStream的構造方法就包含一個位元組陣列作為它本身的內部緩衝區,該緩衝區包含從流中讀取的位元組。
ByteArrayOutputStream實現了一個輸出流,其中的資料被寫入一個 byte 陣列,緩衝區會隨著資料的不斷寫入而自動增長,可使用 toByteArray() 和 toString() 獲取資料。
舉個例子如下:

package cn.lincain.io;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

public class ByteStreamDemo {
	public static void main(String[] args) {
		// 建立記憶體中的位元組陣列
		byte[] btyeArray = { 1, 2, 3 };
		try (
			// 建立位元組輸入流
			ByteArrayInputStream bis = new ByteArrayInputStream(btyeArray);
			// 建立位元組輸出流
			ByteArrayOutputStream bos = new ByteArrayOutputStream())
		{
			byte[] bff = new byte[3];
			// 從位元組流中讀取位元組到指定陣列中
			bis.read(bff);
			System.out.println(bff[0]+","+bff[1]+","+bff[2]);
			
			// 向位元組輸出流中寫入位元組
			bos.write(bff);
			// 從輸出流中獲取位元組陣列
			byte[] bArrayFromBos = bos.toByteArray();
			System.out.println(bArrayFromBos[0] + "," + bArrayFromBos[1] + "," + bArrayFromBos[2]);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

需要注意的是:在ByteArrayInputStream裡面直接使用外部位元組陣列的引用,也就是說,即使得到位元組流物件後,當改變外部陣列時,通過流讀取的位元組也會改變。

PipedOutputStream/PipedInputStream

PipedOutputStreamPipedInputStream分別是管道輸出流和管道輸入流。
它們的作用是讓多執行緒可以通過管道進行執行緒間的通訊。在使用管道通訊時,必須將PipedOutputStreamPipedInputStream配套使用。
使用管道通訊時,大致的流程是:我們線上程A中向PipedOutputStream中寫入資料,這些資料會自動的傳送到與PipedOutputStream對應的PipedInputStream中,進而儲存在PipedInputStream的緩衝中;此時,執行緒B通過讀取PipedInputStream中的資料。就可以實現,執行緒A和執行緒B的通訊。

package cn.lincain.io1;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class PipedStreamTest {
	public static void main(String[] args) {

		try (final PipedOutputStream pos = new PipedOutputStream();
			 final PipedInputStream pis = new PipedInputStream(pos)) 
		{
			ExecutorService es = Executors.newFixedThreadPool(2);
			es.execute(new Runnable() {
				@Override
				public void run() {
					try {
						byte[] bArr = new byte[] { 1, 2, 3 };
						pos.write(bArr);
						pos.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			});
			es.execute(new Runnable() {
				@Override
				public void run() {
					byte[] bArr = new byte[3];
					try {
						// 會導致執行緒阻塞
						pis.read(bArr, 0, 3);
						pis.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
					System.out.println(bArr[0] + "," + bArr[1] + "," + bArr[2]);
				}
			});
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

BufferedInputStream/BufferedOutputStream

BufferedInputStreamBufferedOutputStream 分別是緩衝輸入流和快取輸出流,他們分別繼承自FilterInputStreamFilterOutputStream,主要功能是一次讀取/寫入一大塊位元組到緩衝區,避免每次頻繁訪問外部媒介,提高效能。
其中BufferedInputStream的作用是緩衝輸入以及支援 mark()reset ()方法的能力。
BufferedOutputStream的作用是為另一個輸出流提供“緩衝功能”。
下面的案例演示了採用緩衝流進行檔案的複製。

package cn.lincain.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class BufferedStreamTest {
	public static void main(String[] args) {
		try (BufferedInputStream bis = 
				new BufferedInputStream(new FileInputStream("ByteStreamTest.java"));
			BufferedOutputStream bos = 
				new BufferedOutputStream(new FileOutputStream("copy.txt"))) 
		{
			int hasRead = 0;
			while ((hasRead = bis.read()) != -1) {
				bos.write(hasRead);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

該程式和上面的FileStreamTest .java的程式碼大致相同,但是該案例的效率顯然更高,只用了不到1ms的時間。

DataOutputStream/DataInputStream

DataInputStream允許應用程式讀取在與機器無關方式從底層輸入流基本Java資料型別,應用程式可以使用資料輸出流寫入稍後由資料輸入流讀取的資料。
DataOutputStream允許應用程式以適當方式將基本 Java 資料型別寫入輸出流中。然後,應用程式可以使用資料輸入流將資料讀入。

package cn.lincain.io;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class DataStreamTest {
	public static void main(String[] args) {
		try (
			DataOutputStream dos = 
				new DataOutputStream(new FileOutputStream("target.txt"));
			DataInputStream dis = 
				new DataInputStream(new FileInputStream("target.txt")))
		{
			dos.writeBoolean(false);
			dos.writeChar('夜');
			dos.write(18);
			dos.writeUTF("權利的遊戲");
			
			System.out.println(dis.readBoolean());
			System.out.println(dis.readChar());
			System.out.println(dis.read());
			System.out.println(dis.readUTF());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

如果要想使用資料輸出流,則肯定要由使用者自己制定資料的儲存格式,必須按指定好的格式儲存資料,才可以使用資料輸入流將資料讀取進來。

ObjectInputStream/ObjectOutputStream

ObjectOutputStream用於將指定的物件寫入到檔案的過程,就是將物件序列化的過程。ObjectInputStream則是將儲存在檔案的物件讀取出來,就是將物件反序列化的過程。
一個物件如果要實現序列化和反序列化,則需要該物件實現Serializable或者Externalizable介面。

class Person implements Serializable{
	private String name;
	private int 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 + "]";
	}
}
package cn.lincain.io;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class ObjectStreamTest {
	public static void main(String[] args) {
		Person person = new Person();
		person.setAge(28);
		person.setName("Lincain");
		writeObject(person);
		readObject();
	}
	
	// 將物件寫入指定的檔案
	public static void writeObject(Object object) {
		try (
			ObjectOutputStream oos = 
				new ObjectOutputStream(new FileOutputStream("object.txt")))
		{
			oos.writeObject(object);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	// 將檔案中將物件讀取出來
	public static void readObject() {
		try (
			ObjectInputStream ois = 
				new ObjectInputStream(new FileInputStream("object.txt")))
		{
			Person person = (Person)ois.readObject();
			System.out.println(person);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

執行上面程式,在磁碟上產生了object.txt檔案,說明物件已將序列化到本地檔案,如下圖:
在這裡插入圖片描述
需要注意的是,反序列化過程讀取的僅僅是Java物件的資料,而不是Java類,因此反序列化恢復Java物件時,必須提供該物件的class檔案,否則會引起ClassNotFoundException異常。另外反序列化也無須通過構造器來初始化物件。
關於序列化的知識可參考:https://www.cnblogs.com/fnz0/p/5410856.html

PushbackInputStream

退回:其實就是將從流讀取的資料再推回到流中。實現原理也很簡單,通過一個緩衝陣列來存放退回的資料,每次操作時先從緩衝陣列開始,然後再操作流物件。

package cn.lincain.io;

import java.io.ByteArrayInputStream;
import java.io.PushbackInputStream;

public class PushBackInputStreamTest {
	public static void main(String[] args) {
		String str = "abcdefghijk";
		try (
			//  //建立位元組推回流,緩衝區大小為 7
			ByteArrayInputStream bis = new ByteArrayInputStream(str.getBytes());
			PushbackInputStream pis = new PushbackInputStream(bis,7))
		{
			byte[] hasRead = new byte[7];
			// 從流中將7個位元組讀入陣列
			pis.read(hasRead);
			System.out.println(new String(hasRead)); //abcdefg
			
			// 從陣列的第一個位置開始,推回 4 個位元組到流中
			pis.unread(hasRead,0,4);
			
			// 重新從流中將位元組讀取陣列
			pis.read(hasRead);
			System.out.println(new String(hasRead)); // abcdhij
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
  1. 建立位元組推回流,同時建立了大小為7的緩衝區陣列;要從位元組輸入流讀取位元組到 hasRead 陣列;
  2. 從流中讀取 7 個位元組到hasRead陣列;
  3. 現在要從hasRead的 0 下標(第一個位置)開始,推回 4 個位元組到流中去(實質是推回到緩衝陣列中去);
  4. 再次進行讀取操作,首先從緩衝區開始讀取,再對流進行操作。

PrintStream

PrintStream 為其他輸出流添加了功能,使它們能夠方便地列印各種資料值表示形式,使其它輸出流能方便的通過print()println()printf()等輸出各種格式的資料。

package cn.lincain.io;

import java.io.FileOutputStream;
import java.io.PrintStream;

public class PrintStreamTest {
	public static void main(String[] args) {
		try (
			// 建立列印流,並允許在檔案末端追加
			PrintStream ps = new PrintStream(new FileOutputStream("a.txt", true))) 
		{
			// 將字串“Hello,位元組流!”+回車符,寫入到輸出流中
			ps.println("Hello,位元組流!");
			// 97對應ASCII碼的是'A',也就是說此處寫入的是字元'A'
			ps.write(97);
			// 將字串"97"寫入輸出流中,等價於ps.write(String.valueOf(97))
			ps.print(97);
			// 將'B'追加到輸出流中,和ps.print('B')效果相同
			ps.append('B');
			
			// Java的格式化輸出 
			String str = "CDE";
			int num = 5;
			ps.printf("%s is %d\n", str, num);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

SequenceInputStream

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

package cn.lincain.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
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;
import java.util.List;

public class SequenceInputStreamTest {
	public static void main(String[] args) throws IOException {
		enumMerge();
	}
	
	// 通過public SequenceInputStream(Enumeration<? extends InputStream> e)建立SequenceInputStream
	public static void enumMerge() throws IOException {
		List<FileInputStream> arrayList = new ArrayList<>();

		FileInputStream is1 = new FileInputStream("1.txt");
		FileInputStream is2 = new FileInputStream("2.txt");
		FileInputStream is3 = new FileInputStream("3.txt");

		arrayList.add(is1);
		arrayList.add(is2);
		arrayList.add(is3);

		Enumeration<FileInputStream> enumeration = Collections.enumeration(arrayList);
		
		BufferedInputStream bis = 
			new BufferedInputStream(new SequenceInputStream(enumeration));
		
		BufferedOutputStream bos = 
			new BufferedOutputStream(new FileOutputStream("4.txt"));
		
		int hasRead = 0;
		while ((hasRead = bis.read()) != -1) {
			bos.write(hasRead);
		}
		
		bis.close();
		bos.close();
	}
}

字元流(Reader/Writer)

位元組流和字元流的操作方式幾乎一樣,只是他們操作的資料單元不同,體現在方法上時,就是方法的引數有所差別。
首先字元輸入流Reader,它的三個方法分別是:

  1. int read():從輸入流中讀取單個字元,返回所讀取的世界資料。
  2. int read(char[] c):從輸入流中最多讀取b.length個字元的資料,並將其儲存在緩衝區陣列c中,返回實際讀取的字元數。
  3. int read(char[] c,int off,int len):將輸入流中最多len 個數據字元讀入緩衝區陣列c中,off為陣列 c 中將寫入資料的初始偏移量,返回實際讀取的字元數。

字元輸出流Writer,因為它可以直接操作字元,可以用字串代替字元陣列,所以它有如下五個基本方法。

  1. void write(int c):將指定的字元寫入此輸出流。
  2. void write(char[] c):將 b.length個字元從指定的c陣列寫入此輸出流。
  3. void write(char[] c,int off,int len):將字元陣列中從偏移量off開始的 len個位元組寫入此輸出流。
  4. void write(String str):將str中包含的字元輸出到指定的輸出流中。
  5. void write(String str, int off,int len):將stroff位置開始,長度為len的字元輸出到指定輸出流中。

FileReader/FileWriter

通過FileReader/FileWriter我們可以實現對文字檔案的讀取、寫入,從而實現文字檔案的複製。

package cn.lincain.io;

import java.io.FileReader;
import java.io.FileWriter;

public class FileCopyTest {
	public static void main(String[] args) {
		try (
			FileReader fr = new FileReader("FileCopyTest.java");
			FileWriter fw = new FileWriter("copy.txt"))
		{
			char[] cbuf = new char[32];
			int hasRead = 0;
			while ((hasRead = fr.read(cbuf)) != -1) {
				fw.write(cbuf, 0, hasRead);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

CharArrayReader/CharArrayWriter

CharArrayReader/CharArrayWriter就是字元陣列與字元輸入輸出流之間的各種轉換。和ByteArrayInputStream/ByteArrayOutputStream類似,它們都有一個內部緩衝區,區別是前者操作字元,後置操作位元組。

package cn.lincain.io;

import java.io.CharArrayReader;
import java.io.CharArrayWriter;

public class CharArrayTest {
	public static void main(String[] args) {
		char[] charArray = "無邊落木蕭蕭下,不盡長江滾滾來!!!".toCharArray();

		try (
				CharArrayReader cr = new CharArrayReader(charArray); 
				CharArrayWriter cw = new CharArrayWriter()) 
		{
			int hasRead = 0;
			char[] buff = new char[1024];
			while ((hasRead = cr.read(buff)) != -1) {
				cw.write(buff, 0, hasRead);
			}
			// 將輸入資料轉換為字串。
			System.out.println(cw.toString());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

同樣需要注意的是,在CharArrayReader裡面直接使用外部字元陣列的引用,也就是說,即使得到字元流物件後,當改變外部陣列時,通過流讀取的字元也會改變。

BufferedReader/BufferedWriter

和緩衝位元組流相似的,緩衝字元流可以用來裝飾其他字元流,為其提供字元緩衝區,避免頻繁的和外界互動,從而提高效率。被裝飾的字元流可以有更多的行為,比如readLine()newLine()方法等。
下來我們還是通過檔案複製的案例,對其效果進行演示。

package cn.lincain.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;

public class BufferedCopyTest {
	public static void main(String[] args) {
		try (
			// 建立字元緩衝輸入流
			BufferedReader br = 
				new BufferedReader(new FileReader("BufferedCopyTest.java"));
			// 建立字元緩衝輸出流
			BufferedWriter bw = 
					new BufferedWriter(new FileWriter("copy.txt")))
		{
			String line = null;
			// 呼叫readLine()方法,每次讀取一行
			while ((line = br.readLine()) != null) {
				// 將字串寫入到輸出流中
				bw.write(line);
				// 向輸出流寫入一個換行符
				bw.newLine();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

PipedReader/PipedWriter

通過閱讀原始碼可知,PipedReader/PipedWriter分別將對方作為自己的成員變數,可知二者需要配套使用。通過二者建立聯絡,構建一個字元流通道,PipedWriter向管道寫入資料,PipedReader從管道中讀取資料,從而實現執行緒的通訊。
如下示例:

package cn.lincain.io;

import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class PipedTest {
	public static void main(String[] args) throws IOException {

		final PipedWriter pw = new PipedWriter();
		final PipedReader pr = new PipedReader(pw);

		ExecutorService eService = Executors.newFixedThreadPool(2);
		eService.execute(new Runnable() {

			@Override
			public void run() {
				try {
					pw.write("執行緒互動!!!");
				} catch (IOException e) {
					e.printStackTrace();
				}

			}
		});

		eService.execute(new Runnable() {

			@Override
			public void run() {
				char[] cbuf = new char[32];
				try {
					// 會導致執行緒阻塞
					pr.read(cbuf);
					pr.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
				System.out.println(cbuf);
			}
		});
	}
}

需要注意的是:一定要同一個JVM中的兩個執行緒,且讀寫都會阻塞執行緒。

StringReader/StringWriter

這兩個類和前面介紹的CharArrayReader/CharArrayWriter很相似,只是後者操作的資料型別為字元陣列,而這裡操作的是字串,二者的方法也大致相同,下面通過案例來說明。

package cn.lincain.io;

import java.io.StringReader;
import java.io.StringWriter;

public class StringBufferTest {
	public static void main(String[] args) {
		String str = "字串緩衝流";
		try (
				StringReader sr = new StringReader(str);
				StringWriter sw = new StringWriter())
		{	
			// 從輸入流中讀取資料
			int hasRead = 0;
			char[] cbuf = new char[32];
			while ((hasRead = sr.read(cbuf)) != -1) {
				System.out.println(new String(cbuf, 0, hasRead));
			}
			
			// 向輸出流中寫入資料
			sw.write(str);
			System.out.println(sw.toString());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

PushbackReader

PushbackReaderPushbackInputStream的操作方式基本一樣,這裡就不在再詳細的介紹,通過案例對其進行說明。

package cn.lincain.io;

import java.io.FileReader;
import java.io.IOException;
import java.io.PushbackReader;

public class PushbackReaderTest {
	public static void main(String[] args) throws IOException {
		PushbackReader pbr = 
				new PushbackReader(new FileReader("PushbackReaderTest.java"),30);
		
		char[] cbuf = new char[24];
		pbr.read(cbuf);
		System.out.println(new String(cbuf)); // package cn.lincain.io3;
		
		// 如果pbr中的緩衝區的大小小於回退陣列的大小,則丟擲異常
		pbr.unread(cbuf);
		pbr.read(cbuf);
		System.out.println(new String(cbuf)); // package cn.lincain.io3;
		pbr.close();
	}
}

PrintWriter

PrintWriterPrintStream的操作基本一致,只是前者多了兩個方法:void write(String s)void write(String s,int off,int len)
但通過原始碼可知,PrintStream也有這兩個方法,只是用private修飾,方法內容不一樣,但是最終都指向了Writer類的void write(String str, int off, int len)方法。

package cn.lincain.io;

import java.io.PrintWriter;

public class PrintWriterTest {
	public static void main(String[] args) {
		try (
			PrintWriter pw = new PrintWriter(System.out))
		{
			// 97對應ASCII碼的是'A',也就是說此處寫入的是字元'A'
			pw.write(97);
			// 將字串"97"寫入輸出流中,等價於ps.write(String.valueOf(97))
			pw.print(97);
			// 將'B'追加到輸出流中,和ps.print('B')效果相同
			pw.append('B');
			
			// Java的格式化輸出 
			String str = "CDE";
			int num = 5;
			pw.printf("%s is %d\n", str, num);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}