Java中複雜的I/O流

最近在學Java中的I/O流操作。這“流”的世界,怎一個高深了得。。斷斷續續學了近半個月,在此就想對I/O流操作做一個簡單的梳理工作。。。。

一、I/O流操作的分類
     要想對一個複雜體系有一個全面的認識,就得試着將體系按照各種不同的標準進行分類。
     I/O流按照處理單元的不同可分爲字符流和字節流,按照方向的不同可以分爲輸入流和輸出流。其中對輸入流按照來源的不同,分爲鍵盤和磁盤文件;輸入流按照目的地的不同可以分爲控制檯和磁盤文件。

二、流操作的示例
    本文將就四種情況進行示例:1)從鍵盤讀取;2)從磁盤文件讀取;3)向控制檯輸出;4)向磁盤文件輸出。。。。當然針對每一種情況,都會採用多個流對象實現,以更好地比較相似流對象之間的不同。
    1)從鍵盤讀取
     我們知道要從鍵盤讀取數據,就必須使用系統封裝好的System.in對象。但System.in對象時字節流對象,其方法非常有限。相反,字符流對象很多更實用的方法(尤其是採用緩衝區流BufferedReader),因此可以並且常常考慮將字節流對象轉成字符流對象。以下代碼展示了三種不同的“從鍵盤讀取“,並利用System.out直接向控制檯輸出的示例:
  • 直接使用InputStream流接收System.in,使用最樸素的read(byte[] buf)方法,該方法每次讀入一行並存放到字符數組中,如ReadFromKeyBoard1.java所示
  • 使用InputStreamReader將InputStream流封裝成字符流,然後使用字符流中的方法,如ReadFromKeyBoard2.java所示
  • 使用BufferedReader將字符流封裝成緩衝輸入流,提高效率,如ReadFromKeyBoard3.java所示
/*
該示例直接使用InputStream流
*/
import java.io.*;
class ReadFromKeyBoard1 
{
	public static void main(String[] args) throws IOException
	{   
		//InputStream儘管是抽象類,不可以new出對象,但可以接收一個對象的引用
		InputStream is=System.in;
		byte[] buf =new byte[1024];
		int num;
		while(true)
		{
			num=is.read(buf);       
			//之所以要減去2,是因爲Windows下的換行回車包含兩個字節
			String str=new String(buf,0,num-2);
			if(str.equals("over"))
				break;
			else
				System.out.println(str);
		}
		is.close();
	}
}
/*
該示例直接使用InputStreamReader流封裝原始的字節流,
使其成爲字符流,使用字符流的方法
*/
import java.io.*;
class ReadFromKeyBoard2 
{
	public static void main(String[] args) throws IOException
	{
		InputStreamReader isr= new InputStreamReader(System.in);    
		char[] buf =new char[1024];
		int num;
		while(true)
		{
			num=isr.read(buf);       
			String str=new String(buf,0,num-2);
			if(str.equals("over"))
				break;
			else
				System.out.println(str);
		}
		isr.close();
	}
}

/*
該示例直接使用InputStreamReader流封裝原始的字節流,
再用BufferedReader流封裝,最後使用BufferedReader的方法
尤其是readLine()方法
*/
import java.io.*;
class ReadFromKeyBoard3 
{
	public static void main(String[] args) throws IOException
	{
		//該方法常是獲取鍵盤輸入的最常用方法
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));    
		String str=null;
		while(true)
		{    
			str=br.readLine();
			if(str.equals("over"))
				break;
			else
				System.out.println(str);
		}
		br.close();
	}
}

    2) 從磁盤文件讀取
    直接從磁盤文件讀取,可以分爲兩種方式:按字符流讀取和按字節流讀取(其中字節流又可以轉變爲字符流)。以下是幾種從磁盤文件中讀取數據,然後直接打印到控制檯上的示例:
  • 直接使用字符輸入流FileReader從磁盤文件中讀取數據,並且逐個讀取字符、逐個打印字符,如ReadFromFile1.java所示
  • 直接使用字符輸入流FileReader從磁盤文件中讀取數據,讀取出所有字符到一個數組中再打印,如ReadFromFile2.java所示
  • 使用BufferedReader封裝FileReader流,以提升性能,如ReadFromFile3.java所示
  • 直接使用字節輸入流FileInputStream從文件中讀取數據,此處也可以對字節輸入流再封裝成字符流,衍生出另外三種方法(如小節1中所示)。。。篇幅限制,我們只演示最樸素的一種,如ReadFromFile4.java所示
  • 對System.in重定向到磁盤文件,如ReadFromFile5.java所示
/*
直接按字符流從C盤的ruirui.txt文件中讀取數據,
然後打印到控制檯上
*/

import java.io.*;

class ReadFromFile1 
{
	public static void main(String[] args) throws IOException
	{
		FileReader fr=new FileReader("C:\\ruirui.txt");
		int num;
		while((num=fr.read())!=-1)
		{
			if(num=='\n')
				continue;
			else if(num=='\r')
				System.out.println();//讀入換行符就換行
			else
			{
				System.out.print((char)num);
			}
		}	
		fr.close();
	}
}
/*
按字符流從C盤的ruirui.txt文件中讀取數據,
然後打印到控制檯上.
和ReadFromFile1不同的是其採用read(char[])的方法
*/

import java.io.*;

class ReadFromFile2 
{
	public static void main(String[] args) throws IOException
	{
		FileReader fr=new FileReader("C:\\ruirui.txt");
		char[] buf= new char[1024];
		int num;//記錄讀取的字符個數
		num=fr.read(buf);//讀取所有的字符
		System.out.print(new String(buf,0,num));
		fr.close();
	}
}

/*
按字符流從C盤的ruirui.txt文件中讀取數據,
然後打印到控制檯上,並使用BufferedReader以將提高性能
使用BufferedReader中的readLine()方法
*/

import java.io.*;

class ReadFromFile3 
{
	public static void main(String[] args) throws IOException
	{
		FileReader fr=new FileReader("C:\\ruirui.txt");
		BufferedReader br =new BufferedReader(fr);
		String str;
		while((str=br.readLine())!=null)
		{
			System.out.println(str);
		}
		
		fr.close();
	}
}

/*
按字節流從C盤的ruirui.txt文件中讀取數據,
然後打印到控制檯上
和字節流不同的是,只能有一種方法(ReadFromFile1和
ReadFromFile2是字符流的兩種方法)。。。因爲只讀取
一個字節將無法打印字符。。
當然也有一種補救的方法,讀取兩個字節後再打印,
不過過於複雜。。。。

同理,這裏如果將字節流封裝成字符流,或將字符流再封裝成緩衝區
又可以演變出兩種方法。。爲節省篇幅,此處省略。。。
*/

import java.io.*;

class ReadFromFile4 
{
	public static void main(String[] args) throws IOException
	{
		FileInputStream fis=new FileInputStream("C:\\ruirui.txt");
		byte[] buf=new byte[1024];
		int num;
		num=fis.read(buf);
		System.out.print(new String(buf,0,num));
	}
}

/*
考慮:
對System.in流進行重新設置,使其指向磁盤文件。。。
此處又可以延伸出三個方法,
爲節省篇幅,此處只演示一個方法

*/

import java.io.*;

class ReadFromFile5 
{
	public static void main(String[] args) throws IOException
	{
		System.setIn(new FileInputStream("C:\\ruirui.txt"));
		BufferedReader bd=new BufferedReader(new InputStreamReader(System.in));
		String str;
		while(true)
		{
			str=bd.readLine();
			if(str==null)
				break;
			System.out.println(str);
		}
		bd.close();
	}
}


 
    3)向控制檯上輸出
         由於控制檯System.out是字節輸出流OutputStream的子類,因此可以
  •  直接使用字節輸出流OutputStream接收對象,如WriteToScreen1.java所示
  • 將字節輸出流OutputStream封裝成字符輸出流OutputStreamWriter,如WriteToScreen2.java所示
  • 將字符輸出流OutputStreamWriter封裝成緩衝字符流BufferedWriter,如WriteToScreen3.java所示
  • 使用PrintStream接收對象,然後使用Print方法,如WriteToScreen4.java所示
/*
要向控制檯輸出,必須對System.out進行操作。。
但是System.out也是字節流,一般而言只能直接
使用字節流所擁有的方法。。但其又是特殊的字節流
對象(PrintStream類的對象),所以可以具備一些
特殊的方法,
WriteToScreen1演示的是使用最基本的字節輸出流
OutputStream中的方法,將C:\\ruirui.txt中的字符
打印到控制檯上
*/


import java.io.*;

class WriteToScreen1 
{
	public static void main(String[] args) throws IOException
	{
		OutputStream os=System.out;
		BufferedReader br =new BufferedReader(new FileReader("C:\\ruirui.txt"));
		String str=null;
		while(true)
		{
			str=br.readLine();
			if(str==null)
				break;
			str=str+"\n\r";   //打印換行符
			os.write(str.getBytes());//和WriteToScreen2的唯一不同,Write方法中需要
						//轉爲字節數組
			os.flush();
		}
		os.close();
		br.close();
	}
}


 

/*
同樣地,我們可以講字節流對象System.out封裝成
字符流對象從而使用字符流對象中的方法
*/


import java.io.*;

class WriteToScreen2 
{
	public static void main(String[] args) throws IOException
	{
		OutputStream os=System.out;
		OutputStreamWriter osw=new OutputStreamWriter(os);
		BufferedReader br =new BufferedReader(new FileReader("C:\\ruirui.txt"));
		String str=null;
		while(true)
		{
			str=br.readLine();
			if(str==null)
				break;
			osw.write(str+"\n\r");
			osw.flush();	//千萬不可忘記刷新
		}
		os.close();
		osw.close();
		br.close();
	}
}


 

/*
爲使得“換行符”的打印能夠跨平臺,我們考慮
使用BufferedWriter中的newLine()方法,
爲此必須先將字節流對象封裝成字符流對象,
再用BufferedReader封裝起來
*/


import java.io.*;

class WriteToScreen3 
{
	public static void main(String[] args) throws IOException
	{
		OutputStream os=System.out;
		OutputStreamWriter osw=new OutputStreamWriter(os);
		BufferedWriter bw=new BufferedWriter(osw);
		//這裏其實可以一步到位:
		//BufferedWriter bw =new BufferedWriter(new OutputStreamWriter(System.out));

		BufferedReader br =new BufferedReader(new FileReader("C:\\ruirui.txt"));
		String str=null;
		while(true)
		{
			str=br.readLine();
			if(str==null)
				break;
			bw.write(str);
			bw.newLine();	//該換行方法可以跨平臺
			bw.flush();     //不要忘記刷新
		}
		os.close();
		osw.close();
		bw.close();
		br.close();
	}
}


 

/*
我們發現System.out不只是OutputStream類的對象,
其更是子類PrintStream的對象。。。
PrintStream是一種特殊的流,其爲字節流,卻擁有
許多實用的方法(甚至比字符流還要多),比如說
println方法。。。
*/

import java.io.*;

class WriteToScreen4
{
	public static void main(String[] args) throws IOException
	{
		PrintStream ps=System.out;
		BufferedReader br =new BufferedReader(new FileReader("C:\\ruirui.txt"));
		String str=null;
		while(true)
		{
			str=br.readLine();
			if(str==null)
				break;
			ps.println(str);//此處相當於直接調用System.out.println(str)
		}
		ps.close();
		br.close();
	}
}


 

    4)向磁盤文件中輸出

        可以採取字符流或者字節流向磁盤文件中進行輸出操作,其中字節流又可以封裝成字符流,而且可以將System.out重定向到磁盤文件。。。所以有以下幾種方式:

  • 直接採用字符輸出流FileWriter,如WriteToFile1.java所示
  • 帶有緩衝區的字符輸出流BufferedWriter,如WriteToFile2.java所示
  • 直接採用字節輸出流FileOutputStream,如WriteToFile3.java所示
  • 將System.out重定向到磁盤文件,並採用OutputStream接收,使用最樸素的OutputStream中的Write方法,如WriteToFile4.java所示
  • 將System.out重定向到磁盤文件,並採用PrintStream接收,使用PrinStream中高級的Print或println方法,如WriteToFile5.java所示
/*
我們仍然採用緩衝區字符流的方式從C:\\ruirui.txt中讀取數據
存儲到C:\\haoxiaoya.txt中。。
同讀取類似,存儲到磁盤文件可以用字符流也可以用字節流,其中字節流
可以封裝成字符流,或者對System.out進行重新的定義
*/

import java.io.*;

class WriteToFile1 //採用字符流的直接寫入
{
	public static void main(String[] args) throws IOException
	{	
		BufferedReader br =new BufferedReader(new FileReader("C:\\ruirui.txt"));
		String str=null;
		FileWriter fw=new FileWriter("C:\\haoxiaoya.txt",true);

		while(true)
		{
			str=br.readLine();
			if(str==null)
				break;
			fw.write(str+"\r\n");
		}
		fw.close();
		br.close();
	}
}


 

/*
採用字符流直接寫入,但是封裝成緩衝區對象
從而使用可以跨平臺的“換行符”。。。

需要注意的是,由於BufferedReader的readLine()方法
在讀入數據時會忽略行末的“換行符”,所以在寫入的時候
需要重新添加。。。
而FileReader的read(char[] buf)方法會將換行符也讀取
出來,從而不需要添加新的換行符。。。該示例在
WriteToFile3中使用。。。

*/

import java.io.*;

class WriteToFile2 //採用字符流的直接寫入,並封裝成緩衝區對象
{
	public static void main(String[] args) throws IOException
	{	
		BufferedReader br =new BufferedReader(new FileReader("C:\\ruirui.txt"));
		String str=null;
		FileWriter fw=new FileWriter("C:\\haoxiaoya.txt",true);
		//這裏的true表示在原來的文件末尾添加
		BufferedWriter bw=new BufferedWriter(fw);
		while(true)
		{
			str=br.readLine();
			if(str==null)
				break;
			bw.write(str);
			bw.newLine();
			bw.flush();
		}
		fw.close();//需要提醒的是,在緩衝字節流(BufferedWriter或BufferReader)關閉之前,
					//若還未進行刷新操作(如本例中的bw.flush()),不可直接關閉緩衝字節流中封裝的流對象
					//也就是說,若註釋掉bw.flush(),程序是會報錯的,因爲那樣的flush操作會等到不可以
					//bw.close()關閉時進行,在此之前關閉所封裝的流對象(如本例中的fw對象)
					//但如果將bw.flush()和fw.close()同時註釋掉,那麼bw的刷新和fw的關閉都將由
					//bw在close中自動進行,將不會產生差錯
		bw.close();
		br.close();
	}
}


 

/*
需要注意的是,由於BufferedReader的readLine()方法
在讀入數據時會忽略行末的“換行符”,所以在寫入的時候
需要重新添加。。。
而FileReader的read(char[] buf)方法會將換行符也讀取
出來,從而不需要添加新的換行符。。。該示例在
WriteToFile3中使用。。。

在WriteToFile3中我們使用FileReader流對象中的
read(char[] buf)的方法讀取文件,然後通過字節流
直接寫入到C:\\haoxiaoya.txt中
此處在寫入時,也可以將字節流封裝成字符流,並且可以
封裝成字符流緩衝區。。。。可以衍生出兩種新的方法,
但此處爲節省篇幅,略去

*/

import java.io.*;

class WriteToFile3 //採用字節流的直接寫入
{
	public static void main(String[] args) throws IOException
	{	
//		BufferedReader br =new BufferedReader(new FileReader("C:\\ruirui.txt"));
//		String str=null;
//		此處使用FileReader字符流,而不採用緩衝區
		FileReader fr=new FileReader("C:\\ruirui.txt");
		char[] buf=new char[1024];
		int num=0;

		FileOutputStream os=new FileOutputStream("C:\\haoxiaoya.txt");

		num=fr.read(buf);

		os.write(new String(buf).getBytes(),0,num);
		//因爲FileReader讀出的是字符數組,而FileOutputStream只接受字節數組
		//所以需要做轉換
		//不用寫入換行符,因爲FileReader的read(char[] buf)將會讀取源文件中的換行符
		os.close();
		fr.close();
	}
}


 

/*
仍然使用字符流FileReader的read(char[] buf)方法讀入
但在寫出時,我們採用對System.out進行重定向

在對System.out這個字節流對象進行重定向時,
也可以將其再包裝爲字符流對象,以及字符流緩衝區對象。
衍生出另外兩種方法,爲節省篇幅,省去。。
甚至可以採用System.out的print方法。。。
如WriteToFile5所示
*/

import java.io.*;

class WriteToFile4 //採用System.out進行重定向
{
	public static void main(String[] args) throws IOException
	{	
		FileReader fr=new FileReader("C:\\ruirui.txt");
		char[] buf=new char[1024];
		int num=0;
		
		System.setOut(new PrintStream("C:\\haoxiaoya.txt"));

		OutputStream fos=System.out;

		num=fr.read(buf);

		fos.write(new String(buf).getBytes(),0,num);
		//因爲FileReader讀出的是字符數組,而OutputStream只接受字節數組
		//所以需要做轉換
		//不用寫入換行符,因爲FileReader的read(char[] buf)將會讀取源文件中的換行符
		fos.close();
		fr.close();
	}
}


 

/*
仍然使用字符流FileReader的read(char[] buf)方法讀入
但在寫出時,我們採用對System.out進行重定向

甚至可以採用System.out的print方法。。。
如WriteToFile5所示
*/

import java.io.*;

class WriteToFile5 //採用System.out進行重定向
{
	public static void main(String[] args) throws IOException
	{	
		FileReader fr=new FileReader("C:\\ruirui.txt");
		char[] buf=new char[1024];
		int num=0;
		
		System.setOut(new PrintStream("C:\\haoxiaoya.txt"));

		//使用PrintStream接收對象
		PrintStream ps=System.out;

		num=fr.read(buf);
		//使用PrintStream的print方法
		ps.print(new String(buf,0,num));

		ps.close();
		fr.close();
	}
}


        5)演示一種從鍵盤中輸入字符串,然後直接在控制檯上打印的示例。。。當輸入“over”字符串時,程序結束,如ZhuHangPrint.java所示

/*
本示例演示從鍵盤輸入,並逐行打印。。
當鍵盤輸入“over”字符串時,程序停止
*/

import java.io.*;
class ZhuHangPrint 
{
	public static void main(String[] args) throws IOException
	{
		//使用最常用的鍵盤輸入:BufferedReader流
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		String str;
		while(true)
		{
			str=br.readLine();
			if(str.equals("over"))
				break;
			System.out.println(str);//此處要用“換行打印”println是因爲BufferedReader
					     //的readLine()方法不讀取源中的換行符
		}
		br.close();
	}
}

三、流操作中的小感悟

1.採用BufferedWriter或BufferedReader封裝字符流對象時,緩衝流對象的刷新必須在其內部封裝的字符流關閉之前。。。如下面的代碼就會報錯

	FileWriter fw=new FileWriter("C:\\haoxiaoya.txt");
	BufferedWriter bw=new BufferedWriter(fw);
	bw.write(“ruirui”);
	bw.newLine();
//	bw.flush();
	fw.close();
	bw.close();

代碼中bw對象的刷新只能等待被close時進行,在此之前若關閉其封裝的fw對象,就會出錯。。。但若將fw.close()註釋掉,代碼就會正常執行,因爲那樣的bw.flush();和fw.close();都會在bw.close();中自動執行,系統的內部機制保證了其運行順序的正確性。。這也是WriteToFile2.java中提及的。

 

2.BufferedReader()流對象中的readLine()方法,將不讀取源文件中的換行符,因此當在輸出時需要換行時,需要手動添加“換行”。。。

而FileReader()中的read(char[] buf)將會讀取源文件中的換行符,因此在輸出時不用添加新的“換行”。。。這也是WriteToFile4.java中提及的。

 

3.儘管在流的關閉close操作中會自動進行刷新flush操作,但是我們儘量還是要顯示進行flush操作,以避免一些麻煩,如下面的代碼WriteToScreenDemo.java會沒有輸出現象。。

/*
同樣地,我們可以講字節流對象System.out封裝成
字符流對象從而使用字符流對象中的方法
*/


import java.io.*;

class WriteToScreenDemo 
{
	public static void main(String[] args) throws IOException
	{
		OutputStream os=System.out;
		OutputStreamWriter osw=new OutputStreamWriter(os);
		BufferedReader br =new BufferedReader(new FileReader("C:\\ruirui.txt"));
		String str=null;
		while(true)
		{
			str=br.readLine();
			if(str==null)
				break;
			osw.write(str+"\n\r");
//			osw.flush();	//千萬不可忘記刷新
		}
		os.close();
		osw.close();
		br.close();
	}
}

這是因爲在osw隱式地刷新之前,已經將其封裝的os對象關閉了,所以會沒有輸出現象。。。。這一點和第一點有點類似,所不同的是,第一點中涉及緩衝流BufferedWriter和BufferedReader,錯誤的操作順序會直接報錯;而這一點中錯誤的操作順序只是會產生錯誤的結果(如沒有輸出現象)。這也是WriteToScreen2.java中提及的

發佈了46 篇原創文章 · 獲贊 23 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章