Java的IO流解析

一、流的分類
按照流向分:輸入流、輸出流
輸入流:只能從中讀取數據,而不能向其寫出數據
輸出流:只能向其寫出數據,而不能從中讀取數據
這裏的輸出、輸入都是從程序運行所在內存的角度來劃分的
Java的輸入流主要有InputStream和Reader作爲基類,而輸出流則主要有OutputStream和Writer作爲基類
字節流和字符流
字節流和字符流的區別很簡單,他們的用法幾乎完全一樣,區別在於字節流和字符流所操作的數據單元不同:字節流操作的最小數據單元是8位的字節,而字符流操作的最小數據單元是16位的字符


字節流主要由InputStream和OutputStream作爲基類
字符流主要由Reader和Writer作爲基類


按照流的角色分,可以分爲節點流和處理流
節點流:可以從/向一個特定的IO設備(如磁盤、網絡)讀\寫數據的流,字節流也通常稱爲低級流。字節流來進行數據的輸入\輸出時,程序直接連接到實際的數據源,和實際的輸入\輸出節點連接
處理流:處理流則用於對一個已存在的流進行連接或封裝,通過封裝後的流來實現數據讀\寫功能,處理流也被稱爲高級流。當使用處理流來進行輸入\輸出時,程序並不會直接連接到實際的數據源,沒有和實際的輸入\輸出節點連接。
使用處理流的好處:只要使用相同的處理流,程序就可以採用完全相同的輸入\輸出代碼來訪問不同的數據源,隨着處理流鎖包裝的節點流的改變,程序實際所訪問的數據源也相應的發生改變
備註:實際上Java使用處理流來包裝節點流是一種典型的裝飾器設計模式,通過使用處理流來包裝不同的節點流,即可消除不同節點流的實現差異,也可以提供更方便的方法來完成輸入\輸出功能,因此處理流也被稱爲包裝流
二、流的概念模型
java的IO流的40多個類都是從4個抽象基類派生出來的
-->InputStream和Reader:所有輸入流的基類,前者是字節輸入流,後者是字符輸入流
-->OutputStream和Writer:所有輸出流的基類,前者是字節輸出流,後者是字符輸出流


Java的處理流模型則體現了Java輸入\輸出流設計的靈活性。
處理流的功能主要體現在兩個方面:
-->性能的提高:主要以增加緩衝流的方式來提高輸入\輸出的效率
-->操作的便捷:處理流可能提供了系列便捷的方法來一次輸入\輸出大批量的內容,而不是輸入\輸出一個或多個(字節\字符)
三、字節流和字符流
1、InputStream和Reader
InputStream和Reader是所有輸入流的基類,它們都是兩個抽象類,本身並不能創建實例來執行輸入,但它們將成爲所有輸入流的模板,所有它們的方法是所有輸入流都可以使用的方法
在InputStream裏包含如下三個方法
-->int read():從輸入流中讀取單個字節,返回所讀取的字節數據(字節數據可直接轉換爲int類型)
-->int read(byte[] b):從輸入流中讀取最多b.length個字節的數據,並將其儲存在字節數組b中,返回實際讀取的字節數
-->int read(byte[] b,int off,int len):從輸入流中讀取最多len個字節的數據,並將其儲存在數組b中,放入b數組中時,並不是從數組的起點開始,而是從offset位置開始,返回實際讀取的字節數
在Reader裏包含如下三個方法
-->int read():從輸入流中讀取單個字符,返回所讀取的字符數據(字符數據可直接轉換爲int類型)
-->int read(char[] cbuf):從輸入流中讀取最多cbuf.length個字符的數據,並將其儲存在字符數組cbuf中,返回實際讀取的字符數
-->int read(char[] cbuf,int off,int len):從輸入流中讀取最多len個字符的數據,並將其儲存在數組cbuf中,放入cbuf數組中時,並不是從數組的起點開始,而是從offset位置開始,返回實際讀取的字符數
InputStream和Reader都是抽象類,本身不能創建實例,但他們分別有一個用於讀取文件的輸入流:FileInputStream和FileReader,他們都是節點流--會直接和指定文件關聯。

eg:使用FileInputStream來讀取自身的效果

public class FileInputStreamTest
{
	public static void main(String[] args) throws IOException
	{
		//創建字節輸入流
		FileInputStream fis = null;
		try
		{
		
		fis = new FileInputStream("FileInputStreamTest.java");
		//創建一個長度爲1024的字節數組
		byte[] bbuf = new byte[1024];
		//用於保存實際讀取的字節數
		int hasRead = 0;
		while((hasRead = fis.read(bbuf)) > 0)
		{
		//取出字節數組內的字節,並將字節數組轉換成字符串輸入!
		System.out.print(new String(bbuf,0,hasRead));
		}
		}
		actch (IOException ioe)
		{
			ioe.printStackTrace();
		}
		//關閉文件輸入流
		finally
		{
			fis.close();
		}

	}
}

eg:使用FileReader來讀取自身的效果
public class FileReaderTest
{
	public static void main(String[] args) throws IOException
	{
		//創建字符輸入流
		FileReader fr = null;
		try
		{
		
		fr = new FileReader("FileReaderTest.java");
		//創建一個長度爲32的字符數組
		char[] cbuf = new char[32];
		//用於保存實際讀取的字符數
		int hasRead = 0;
		while((hasRead = fr.read(cbuf)) > 0)
		{
		//取出字節數組內的字節,並將字節數組轉換成字符串輸入!
		System.out.print(new String(cbuf,0,hasRead));
		}
		}
		actch (IOException ioe)
		{
			ioe.printStackTrace();
		}
		//關閉文件輸入流
		finally
		{
		//使用finally塊來關閉文件輸入流
			if(fr !=null)
			{
			fr.close();
			}
		}

	}
}
除此之外,InputStream和Reader還支持如下幾個方法來移動記錄指針
-->void mark(int readAheadLimit):在記錄指針當前位置記錄一個標記(mark)
-->boolean markSupported():判斷此輸入流是否支持mark()操作,即是否支持記錄標記
-->long skip(long n):記錄指針向前移動n個字節/字符
2、OutputStream和Writer
OutputStream和Writer提供瞭如下三種方法
-->void writer(int c):講指定的字節/字符輸出到輸出流中,其中c即可代表字節,也可以代表字符
-->void writer(byte[]/char[] buf):講字節數組/字符數組中的數據輸入到指定輸出流中
-->void writer(byte[]/char[],int off,int len):講字節數組/字符數組中從off位置開始,長度爲len的字節/字符輸出到輸出流中
因爲字符流字節以字符爲操作單位,所以Writer可以用字符串來代替字符數組,即以String對象作爲參數。
Writer裏還包含如下兩個方法
-->void write(String str):將str字符串裏包含的字符輸出到指定輸出流中
-->void write(String str,int off,int len):將str字符串裏從off位置開始,長度爲len的字符輸出到指定輸出流中

eg:使用FileInputStream來執行輸入,並用FileOutputStream來執行輸出,用以實現複製FileOutputStream.java文件功能
public void FileOutputStreamTest
{
	public static void main(String[] args) throw IOException
	{
		FileOutputStream fos = null;
		FileInputStream fis = null;
		try
		{
		//創建字節輸入流
			fis = new FileInputStream("FileOutputStreamTest.java");
			//創建字節輸出流
			fos = new FileOutputStream("newFile.txt");
			byte[] bbuf = new byte[1024];
			int hasRead = 0;
			while((hasRead = fis.read(bbuf)) > 0)
			{
			//沒讀取一次,即寫入文件輸出流,讀了多少,就寫多少
			fos.write(bbuf,0,hasRead);
			}
		}
		actch (IOException ioe)
		{
			ioe.printStackTrace();
		}
		//關閉文件輸入流
		finally
		{
			//使用finally塊來關閉文件輸入流
			if(fis !=null)
			{
			   fis.close();
			}
			//使用finally塊來關閉文件輸出流
			if(fos !=null)
			{
			   fos.close();
			}
		}

	}
}

eg:如果我們希望直接輸出字符串內容,則使用Writer會有更好的效果

public void FileWriterTest
{
	public static void main(String[] args) throw IOException
	{
		FileWriter fw = null;
		try
		{
			//創建字符輸出流
			fw = new FileWriter("poem.txt");
			fw.write("服務器在偷懶\r\n");
			fw.write("偷懶還這麼任性\r\n");
			fw.write("服務器在偷懶,大家沒有火車票\r\n");
		}
		actch (IOException ioe)
		{
			ioe.printStackTrace();
		}
		finally
		{
			if(fw != null)
			{
			fw.close();
			}
		}
	}

}

上面程序在輸出字符串內容時,字符串內容的最後是\r\n,這是Windows平臺的換行符,通過這種方式就可以讓輸出的內容換行;如果是Unix/Linux/BSD等平臺,只要使用\n就可以作爲換行符


四、處理流的用法
處理流可以隱藏底層設備上節點流的差異,並對外提供更加方便的輸入\輸出方法,讓程序員需要關係高級流的操作
處理流的典型思路:使用處理流來包裝節點流,程序通過處理流來執行輸入\輸出功能,讓節點流與底層的I\O設備、文件交互
處理流的識別:只要流的構造器不是一個物理節點流,而是已經存在的流,那麼這種流一定是處理流;而所有節點流都是直接以物理IO節點作爲構造器的

eg:下面程序使用PrintStream處理流來包裝InputStream,使用處理流後的輸出流在輸出時將更加方便
public class PrintStreamTest{
	public static void main(String[] args) throws IOException {
		PrintStream ps = null;
		try
		{
			//創建一個節點輸出流:FileOutputStream
			FileOutputStream fos = new FileOutputStream("text.txt");
			//以PrintStream來包裝FileOutputStream輸出流
			ps = new PrintStream(fos);
			//使用PrintStream執行輸出
			ps.println("普通子字符串");
			ps.println(new PrintStreamTest());
		}
		catch(IOException ioe)
		{
			ioe.printStackTrace(ps);
		}
		finally
		{
			
			ps.close();
		}
	}

}
由於PrintStream類的輸出功能非常強大,通常如果我們需要輸出文本內容,都應該講輸出流包裝成PrintStram後進行輸出。
處理流的使用非常簡單,通常只需要在創建處理流時傳入一個節點流構造參數即可,這樣創建的處理流就是包裝了該節點流的處理流
當我們使用處理流來包裝底層節點流之後,關閉輸入\輸出流資源時,只要關閉最上層的處理流即可。關閉最上層的處理流時,系統會自動關閉該處理流包裝的節點流
五、輸入/輸出流體系
以數組爲物理節點的節點流除了在創建節點流時需要傳入一個字節數組或者字符數組之外,用法上與文件節點流相似。
字符流還可以使用字符串作爲物理節點,用於實現從字符串內讀取內容,或將內容寫入字符串(實際上用StringBuffer充當了字符串)的功能

eg:下面程序示範了使用字符串作爲物理節點的字符輸入\輸出流的用法
public class StringNodeTest{
	public static void main(String[] args) throws IOException {
		String src ="從明天起,做一個幸福的人\r\n"
				+"餵馬,劈柴,周由世界\r\n"
				+"告訴他們我是幸福的\r\n";
		StringReader sr = new StringReader(src);
		char[] buffer = new char[32];
		int hasRead = 0;
		try
		{
			while((hasRead = sr.read(buffer)) > 0){
				System.out.print(new String(buffer,0,hasRead));
			}
		}
		catch (IOException ioe) {
			ioe.printStackTrace();
		}
		finally
		{
			sr.close();
		}
		//創建StringWrite時,實際上以一個StringBuffer作爲輸出節點
		//下面指定的20就是StringBuffer的初始長度
		StringWriter sw = new StringWriter(20);
		//調用StringWriter的方法執行輸出
		sw.write("我遠離了大海,\r\n");
		sw.write("我遠離了小溪,\r\n");
		sw.write("我遠離了海流,\r\n");
		sw.write("我遠離了湖泊,\r\n");
		System.out.println("-----下面是sw的字符串節點裏的內容");
		//使用toString方法返回StringWriter的字符串節點的內容
		System.out.println(sw.toString());
	}

}
上面程序與前面使用FileReader和FileWriter的程序基本類似,只是在創建StringReader和StringWriter對象時傳入的是字符串節點,而不是文件節點。由於String是不可變的字符串對象,所以StringWriter使用StringBuffer作爲輸出節點
六、轉換流
輸入\輸出流體系裏提供了兩個轉換流,這兩個轉換流用於實現將字節流轉換成字符流,
其中InputStreamReader將字節流轉換成字符輸入流
其中OutputStreamWriter將字節輸出流轉換成字符輸出流
沒有字符流轉換成字節流的原因:字節流比字符流使用範圍更廣,但字符流比字節流操作更方便。我們知道已有的字節流內容都是文本,那麼把它轉換成字符流來處理就會更方便一些。所以Java只提供了將字節流轉換成字符流的轉換流,沒有提供將字符流轉換成字節流的轉換流。
下面以獲取鍵盤輸入爲例來介紹轉換流的用法,Java使用System.in代表標準輸入,即鍵盤輸入,但這個標準輸入流就是InputStream類的實例,使用不太方便而且我們知道鍵盤輸入內容都是文本內容,所以我們可以使用InputStreamReader將其轉換成字符輸入流,普通Reader讀取輸入內容時依然不太方便,我們可以將普通Reader再次包裝成BuffedReader,利用BuffedReader的readLine方法可以一次讀取一行內容。

eg:
public class PrintStreamTest{
	public static void main(String[] args) throws IOException {
		
		BufferedReader br = null;
		try
		{
			//將System.in對象轉換成Reader對象
			InputStreamReader reader = new InputStreamReader(System.in);
			//將普通的Reader包裝成BufferedReader
			br = new BufferedReader(reader);
			String buffer = null;
			//採用循環一行一行的讀取數據
			while((buffer = br.readLine()) !=null)
			{
				//如果讀取的字符串爲“exit”,程序退出
				if(buffer.equals("exit"))
				{
					System.exit(1);
				}
				//打印讀取的內容
				System.out.println("讀取內容爲:"+buffer);
			}
		}
		catch (IOException ioe) {
			ioe.printStackTrace();
		}
		finally
		{
			br.close();
		}
	}
}

上面程序中將System.in包裝成BufferedReader,BufferedReader流具有一個緩衝的功能,它可以一次讀取一行文本--以換行符爲標誌,如果它沒有讀到換行符,則程序阻塞,等到讀到換行符爲止。
由於bufferedReader具有一個readLine方法,可以非常方便地一次讀取一行內容,所有經常把讀取文本內容的輸入流包裝成BufferedReader,用以方便地讀取輸入流的文本內容。

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