IO繼承關係、字節流和字符流

在Java中,可從中讀出一系列數據的對象稱爲“輸入流(InputStream)”,而能向其中寫入一系列數據的對象稱爲“輸出流(OutputStream)”。Java的輸出/輸入都是通過繼承抽象類InputStream和OutputStream(面向字節)、Reader和writer(面向字符)來實現的。

一、IO流對象層次關係

二、IO 基本操作

在java.io包中流的操作主要有字節流、字符流兩大類,兩者都有輸入和輸出的操作。在字節流中輸出數據主要使用OutputStream類完成,輸入使用InputStream類。在字符流中輸出主要是使用Writer類完成,輸入主要使用Reader類完成。

在Java中IO操作也有相應的步驟,以文件的操作爲例,主要的操作流程如下:

(1) 使用File類打開一個文件。

(2) 通過字節流或字符流的子類指定輸出位置。

(3) 進行讀/寫操作。

(4) 關閉輸入/輸出。

下面分別介紹如何使用字節流或字符流保存或讀取數據。

三、字節流

字節流主要存操作byte類型數據,以byte數組爲準,主要操作類是OutputStream類和InputStream類。

1.字節輸出流:OutputStream

OutputStream是整個IO包中字節輸出流的最大父類,此類的定義如下:

public abstract class OutputStream extends Objectimplements Closeable, Flushable

從以上定義中可以發現,OutputStream是一個抽象類,如果要是使用此類,則首先必須通過子類實例化對象。如果現在操作的是一個文件,則可以使用FileOutputStream類,通過向上轉型後,可以爲OutputStream實例化,在OutputStream類中的主要操作方法:

// 關閉此輸出流並釋放與此流有關的所有系統資源
public void close() throws IOException 
// 刷新此輸出流並強制寫出所有緩衝的輸出字節。
public void flush() throws IOException
// 將 b.length 個字節從指定的 byte 數組寫入此輸出流
public void write(byte[] b) throws IOException
// 將指定 byte 數組中從偏移量 off 開始的 len 個字節寫入此輸出流
public void write(byte[] b, int off, int len) throws IOException

 

範例:向文件中寫入字符串。

public class OutputStreamDemo {
	/**
	 * 向文件中寫入字符串
	 */
	public static void main(String[] args) throws IOException {
		// 1.使用File類,找到一個將字符串寫入的文件
		File file = new File("E:" + File.separator + "ops.txt");
		// 2.通過子類FileOutputStream實例化OutputStream
		OutputStream outputStream = null;
		try {
			outputStream = new FileOutputStream(file);
			// 3.進行寫操作
			String str = "Hello world!";
			byte[] b = str.getBytes();
			outputStream.write(b);
		} finally {
			try {
				// 4.關閉輸出流
				outputStream.close();
			} catch (IOException e) {
				throw new RuntimeException("關閉字符輸出流失敗!");
			}
		}
	}
}

 

①提問:通過上面的例子,我們發現將字符串寫入到文件中,但是我們再次運行時會發現,新寫入的字符串將過去的覆蓋了,我們該如何解決了?

回答:

此時我們應該使用FileOutPutStream的另外一個構造方法:

// 如果第二個參數爲 true,則將字節寫入文件末尾處,而不是寫入文件開始處。
public FileOutputStream(File file, boolean append) throws FileNotFoundException

②提問:如何增加換行?

回答:如果要換行,則直接在字符串要換行處加一個"\r\n"。在windows操作系統中“\r\n”表示換行,但是在Linux操作系統中“\n”表示換行,解決辦法,如下:

private static final String FILE_SEPARATOR = System.getProperty("line.separator");

2.字節輸入流

既然程序可以向文件中寫入內容,則也可以通過InputStream從文件中將內容讀取出來。InputStream類的定義如下:

public abstract class InputStreamextends Objectimplements Closeable

與OutputStream類一樣,InputStream本身也是一個抽象類,需要依靠子類來實現,如果從文件中讀取數據,子類是FileInputStream。InputStream的主要方法:

// 返回此輸入流下一個方法調用可以不受阻塞地從此輸入流讀取(或跳過)的估計字節數
public int available() throws IOException
// 關閉此輸入流並釋放與該流關聯的所有系統資源。 
public void close() throws IOException
// 將輸入流中最多 len 個數據字節讀入 byte 數組
public int read(byte[] b, int off, int len) throws IOException

範例:從文件中讀取內容。

public class InputStreamDemo {
	public static void main(String[] args) throws IOException {
		// 1.使用File類,找到一個將字符串寫入的文件
		File file = new File("E:" + File.separator + "ops.txt");
		InputStream inputStream = null;
		try {
			// 2.實例化字節輸入流 InputStream對象
			inputStream = new FileInputStream(file);
			// 3.進行讀操作
			byte[] bs = new byte[1024];
			int len = 0;
			while ((len = inputStream.read(bs)) != -1) {
				System.out.println(new String(bs, 0, len));
			}
		} finally {
			try {
				// 4.關閉資源
				inputStream.close();
			} catch (IOException e) {
				throw new RuntimeException("關閉輸入字節流失敗!");
			}
		}
	}
}

①問題:上面程序中,開闢的byte數據大小爲1024,而實際的內容只有12個字節,顯然開闢的空間太大,該怎麼解決了?

回答:

byte[] bs = new byte[(int) file.length()];

此程序明確知道了要讀取的內容爲14字節,所以可以定義(int)file.length()大小的空間,如果不確定大小,不建議使用此種方法。

四、字符流

在程序中一個字符等於兩個字節,那麼Java提供了Reader和Writer兩個專門操作字符流的類。

1.字符輸出流Writer

此類的定義如下:

public abstract class Writer extends Objectimplements Appendable, Closeable, Flushable

關於對Appendable接口的說明,此接口的定義如下:


public interface Appendable {
	Appendable append(char c) throws IOException;
	Appendable append(CharSequence csq, int start, int end) throws IOException
	Appendable append(CharSequence csq) throws IOException
}

此接口表示的是內容可以被追加,接受的參數是CharSequence,實際上String類也實現了此接口,所以可以直接通過此接口的方法向輸出流中追加內容。

Writer的主要方法:

// 關閉此流,但要先刷新它
public abstract void close()throws IOException
// 刷新該流的緩衝
public abstract void flush() throws IOException
// 寫入字符串
public void write(String str) throws IOException
// 寫入字符數組
public void write(char[] cbuf) throws IOException
範例:向文件中寫入數據
File file = new File("E:" + File.separator + "w.txt");
Writer out = new FileWriter(file);
String str = "hello world!";
out.write(str);
out.close();

2.字符輸入流Reader

Reader是使用字符的方式從文件中讀取數據,Reader類的定義如下:

public abstract class Reader extends Objectimplements Readable, Closeable

Reader類的常用方法:

// 關閉該流並釋放與之關聯的所有資源
public abstract void close()throws IOException
// 讀取單個字符
public int read() throws IOException
// 將字符讀入數組
public int read(char[] cbuf) throws IOException

範例:從文件中讀取內容

File file = new File("E:" + File.separator + "w.txt");
Reader reader = new FileReader(file);
char[] c = new char[1024];
int len = 0;
while ((len = reader.read(c)) != -1) {
	System.out.println(new String(c, 0, len));
}
reader.close();

五、字節流與字符流的區別

字節流與字符流使用非常相似,兩者除了操作代碼上的不同之外,是否還有其他的不同了?

實際上字節流在操作時本身不會用到緩衝區(內存),是文件本身操作的,二字符流在操作時使用了緩衝區,通過緩衝區在操作文件。

下面一兩個寫文件的操作爲主進行比較,但是在操作時字節流和字符流的操作完成之後都不關閉輸出流。

範例:使用字節流不關閉執行

File file = new File("E:" + File.separator + "ops.txt");
OutputStream outputStream = new FileOutputStream(file);
String str = "Hello world!";
byte[] b = str.getBytes();
outputStream.write(b);
// 關閉輸出流
// outputStream.close();

沒有關閉字節流操作,但是文件中也依然存在輸出的內容,證明字節流是直接操作文件本身的。下面使用字符流,觀察效果

範例:使用字符流不關閉執行

File file = new File("E:" + File.separator + "w.txt");
Writer out = new FileWriter(file);
String str = "hello world!";
out.write(str);
// 關閉輸出流
// out.close();

程序運行之後發現文件中沒有任何內容,這是因爲字符流操作使用了緩衝區,而在關閉字符流時會強制性的將緩衝區中的內容進行輸出,但是如果程序沒有關閉,則緩衝區中的內容是無法輸出的,所以得出結論:字符流使用緩衝區,而字節流沒有使用緩衝區。
①問題:什麼是緩衝區?

回答:可以簡單地把緩衝區理解爲一段特殊的內存。

某些情況下,一個程序頻繁地操作一個資源(如文件),則性能會很低,此時爲了提升性能,就可以將一部分數據暫時讀入到內存的一塊區域中,以後直接從此區域中讀取數據即可,因爲讀內存速度比較快,這樣提高性能。

強制性清空緩衝區

OutputStream的flush()方法。

②問題:使用字節流好還是字符流好?

回答:字節流更好。所有的文件在硬盤或者在傳輸時都是以字節的方式進行的,包括圖片等,而字符是只有在內存中才使用。

範例:文件複製

1.實現要求,在dos命令中存在一個文件的複製命令(copy),copy命令的語法格式如下:

copy 源文件 目標文件

下面使用Java完成以上功能,程序運行時可以按照如下格式進行:

java Copy 源文件 目標文件 

2.思路,從運行格式中發現,要輸入源文件和目標文件的路徑,可以使用命令行參數完成,必須對輸入的參數進行驗證,如果輸入的參數不是兩個,或者源文件不存在,則程序給出錯誤信息並退出。

是選擇字節流還是字符流,因爲要複製的文件不一定都是文本文件,也可能包含圖片或者聲音,所以使用字節流。

兩種操作方式:

將源文件中的內容全部讀取到內存,並一次性寫入到目標文件中。

不要將源文件中的內容全部都取進來,而是使用邊讀邊寫的方式。

很顯然,使用邊讀邊寫更好,文件內容過多,則內存是無法裝的。

public class Copy {
	public static void main(String[] args) {
		if (args.length != 2) {
			System.out.println("輸入的參數不正確!");
			System.out.println("例:java Copy 源文件路徑 目標文件路徑");
			System.exit(1);    // 系統退出
		}
		File source = new File(args[0]);  // 源文件的File對象
		File target = new File(args[1]);  // 目標文件的File對象
		if (!source.exists()) {
			System.out.println("源文件不存在!");
			System.exit(1);
		}
		InputStream inputStream = null;
		OutputStream outputStream = null;
		try {
			inputStream = new FileInputStream(source);
			outputStream = new FileOutputStream(target);
			if (inputStream != null && outputStream != null) {
				int len = 0;
				byte[] bs = new byte[1024];
				while ((len = inputStream.read(bs)) != -1) {
					outputStream.write(bs, 0, len);
				}
				System.out.println("複製完成!");
			}
		} catch (IOException e) {
			System.out.println("複製失敗!");
			throw new RuntimeException(e.getMessage());
		}
		finally {
			if (outputStream != null) {
				try {
					outputStream.close();
				} catch (IOException e) {
					throw new RuntimeException("輸出關閉流失敗!");
				}
			}
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					throw new RuntimeException("輸入關閉流失敗!");
				}
			}
		}
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章