1. 程式人生 > >黑馬程式設計師——Java IO流(一)之IO流概述、字元流、位元組流等

黑馬程式設計師——Java IO流(一)之IO流概述、字元流、位元組流等

-----------android培訓java培訓、java學習型技術部落格、期待與您交流!------------

IO流

一、概述

1.IO流是用來處理裝置之間的資料傳輸。

 2.Java對資料的操作時通過流的方式。

 3.Java用於操作流的物件都在IO包中。

 4.流按操作資料分為兩種:位元組流和字元流。流按流向分為:輸入流和輸出流。

 5.流常用的基類:

  1)位元組流的抽象基類:ImputStream和OutputStream。

  2)字元流的抽象基類:Reader和Writer。

 6.體系架構:

  字元流體系架構:

   Reader:用於讀取字元流的抽象類。

    |--BufferedReader:從字元輸入流中讀取文字,緩衝各個字元,從而實現字元、陣列和行的高效讀取。

      |--LineNumberReader:跟蹤行號的緩衝字元輸入流。

    |--InputStreamReader:轉換流,是位元組流通向字元流的橋樑。

      |--FileReader:用來讀取字元檔案的便捷類。

   Writer:寫入字元流的抽象類。

    |--BufferedWriter:將文字寫入字元輸出流,緩衝各個字元,從而提供單個字元、陣列和字串的高效寫入。

    |--OutputStreamWriter:轉換流,是字元流通向位元組流的橋樑。

      |--FileWriter:用來寫入字元檔案的便捷類。

  位元組流體系架構:

   ImputStream:

此抽象類是表示位元組輸入流的所有類的超類。

    |--FileInputStream:從檔案系統中的某個檔案中獲得輸入位元組, 用於讀取諸如影象資料之類的原始位元組流。

    |--FilterInputStream:

      |--BufferedInputStream:位元組輸入流緩衝區。

   OutputStream:此抽象類是表示輸出位元組流的所有類的超類,輸出流接受輸出位元組並將這些位元組傳送到某個接收器。

    |--FileOutputStream:檔案輸出流是用於將資料寫入 File 或 FileDescriptor 的輸出流,用於寫入諸如影象資料之類的原始位元組的流。

   |--FilterOutputStream:此類是過濾輸出流的所有類的超類。

      |--BufferedOutputStream:該類實現緩衝的輸出流。通過設定這種輸出流,應用程式就可以將各個位元組寫入底層輸出流中,而不必針對每次位元組寫入呼叫底層系統。

      |--PrintStream:為其他輸出流添加了功能,使它們能夠方便地列印各種資料值表示形式。列印的所有字元都使用平臺的預設字元編碼轉換為位元組。PrintStream 永遠不會丟擲 IOException,而是,異常情況僅設定可通過 checkError 方法測試的內部標誌。在需要寫入字元而不是寫入位元組的情況下,應該使用 PrintWriter 類。

 問題思考:位元組流和字元流有什麼不同?

  答:位元組流可以用於進行任何資料型別的操作,而字元流帶編碼表,只能用於進行純文字資料型別的操作。

二、格式示例

 IO異常格式的處理:

import java.io.*;
class  FileWriterDemo2
{
	public static void main(String[] args) 
	{
		//需要將fw引用定義在外部,因為在finally中需要呼叫close方法
		FileWriter fw=null;
		try
		{
			//建立物件的時候,需要定義在try裡面,因為丟擲了IO異常。
			fw=new FileWriter("E:\\heima\\deme2.txt");

			//write方法丟擲了IO異常。
			fw.write("hello world");
		}
		catch (IOException e)
		{
			System.out.println("cacht="+e.toString());
		}
		finally 
		{
			//關閉流資源一定要執行,需定義在finally內部,且close方法丟擲了
			//IO異常,需要進行try處理。
			try
			{
				//需要對fw是否為null進行判斷,因為如果fw為null,則再呼叫close方法
				//發發生NullPoterException異常。如果有多個流資源需要關閉,應進行多次
				//判斷並關閉,不要寫在一個判斷裡。
				if (fw!=null)
				{
					fw.close();
				}
			}
			catch (IOException ex)
			{
				System.out.println(ex.toString());
			}
		}
	}
}

三、字元流

 字元流常用對字元檔案的操作。對於字元流的操作,應熟練掌握以下幾個內容:

 1.在指定目錄下建立一個純文字檔案,並在這個檔案中寫入指定內容:

  示例1:"E:\\heima"的目錄下建立純文字檔案demo.txt,並向該檔案demo.txt寫入內容。

import java.io.*;
class FileWriterDemo 
{
	public static void main(String[] args) throws IOException
	{
		
		FileWriter fw=null;
		try
		{
			//fileWriter物件一被初始化就必須明確被操作的檔案的物件。
			//而且該檔案會被建立到指定目錄下,如果該目錄下已有同名檔案存在,
			//則將被覆蓋。由於建立的目錄可能不存在,因此丟擲了IOException。
			fw=new FileWriter("E:\\heima\\demo.txt");

			//呼叫write方法,將字串寫入到流中。
			fw.write("nihao");

			//重新整理流物件中的緩衝區的資料。將資料刷到目的地中。
			fw.flush();

			//可重複進行寫入資料到檔案中。
			fw.write("hello");
			fw.flush();
		}
		catch (IOException e)
		{
			System.out.println("cacht="+e.toString());
		}
		finally 
		{
			try
			{
				if (fw!=null)
				{
					//關閉流物件,在關閉前會重新整理流一次。關閉後不能再向流中寫入資料,
					//否則將發生IO異常。該動作一定做,因為流在呼叫window資源,需要進行關閉。
					fw.close();
				}
			}
			catch (IOException ex)
			{
				System.out.println(ex.toString());
			}
		}
	}
}

  程式執行後在"E:\\heima"的目錄下的建立了一個Demo.txt檔案,檔案的內容如下圖:


 2.對指定目錄下的一個純文字檔案進行內容續寫:

  示例2:對示例1中建立的檔案demo.txt進行內容續寫。

import java.io.*;
class  FileWriterDemo3
{
	public static void main(String[] args) 
	{
		FileWriter fw=null;
		try
		{
			//傳遞一個true穿引數,代表不覆蓋已有檔案,並在已有檔案末尾處新增。
			//如果沒有已有檔案,則會新建立一個檔案。
			fw=new FileWriter("E:\\heima\\demo2.txt",true);

			//新增內容
			fw.write("zhangsan");
		}
		catch (IOException e)
		{
			System.out.println(e.toString());
		}
		finally
		{	
			try
			{
				if (fw!=null)
				{
					fw.close();
				}
			}
			catch (IOException ex)
			{
				System.out.println(ex.toString());
			}
		}
	}
}

  程式執行後,在示例1中的demo.txt檔案內容中續寫了一些指定內容,截圖如下:


 3.寫入內容的方法總結:

  1)寫入單個字元。

   void write(int c):將int型別的字元c寫入到指定目的物件中。

  2)寫入字元陣列或其中的某一部分。

   void write(char[] cbuf):將字元陣列cbuf中的全部字元寫入到指定物件中。

   void write(char[] cbuf,int off,int len):將字元陣列cbuf中從索引off開始寫入到指定物件,寫入的字元總數為len。

  3)寫入字串或其中的某一部分。

   void write(String str):將整個字串內容寫入到指定物件中。

   void write(String str,int off,int len):將字串str中從索引off開始寫入到指定物件,寫入的字元總數為len。

  注:上述所有寫入的方法都會丟擲IOException異常。 

4.對指定目錄下的一個純文字檔案進行內容讀取:

示例3:對示例2續寫的檔案demo.txt進行內容讀取並列印到控制檯。

import java.io.*;
class FileReaderDemo1 
{
	public static void main(String[] args) 
	{
		FileReader fr=null;
		try
		{
			//建立一個檔案讀取流物件,和指定的檔案demo.txt相關聯。
			//要保證該檔案是存在的,如果不存在則會發生FileNotFoundException異常。
			fr=new FileReader("E:\\heima\\demo.txt");

			int ch=0;

			//read()方法讀取檔案的內容,一次讀一個字元,而且會自動往下讀。
			//迴圈讀取檔案內容,如果讀到檔案末尾則會返回-1,可根據此條件
			//來判斷是否繼續讀取。
			while ((ch=fr.read())!=-1)
			{
				System.out.print((char)ch); //將讀取到int型別資料轉換成字元型別輸出。
			}
		}
		catch (IOException e)
		{
			System.out.println(e.toString());
		}
		finally
		{	
			try
			{
				if (fr!=null)
				{
					fr.close();
				}
			}
			catch (IOException ex)
			{
				System.out.println(ex.toString());
			}
		}
	}

}

  程式執行後的結果如下圖:

5.讀取內容的方式總結:

  1)讀取單個字元。

   int read():返回值為讀取的int型別字元。如果讀取的字元已到達流的末尾,則返回 -1。

  2)將字元讀入陣列或陣列中的某一部分。

   int read(char[] cbuf):將字元讀入陣列。在某個輸入可用、發生 I/O 錯誤或者已到達流的末尾前,此方法一直阻塞。

   int read(char[] cbuf,int offset,int length):將字元讀入字元陣列cbuf中,從索引offset開始儲存,存入的字元個數限制為length。

   注:返回值為實際儲存到字元陣列的個數。如果讀取的字元數已到達流的末尾,則返回 -1。

  3)將字元讀入指定的字元緩衝區。瞭解charBuffer更多用法,參加AIP文件。

   int read(CharBuffer target):返回值為新增到緩衝區的字元數量,如果此字元源位於緩衝區末端,則返回 -1。

 6.字元流緩衝區:

  1)BufferedWriter:字元流寫入緩衝區。

寫入資料的方法有:

   a)寫入單個字元:

    void write(int c):該方法丟擲了IO異常。

   b)寫入字元陣列:

    void write(char[] c):該方法丟擲了IO異常。

   c)寫入字元陣列的某一部分:

    void write(char[] cbuf, int off, int len) :該方法丟擲了IO異常。

   d)寫入字串:

    void write(String str):該方法丟擲了IO異常。

   e)寫入字串的某一部分:

    write(String s, int off, int len):如果 len 引數的值為負數,則不寫入任何字元。這與超類中此方法的規範正好相反,它要求丟擲 IndexOutOfBoundsException。該方法丟擲了IO異常。

   示例4:使用緩衝區技術,在"E:\\heima"的目錄下建立純文字檔案test.txt,並向該檔案test.txt寫入內容。

<pre name="code" class="java">import java.io.*;
class BufferedWriterDemo
{
	public static void main(String[] args) 
	{
		//建立字元流讀取緩衝區引用
		BufferedWriter bufw=null;

		try
		{
			//建立字元流讀取緩衝區物件,並傳入檔案讀取流物件
			bufw=new BufferedWriter(new FileWriter("E:\\heima\\test.txt"));

			//將字串內容寫入到字元流讀取緩衝區中
			bufw.write("nihao");

			//重新整理緩衝區中的資料,在寫入內容不多的情況下,可以不用flush方法,但一定要close方法關閉
			//資源,即將寫入的內容最後關閉的時候一次性刷入檔案中。如果寫入內容很多,應加flush,防止
			//在還沒有重新整理的情況下,出現停電,導致資料丟失。當加了flush後,可以即時將資料刷入檔案中。
			bufw.flush();

			//換行
			bufw.newLine();

			bufw.write("nihao");

			
		}
		catch (IOException e)
		{
			throw new RuntimeException("檔案寫入失敗");
		}
		finally
		{	
			if (bufw!=null)
			{
				try
				{
					bufw.close();
				}
				catch (IOException ex1)
				{
					throw new RuntimeException("寫入關閉失敗");
				}
			}
			
		}
	}

}
  程式執行後在"E:\\heima"的目錄下生成了test.txt檔案,檔案內容如下截圖所示:

 2)BufferedReader:字元流讀取緩衝區。

讀取資料的方法有:

   a)讀取單個字元:

    int read():返回字元的int型別值。如果已到達流末尾,則返回 -1。

   b)將字元讀入陣列:

    int read(char[] cbuf):返回讀入的字元數。如果已到達流的末尾,則返回 -1。在某個輸入可用、發生 I/O 錯誤或者已到達流的末尾前,此方法一直阻塞。

c)將字元讀入陣列的某一部分:

    int read(char[] c,int off,int len):返回讀取的字元數。如果已到達流末尾,則返回 -1。

   d)讀取一個文字行:

    int readLine():返回包含該行內容的字串,不包含任何行終止符。如果已到達流末尾,則返回 null。

   e)試圖將字元讀入指定的字元緩衝區:

    int read(charBuffer target):返回新增到緩衝區的字元數量,如果此字元源位於緩衝區末端,則返回 -1。

   注:上述方法都丟擲了IO異常。

  示例5:使用緩衝區技術,對示例4建立的test.txt檔案進行內容讀取並列印輸出到控制檯。

import java.io.*;
class BufferedReaderDemo
{
	public static void main(String[] args) 
	{
		//建立字元流讀取緩衝區引用
		BufferedReader bufr=null;

		try
		{
			//建立字元流讀取緩衝區物件,並傳入檔案讀取流物件
			bufr=new BufferedReader(new FileReader("E:\\heima\\test.txt"));

			String line=null;

			//呼叫字元流讀取緩衝區物件的readLine方法,如果判斷不為空,則繼續讀下一行
			while ((line=bufr.readLine())!=null)
			{
				System.out.println(line);
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("檔案讀取失敗");
		}
		finally
		{	
			if (bufr!=null)
			{
				try
				{
					bufr.close();
				}
				catch (IOException ex1)
				{
					throw new RuntimeException("讀取關閉失敗");
				}
			}
			
		}
	}

}
  程式執行後的結果如下截圖所示:



 7.裝飾設計模式:

  1)概述

   當想要對已有的物件進行功能增強時,可以定義類,將已有的功能,並提供加強功能。那麼自定義的類就稱為裝飾類。

   裝飾類通常會通過構造方法接受被裝飾的物件,並基於被裝飾的物件的功能,提供更強的功能。

   示例6:對已存在的Person類進行功能增強,並用SuperPerson類描述。

<pre name="code" class="java">class PersonDemo 
{
	public static void main(String[] args) 
	{
		SuperPerson sp=new SuperPerson(new Person());
		sp.superEat();
	}
}
class Person
{
	public void eat()
	{
		System.out.println("吃飯");
	}
}

//裝飾類
class SuperPerson
{
	private Person p;

	SuperPerson(Person p)
	{
		this.p=p;
	}

	//對eat功能進行增強
	public void superEat()
	{
		System.out.println("開胃酒");
		p.eat();
		System.out.println("甜點");

	}
}
  程式執行後的結果如下圖所示:

  

  2)裝飾與繼承的區別:

   繼承結構:    

    MyReader     

     |--MyTextReader      

       |--MyBuffferedTextReader:繼承增強類。     

     |--MyMediaReader      

       |--MyBufferedMediaReader:繼承增強類。     

     |--MyDataReader      

       |--MyBufferedDataReader:繼承增強類。

   裝飾結構:    

    MyReader     

     |--MyTextReader     

     |--MyMediaReader     

     |--MyDataReader     

     |--MyBufferedReader:裝飾類,用於給MyReader子類的功能進行增強。

   區別:裝飾模式比繼承要靈活,避免了繼承體系的臃腫,而且降低了類與類之間的關係。裝飾類因為增強已有物件,具備的功能和已有功能是相同的,只不過提供了更強的功能。所以裝飾類和被裝飾類通常屬於一個體系中的。但裝飾類只能在被裝飾類的父類的基礎上進行增強,而繼承可以在被繼承類的基礎上直接增強。

  3)自定義裝飾類:

   示例7:模擬BufferedReader類。

//自定義裝飾類
class MyBufferedReader extends Reader
{
	private FileReader r;

	MyBufferedReader(FileReader r)
	{
		this.r=r;
	}
	
	//定義讀取行功能
	public String myReadLine() throws IOException
	{
		StringBuilder sb=new StringBuilder();

		int ch=0;

		while ((ch=r.read())!=-1)
		{
			
			if (ch=='\r')
				continue;
			else if (ch=='\n')
				return sb.toString();
			else 
				sb.append((char)ch);
			
		}
		if (sb.length()!=0)
		{
			return sb.toString();
		}
	
		return null;

	}

	//直接用傳進來的子類進行覆蓋,因為該子類一定實現了父類的方法
	public int read(char buf,int off,int len)
	{
		r.read(buf,off,len);
	}
	
	//覆蓋父類的close抽象方法
	public void close()
	{
		r.close();
	}
	
}

 8.LinenumberReader類:

  1)概述

   LineNumberReader是BufferedReader的子類,用於跟蹤行號的緩衝字元輸入流。該類的setLineNumber和getLineNumber方法可以用於設定和獲取行號。

   示例8:對示例4建立的test.txt檔案進行內容讀取並列印輸出到控制檯,且要求列印的內容帶有行號,並要求從100行開始輸出。

import java.io.*;
class LineNumberReaderDemo
{
	public static void main(String[] args) 
	{
		LineNumberReader lnr=null;

		try
		{
			
			lnr=new LineNumberReader(new FileReader("E:\\heima\\test.txt"));

			String line=null;

			//設定行號為100
			lnr.setLineNumber(100);

			while ((line=lnr.readLine())!=null)
			{
				//連同行號一起列印
				System.out.println(lnr.getLineNumber()+"::"+line);
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("檔案讀取失敗");
		}
		finally
		{	
			if (lnr!=null)
			{
				try
				{
					lnr.close();
				}
				catch (IOException ex1)
				{
					throw new RuntimeException("讀取關閉失敗");
				}
			}
			
		}
	}

}
  程式執行後的結果如下圖:


  2)自定義LineNumberReader類:

/*
自定義LineNumberReader類:

思路:
	1.定義MyLineNumberReader類,並繼承BufferedReader。
	2.複寫readLine方法,使其沒讀取一次該方法,行號計數加1。
	3.對外提供設定行號和獲取的方法。
*/

import java.io.*;

//繼承BufferedReader類,可以使類更加簡單,只需定義特有方法即可。
class MyLineNumberReader extends BufferedReader
{
	//定義行號屬性
	private int lineNumber;

	MyLineNumberReader(Reader r)
	{
		super(r);
	}

	public String readLine() throws IOException
	{
		//每呼叫一次行號自增1
		lineNumber++;
		return super.readLine();
	}

	//設定行號
	public void setLineNumber(int lineNumber)
	{
		this.lineNumber=lineNumber;
	}
	
	//獲取行號
	public int getLineNumber()
	{
		return lineNumber;
	}

}

 9.程式碼練習:

  練習1:完成對示例4建立的test.txt檔案的複製。

/*

需求:完成<span style="font-size:14px;">對示例4建立的test.txt檔案</span>的複製。

思路:
	1.在指定目錄下建立一個檔案,用來儲存複製文字內容。
	2.建立一個檔案讀取流物件,與要複製文字檔案相關聯。
	3.將要複製的文字檔案的內容讀取到陣列中。
	4.將陣列中的內容寫入到新建立的檔案中。
*/
import java.io.*;
class FileCopyDemo1
{
	public static void main(String[] args) 
	{
		FileWriter fw=null;
		FileReader fr=null;

		try
		{
			fw=new FileWriter("E:\\heima\\testCopy.txt");
			fr=new FileReader("E:\\heima\\test.txt");
			
			//定義字元陣列用來儲存
			char[] buf=new char[1024];

			int num=0;

			
			while ((num=fr.read(buf))!=-1)
			{
				//將陣列中的內容寫入到新建立的文字檔案中,不需要重新整理。
				fw.write(buf,0,num);
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("檔案讀取失敗");
		}
		finally
		{	
			if (fr!=null)
			{
				try
				{
					fr.close();
				}
				catch (IOException ex1)
				{
					throw new RuntimeException("讀取關閉失敗");
				}
			}
			if (fw!=null)
			{
				try
				{
					fw.close();
				}
				catch (IOException ex2)
				{
					throw new RuntimeException("寫入關閉失敗");
				}
			}
		}
	}

}
  程式執行後在"E:\\heima"的目錄下建立了一個testCopy.txt檔案,該檔案的內容的截圖如下:



  練習2:使用字元流緩衝區技術完成練習1。

/*
思路:
	1.在指定目錄下建立一個檔案,用來儲存複製文字內容。定義一個字元寫入流緩衝區,並與新建立的文字檔案相關聯。
	2.建立一個檔案讀取流物件,與要複製文字檔案相關聯,定義一個字元讀取流緩衝區,並與檔案讀取流物件相關聯。
	3.用readLine方法迴圈取出字元讀取緩衝區的內容。
	4.將readLine取出的內容寫入到字元寫入流緩衝區中。
	5.用newLine方法換行,並重新整理到新建立的檔案中。
*/
import java.io.*;
class FileCopyDemo2
{
	public static void main(String[] args) 
	{
		BufferedWriter bufw=null;
		BufferedReader bufr=null;

		try
		{
			bufw=new BufferedWriter(new FileWriter("E:\\heima\\test_Copy.txt"));
			bufr=new BufferedReader(new FileReader("E:\\heima\\test.txt"));

			String line=null;

			
			while ((line=bufr.readLine())!=null)
			{
				
				bufw.write(line);
				bufw.newLine();
				bufw.flush();
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("檔案讀取失敗");
		}
		finally
		{	
			if (bufr!=null)
			{
				try
				{
					bufr.close();
				}
				catch (IOException ex1)
				{
					throw new RuntimeException("讀取關閉失敗");
				}
			}
			if (bufw!=null)
			{
				try
				{
					bufw.close();
				}
				catch (IOException ex2)
				{
					throw new RuntimeException("寫入關閉失敗");
				}
			}
		}
	}

}
  程式執行後在"E:\\heima"的目錄下建立了test_Copy.txt檔案,該檔案的內容截圖如下:


四、位元組流

 位元組流常用於對非字元檔案的操作,如圖象、視訊等,但不表示位元組流不能操作字元檔案,而是用字元流來操作字元檔案更為便捷。位元組流應掌握的內容基本同字元流一致。

 1.寫入內容的方法總結:

  1)將指定位元組寫入此檔案輸出流。

   void write(int b):該方法丟擲了IO異常。

  2)將 b.length 個位元組從指定 byte 陣列寫入此檔案輸出流中。

   void write(byte[] b):該方法丟擲了IO異常。

  3)將指定 byte 陣列中從偏移量 off 開始的 len 個位元組寫入此檔案輸出流。

   void write(byte[] b,int off,int len):該方法丟擲了IO異常。

  注:上述所有方法都是FileOutputStream類特有方法,且寫入檔案時,不需要進行重新整理操作。

 2.讀取內容的方法總結:

  1)從此輸入流中讀取一個數據位元組。

   int read():返回下一個資料位元組。如果已到達檔案末尾,則返回 -1。

  2)從此輸入流中將最多 b.length 個位元組的資料讀入一個 byte 陣列中。

   int read(byte[] b):返回讀入緩衝區的位元組總數。如果因為已經到達檔案末尾而沒有更多的資料,則返回 -1。在某些輸入可用之前,此方法將阻塞。

3)從此輸入流中將最多 len 個位元組的資料讀入一個 byte 陣列中。

   int read(byte[] b,int off,int len):讀入緩衝區的位元組總數,如果因為已經到達檔案末尾而沒有更多的資料,則返回 -1。如果 len 不為 0,則在輸入可用之前,該方法將阻塞;否則,不讀取任何位元組並返回 0。

3.複製圖片(複製視訊等原理類似):

  示例9:複製指定目錄的檔案。

/*

思路:
	1.用位元組讀取流物件與圖片關聯。
	2.用位元組寫入流物件建立一個新的圖片檔案,用來進行儲存獲取到的檔案資料。
	3.通過迴圈讀寫完成資料的儲存。
	4.關閉資源。

注:位元組流寫入資料時不需要重新整理,但仍需關閉資源。
*/
import java.io.*;
class  CopyPicDemo
{
	public static void main(String[] args) 
	{
		FileInputStream fis=null;
		FileOutputStream fos=null;

		try
		{
			
			fos=new FileOutputStream("E:\\heima\\2.png"); //建立位元組寫入流物件,生成複製影象檔案。
			fis=new FileInputStream("E:\\heima\\1.png"); //建立位元組讀取流物件,並與要被複制的影象檔案關聯。

			byte[] by=new byte[1024];

			int len=0;
			while ((len=fis.read(by))!=-1)
			{
				
				fos.write(by,0,len);
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("檔案複製失敗");
		}
		finally
		{	
			if (fis!=null)
			{
				try
				{
					fis.close();
				}
				catch (IOException ex1)
				{
					throw new RuntimeException("讀取關閉失敗");
				}
			}
			if (fos!=null)
			{
				try
				{
					fos.close();
				}
				catch (IOException ex1)
				{
					throw new RuntimeException("寫入關閉失敗");
				}
			}
			
		}
	}
}
  程式執行後,複製影象檔案與被複製圖形檔案對比圖如下:



 4.位元組流緩衝區

  位元組流緩衝區有兩個:BufferedInputStream(位元組流讀取緩衝區)和BufferedOutputStream(位元組流寫入緩衝區)。

從輸入流讀取資料的方法總結:

   1)從輸入流中讀取資料的下一個位元組: 

    int read():返回下一個資料位元組。如果到達流末尾,則返回 -1。

   2)從此輸入流中將 byte.length 個位元組的資料讀入一個 byte 陣列中:

    int read(byte[] b):返回讀入緩衝區的位元組總數,如果因為已經到達流末尾而沒有更多的資料,則返回 -1。在某些輸入可用之前,此方法將阻塞。

   3)從此位元組輸入流中給定偏移量處開始將各位元組讀取到指定的 byte 陣列中:

    int read(byte[] b, int off, int len):返回讀取的位元組數。如果已到達流末尾,則返回 -1。

   注:上述方法都丟擲了IO異常。

  將資料寫入緩衝的輸出流的方法:

   1)將指定的位元組寫入此緩衝的輸出流:

    void write(int b):該方法丟擲了IO異常。

   2)將 b.length 個位元組寫入此輸出流:

    void write(byte[] b):該方法丟擲了IO異常。

   3)將指定 byte 陣列中從偏移量 off 開始的 len 個位元組寫入此緩衝的輸出流:

    void write(byte[] b,int off,int len):該方法丟擲了IO異常。

  示例10:使用緩衝區技術完成對示例9的複製檔案操作。

import java.io.*;
class  BufferedStreamDemo
{
	public static void main(String[] args) 
	{
		BufferedInputStream bufis=null;
		BufferedOutputStream bufos=null;

		try
		{
			//建立位元組流寫入緩衝區物件
			bufos=new BufferedOutputStream(new FileOutputStream("E:\\heima\\2.png"));

			//建立位元組流讀取緩衝區物件
			bufis=new BufferedInputStream(new FileInputStream("E:\\heima\\1.png"));

			int num=0;
			while ((num=bufis.read())!=-1)
			{
				bufos.write(num);
			}
		}
		catch (IOException e)
		{
			System.out.println(e.toString());
		}
		finally
		{	
			if (bufis!=null)
			{
				try
				{
					bufis.close();
				}
				catch (IOException ex1)
				{
					throw new RuntimeException("讀取關閉失敗");
				}
			}
			if (bufos!=null)
			{
				try
				{
					bufos.close();
				}
				catch (IOException ex1)
				{
					throw new RuntimeException("寫入關閉失敗");
				}
			}
			
		}
	}
}
  程式執行後,複製影象檔案與被複製圖形檔案對比圖如下:



 5.讀取鍵盤錄入:

  示例11:通過鍵盤錄入,當錄入一行的資料後,就將該行資料轉換成大寫並進行列印。如果錄入的資料是over,那麼停止錄入。

/*

System.out:對應的標準輸出裝置,控制檯。
System.in:對應的標準輸入裝置,鍵盤。
*/
import java.io.*;
class  ReadInDemo
{
	public static void main(String[] args) 
	{
		InputStream in=System.in;
		StringBuilder sb=new StringBuilder();
		try
		{
			while (true)
			{
				int ch=in.read();
				if (ch=='\r')
				{
					continue;
				}
				else if (ch=='\n')
				{
					String s=sb.toString();
					if (s.equals("over"))
					{
						break;
					}
					System.out.println(s.toUpperCase());
					sb.delete(0,sb.length());
				}
				else 
					sb.append((char)ch);
			}

		}
		catch (IOException ex)
		{
			throw new RuntimeException("發生異常");
		}
		finally
		{
			try
			{
				in.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException("關閉失敗");
			}
		}
	}
}
  程式執行後的結果如下圖:


 

 6.轉換流

  轉換流包括兩個:InputStreamReader(位元組流通向字元流)和OutpuStreamWriter(字元流通向位元組流)。

  示例12:通過轉換流完成對示例11的操作。

import java.io.*;
class  TransStreamDemo
{
	public static void main(String[] args) 
	{
		BufferedReader bufr=null;
		BufferedWriter bufw=null;
		try
		{
			//將位元組流轉換成字元流輸入
			bufr=new BufferedReader(new InputStreamReader(System.in));

			//將字元流轉換成位元組流輸出
			bufw=new BufferedWriter(new OutputStreamWriter(System.out));
			String line=null;
			while ((line=bufr.readLine())!=null)
			{
				if (line.equals("over"))
				{
					break;
				}
				bufw.write(line.toUpperCase());  //將讀取內容轉換成大寫,並寫入字元流寫入緩衝區
				bufw.newLine();  //換行
				bufw.flush();  //重新整理字元流寫入緩衝區的資料到控制檯
			}
		}
		catch (IOException ex)
		{
			throw new RuntimeException("發生異常");
		}
		finally
		{
			if (bufr!=null)
			{
				try
				{
					bufr.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("讀取關閉失敗");
				}
			}
			if (bufw!=null)
			{
				try
				{
					bufw.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("寫入關閉失敗");
				}
			}
		}
	}
}
  程式執行後的結果如下圖:



五、自定義緩衝區

 1.自定義字元流緩衝區:

  字元流緩衝區提供了一個一次讀一行的方法readline,方便於對文字資料的讀取。當返回null時,表示讀到檔案末尾。

  readLine方法的原理:無論是讀一行,獲取讀取的多個字元。其實最終都是在硬碟上一個一個讀取,所以最終使用的還是read方法一次讀一個的方法。

  注:readLine方法返回的時候,只返回回車符之前的資料內容,並不返回回車符。

  示例13:自定義字元流讀取緩衝區。

<pre name="code" class="java">import java.io.*;
class MyBufferedReaderDemo
{
	public static void main(String[] args) 
	{
		MyBufferedReader bufr=null;

		try
		{
			//建立自定義字元流讀取緩衝區物件
			bufr=new MyBufferedReader(new FileReader("E:\\heima\\BufferDemo.txt"));

			String line=null;

			
			while ((line=bufr.myReadLine())!=null)
			{
				System.out.println(line);
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("檔案讀取失敗");
		}
		finally
		{	
			if (bufr!=null)
			{
				try
				{
					bufr.myClose();
				}
				catch (IOException ex1)
				{
					throw new RuntimeException("讀取關閉失敗");
				}
			}
			
		}
	}

}

//自定義字元讀取流緩衝區
class MyBufferedReader
{
	private FileReader r;

	MyBufferedReader(FileReader r)
	{
		this.r=r;
	}
	
	//定義讀取行功能
	public String myReadLine() throws IOException
	{
		StringBuilder sb=new StringBuilder();

		int ch=0;

		while ((ch=r.read())!=-1)
		{
			
			if (ch=='\r')
				continue;
			else if (ch=='\n')
				return sb.toString();
			else 
				sb.append((char)ch);
			
		}
		if (sb.length()!=0)
		{
			return sb.toString();
		}
	
		return null;

	}

	//定義關閉字元讀取流功能
	public void myClose() throws IOException
	{
		r.close();
	}
}
  檔案BufferedDemo.txt內容截圖如下:

   程式執行後的結果如下圖:

2.自定義位元組流緩衝區:

  示例14:自定義位元組流讀取緩衝區。

import java.io.*;
class  MyBufferedInputStreamDemo
{
	public static void main(String[] args) 
	{
		MyBufferedInputStream mybufis=null;
		BufferedOutputStream bufos=null;

		try
		{
			//建立自定義位元組流寫入緩衝區物件
			bufos=new BufferedOutputStream(new FileOutputStream("E:\\heima\\2.mp3"));

			//建立位元組流讀取緩衝區物件
			mybufis=new MyBufferedInputStream(new FileInputStream("E:\\heima\\1.mp3"));

//			byte[] by=new byte[1024];

			int num=0;
			while ((num=mybufis.myRead())!=-1)
			{
				//num是int型別,那麼寫入檔案的位元組數是不是4個位元組呢?不是的,因為write方法
				//裡對int型別做了強制型別轉換,只保留了int型別的最後八位。所以還是隻寫入了
				//一個位元組的資料。
				bufos.write(num);
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("檔案複製失敗");
		}
		finally
		{	
			if (mybufis!=null)
			{
				try
				{
					mybufis.myClose();
				}
				catch (IOException ex1)
				{
					throw new RuntimeException("讀取關閉失敗");
				}
			}
			if (bufos!=null)
			{
				try
				{
					bufos.close();
				}
				catch (IOException ex1)
				{
					throw new RuntimeException("寫入關閉失敗");
				}
			}
			
		}
	}
}

//自定義位元組流讀取緩衝區
class MyBufferedInputStream 
{
	private InputStream is;
	private byte[] by=new byte[1024*4];
	private int pos=0,count=0;

	MyBufferedInputStream(InputStream is)
	{
		this.is=is;
	}
	
	//一次讀一個位元組,從緩衝區(位元組陣列)獲取
	public int myRead() throws IOException
	{

		if (count==0)
		{
			//通過is物件獲取硬碟上的資料,並存儲在by陣列中
			count=is.read(by);
			pos=0;
		}
		if (count>0)
		{
			int b=by[pos];
			pos++;
			count--;

			//為什麼要與上255?因為位元組流的位元組資料都是二進位制,如果讀取的一個位元組資料為1111-1111,即十進位制為-1,
			//那麼提升為int型別時,還是-1,此時這個位元組資料的-1就會和迴圈讀取的判斷條件-1一致,導致迴圈停止,復
			//制檔案失敗,所以需與上255,從而保證int型別的最後一個八位不變,前面三個八位都為0,此時就不會出現返回
			//-1的現象。這也是返回型別不是byte而是int的原因所在。
			return b&255;
		}
		return -1;
	}

	public void myClose() throws IOException
	{
		is.close();
	}
}
  程式執行後的截圖如下: