1. 程式人生 > >Java基礎(14):IO流—理解I/0概念和掌握相關類的作用(附有操作程式碼)

Java基礎(14):IO流—理解I/0概念和掌握相關類的作用(附有操作程式碼)

在Java中如果要進行輸出和輸入操作,就需要使用到IO流,例如第一次寫的語句System.out.println("hello,world")就是一個典型的輸出流。IO流是Java的重點知識,除了要理解輸入與輸出的概念,還需要多次編寫程式碼才能更好的理解。IO體系中涉及到的類很多,但核心體系就是由File、 InputStream 、OutputStream、Reader、Writer和Serializable(介面)組成的這些類會在下半部分的程式碼實踐中詳細說明。

I/O流基礎概念篇

按照流的方向可以分為輸入流(InputStream)與輸出流(OuputStream):

  • 輸入流:只能讀取資料,不能寫入資料。
  • 輸出流:只能寫入資料,不能讀取資料。

因為程式是執行在記憶體中,所以從記憶體的角度來理解輸入輸出概念,貼上一張圖。


但輸入和輸出是相對的,程式把資料寫入本地檔案,對於程式來說是輸出流,而本地檔案則是獲取了資料,對本地檔案來說就是輸入流。本地檔案寫入資料到Java程式中也是輸出流,對於Java程式來說就是輸入流。

所以需要從使用的角度來區分,讀取資料就是輸入流,寫入資料就是輸出流。

按照處理的資料單位可以分為位元組流和字元流:

  •  位元組流:操作的資料單元是8位的位元組。以InputStream、OutputStream作為抽象基類。
  •  字元流:操作的資料單元是字元。以Writer、Reader作為抽象基類。
  •  位元組流可以處理所有資料檔案,如果處理的是純文字資料,就使用字元流。

按照流的作用可分為節點流和處理流:

節點流:程式直接與資料來源連線,和實際的輸入/輸出節點連線。

處理流:對節點流進行包裝,擴充套件原來的功能,通過處理流執行輸入/輸出操作。這涉及到裝飾者模式。

IO流中的資料來源有三類:

  • 基於磁碟檔案:FileInputStream、FileOutputSteam、FileReader、FileWriter
  • 基於記憶體:ByteArrayInputStream ByteArrayOutputStream(ps:位元組陣列都是在記憶體中產生的)
  • 基於網路:SocketInputStream、SocketOutputStream(ps:網路通訊時傳輸資料)

處理流的作用和分類:

處理流可以隱藏底層裝置上節點流的差異,無需關心資料來源的來源,程式只需要通過處理流執行輸入/輸出操作。處理流是以一個存在的節點流作為構造引數的。通常情況下,都推薦使用處理流來完成輸入/輸出操作。

緩衝流:提供一個緩衝區,能夠提高輸入/輸出的執行效率,減少同節點的頻繁操作。

BufferedInputStream/BufferedOutputStream、BufferedReader/BufferWriter

轉換流:將位元組流轉成字元流。位元組流使用範圍廣,但字元流使用更方便。例如一個位元組流的資料來源是文字內容的話,轉成字元流來處理會更加方便。

InputStreamReader/OutputStreamWriter

列印輸出流:把指定內容列印輸出,根據構造引數中的節點流來決定輸出到何處。

PrintStream :列印輸出位元組資料,使用該類。

PrintWriter : 列印輸出文字資料,使用該類。

放上一張JavaI/O體系的常用類圖片,方便後面程式碼講解。

---------------------------------------------------------------------------------------------------

I/O流實踐

File類: 代表本地磁碟的檔案或是一個目錄路徑,File類只能對檔案本身進行操作,例如判斷是檔案還是目錄,判斷檔案是否存在,建立一個檔案或者資料夾等操作。但不能對檔案儲存的內容進行操作。File類中還有許多方法,這裡不一一概述。一些方法會在接下來的程式碼中使用到。想了解更多方法可以去檢視JDK文件。

介紹完基礎概念之後,開始使用IO流來完成一些功能:

(一)使用位元組流讀取本地檔案內容:

	//使用File物件來獲取資料來源,並讀取其中內容,將其輸出到標準控制檯
	public static void getContent(File file) throws IOException {
		//建立檔案輸入流,資料來源是file
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
		byte[] buf = new byte[1024];//可以快取50位元組
		int len = 0; //讀取後返回的長度
		while((len = bis.read(buf)) != -1) { //長度為-1則讀取完畢,結束迴圈讀取
			//列印快取區的內容,從0開始
			System.out.println(new String(buf,0,len));		
		}
		bis.close();
	}
使用File類獲取資料來源,使用檔案位元組輸入流來讀取該資料來源。如果資料來源是純文字資料的話,使用字元流會更方便,而且效率更高。

(二)使用字元處理流讀取本地檔案內容:

	public static void getContent(String path) throws IOException {
		File f = new File(path);
		//判斷f指向的資料來源是否存在
		if(f.exists()) { 
			//判斷是不是檔案
			if(f.isFile()) {
				//不要向上轉型成Reader,readLine()是子類獨有方法
				BufferedReader br = new BufferedReader(new FileReader(path));
				String s = null;
				while((s = br.readLine()) != null) { //readLine()每次讀取一行
					System.out.println(s);
				}
			}
		}		
	}
該方法比上一個增加了檔案判斷,提高了程式的健壯性。使用了BufferedReader處理流來處理純文字資料,比位元組流更加簡潔方便。如果處理的是純文字資料,強烈推薦字元處理流!!!

(三)使用字元流寫入資料到指定檔案:

public static void main(String[] args) throws IOException {
		//以標準輸入作為掃描來源
		Scanner sc = new Scanner(System.in);
		File f = new File("D:\\reviewIO\\WRITERTest.txt");
		BufferedWriter bw = new BufferedWriter(new FileWriter(f));
		if(!f.exists()) {
			f.createNewFile();
		}
		while(true) {
			String s = sc.nextLine();
			bw.write(s);
			bw.flush();
			if(s.equals("結束") || s.equals("")) {
				System.out.println("寫入資料結束!");
				return;
			}
		}
	}
上面程式使用字元流簡單地實現了寫入檔案的操作,位元組流也差不多,可以自行更改。關於Scanner類,該類是一個掃描類,以System.in作為資料來源。System.in是標準輸入(鍵盤輸入資料),這裡只是先了解一下。

(四)使用轉換流(InputStreamReader/OutputStreamWriter),對寫入資料進行改進:

public static void testConvert(File f) throws IOException {
		if(!f.exists()) {
			f.createNewFile();
		}
		//以System.in作為讀取的資料來源
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		BufferedWriter bw = new BufferedWriter(new FileWriter(f,true)); //允許新增內容,不會清除原來的資料來源內容。
		String s = null;
		while(!(s = br.readLine()).equals("")) {
			bw.write(s);
			bw.newLine();//空一行
		}
		bw.flush();		
		bw.close();
		br.close();
	} 

因為System.in是一個InputStream物件,字元處理流無法直接使用,所以需要使用轉換流將位元組流轉成字元流。然後使用字元輸入處理流的readLine()每次讀取一行,使用newLine()完成換行。

注意點:通常使用IO流寫入檔案時,寫入的資料總會覆蓋原來的資料,這是因為檔案輸出流預設不允許追加內容,所以需要為FileOuputStream、FileWriter的構造引數boolean append 傳入true。 (五)使用位元組流完成檔案拷貝:
//位元組流實現檔案拷貝
	public static String copyFile(String src, String dest) throws IOException, ClassNotFoundException {
		File srcFile = new File(src);//原始檔資料來源
		File desFile = new File(dest);//寫入到目標資料來源
		//資料來源不存在
		if(!srcFile.exists() || !desFile.exists()) {
			throw new ClassNotFoundException("原始檔或者拷貝目標檔案地址不存在!");
		}
		//非檔案型別
		if(!srcFile.isFile() || !desFile.isFile()) {
			return "原始檔或者目標檔案不是檔案型別!";
		}
		InputStream is = null;
		OutputStream os = null;
		byte[] buf = new byte[1024];//快取區
		int len = 0;//讀取長度
		try {
			is = new BufferedInputStream(new FileInputStream(srcFile));//讀取資料來源
			os = new BufferedOutputStream(new FileOutputStream(desFile));//寫入到資料來源			
			while((len = is.read(buf)) != -1) { //讀取長度不為-1,繼續讀取
				os.write(buf); //讀取內容之後馬上寫入目標資料來源
			}
			os.flush();//輸出
			return "檔案拷貝成功!檢視拷貝檔案路徑:" + desFile.getPath();						
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			if(is != null)
				is.close();
			if(os != null)
				os.close();
		}
		return "檔案拷貝失敗";
	}
如果是對檔案進行復制,一般採用位元組流來完成,因為可以作用於一切型別資料。但如果操作的是純文字資料,應該使用字元流。

(六)使用列印流來完成寫入資料操作:

		//輸出內容的檔案資料來源
		File f = new File("D:\\reviewIO\\PW.java");
		PrintWriter pw = new PrintWriter(f);
		//把指定內容列印至資料來源中
		pw.println("AAAAAAAAA");
		pw.println("BBBBBBBBB");
		pw.println("CCCCCCCCC");
		pw.flush();
		System.out.println("使用PrintWriter寫入資料完成");
		System.out.println("==========讀取寫入的資料==========");
		BufferedReader br = new BufferedReader(new FileReader(f));
		String s = null;
		StringBuilder sb = new StringBuilder();//一個可變字串
		while((s = br.readLine()) != null) {
			sb.append(s); //把讀取的字串組合起來
		}
		System.out.println(sb);
		br.close();
		pw.close();
如上面程式碼所示,列印流可以完成寫入資料操作。而且列印流比常規輸出流功能更加強大,例如PrintWriter可以指定輸出文字使用何種字符集。也可以在構造引數中指定自動重新整理。如果不想覆蓋原來的資料,使用該類的append()方法,就會在檔案尾部新增內容。

(七)使用列印流來完成文字拷貝:

//使用列印流PrintStream來完成檔案拷貝
	public static void copyFile(File src, File dest) throws Exception {
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest));
		PrintStream ps = new PrintStream(bos,true);
		byte[] buf = new byte[1024];
		int len = 0;
		while((len = bis.read(buf)) != -1) {
			ps.write(buf);
		}
		ps.close();
		bos.close();
		bis.close();
	}	

上面程式碼使用列印流來簡單實現了檔案拷貝操作,利用了列印流建構函式的自動重新整理。使用列印流還有一個好處就是不需要檢查異常。

常用的IO流都已實現,由於篇幅過長,剩餘的知識點會放在下一章節中。