1. 程式人生 > >IO流總結(基礎知識)

IO流總結(基礎知識)

IO流總結

 IO這章的知識在上面一篇部落格也說過一點,主要要體會一下裝飾者設計模式和介面卡設計模式,這樣更利於我們理解複雜的IO體系結構。今天就讓我們看一看。不過在講IO
之前,我們先把檔案(File)的知識簡單過一下。

一、檔案File

檔案大家都不陌生,檢視JDK幫助文件,我們知道File的定義——檔案和目錄(資料夾)路徑名的抽象表示形式。

1、構造方法

File(String pathname):根據一個路徑得到File物件
File(String parent, String child):根據一個目錄和一個子檔案/目錄得到File物件
File(File parent, String child):根據一個父File物件和一個子檔案/目錄得到File物件

2、建立功能

public boolean createNewFile():建立檔案 如果存在這樣的檔案,就不建立了
public boolean mkdir():建立資料夾 如果存在這樣的資料夾,就不建立了
public boolean mkdirs():建立資料夾,如果父資料夾不存在,會幫你創建出來

舉例:
         //需求1:我要在e盤目錄下建立一個檔案demo
File file = new File("e:\\a.txt");
大家可能會認為這裡用了關鍵字new,那麼檔案File就會出來了,其實不是,因為File的定義是:檔案和目錄(資料夾)路徑名的抽象表示形式。它僅僅是檔案的表示
形式,不是真實的檔案。 所以需要呼叫這樣一句程式碼file.createNewFile()

        //需求2:我要在e盤目錄test下建立一個檔案b.txt

File file = new File("e:\\test\\b.txt");
System.out.println("createNewFile:" + file3.createNewFile());
執行之後可以看到控制檯打印出:
Exception in thread "main" java.io.IOException: 系統找不到指定的路徑。

所以注意:要想在某個目錄下建立內容,該目錄首先必須存在。我們可以這麼做:File file1=new File("e:\\test);file1.mkdir();//先把資料夾(目錄)創建出來然後在:

        File file2=new   File(e:\\test\\b.txt);file2.createNewFile();//在建立檔案

        //看下面這行程式碼
        File file = new File("e:\\liuyi\\a.txt");
System.out.println("mkdirs:" + file8.mkdirs());
你會看到a.txt是個目錄,不要認為指定了.txt字尾就是檔案了,要看它呼叫的是creatNewFile()還是mkdir();

3、刪除功能

public boolean delete()
這個方法比較簡單,注意:
A:如果你建立檔案或者資料夾忘了寫碟符路徑,那麼,預設在專案路徑下。
B:Java中的刪除不走回收站。
C:要刪除一個資料夾,請注意該資料夾內不能包含檔案或者資料夾,所以delete一次只能刪除一個檔案,如果想刪除一個資料夾,那必須要迴圈遍歷了。

4、重新命名功能

    public boolean renameTo(File dest)
如果路徑名相同,就是改名。
        如果路徑名不同,就是改名並剪下。

需求:將G盤所有視訊檔案該名成“0?-網路程式設計(概述).avi”,原來視訊檔名稱為:黑馬程式設計師_畢向東_Java基礎視訊教程第23天-0?-網路程式設計(概述).avi

<span style="font-size:18px;">public class FileDemo {
	private static File dest;
	public static void main(String[] args) {
		//封裝目錄
		File  file=new File("G:\\黑馬入學\\網路程式設計1");
		File[] sorcFolder = file.listFiles();
		for (File f : sorcFolder) {
			String name = f.getName();
			//黑馬程式設計師_畢向東_Java基礎視訊教程第23天-01-網路程式設計(概述).avi
			//System.out.println(name);			
			int index = name.indexOf('-');
			String string = name.substring(index+1);
			//再次封裝新的檔案目錄
             dest = new File(file,string);
             f.renameTo(dest);            
		}
	}
}</span>
執行結果:

一看全部改名成功,哈哈。。。

5、判斷功能

  public boolean isDirectory():判斷是否是目錄
  public boolean isFile():判斷是否是檔案
  public boolean exists():判斷是否存在
  public boolean canRead():判斷是否可讀
  public boolean canWrite():判斷是否可寫
  public boolean isHidden():判斷是否隱藏

6、簡單獲取功能

 public String getAbsolutePath():獲取絕對路徑
 public String getPath():獲取相對路徑
 public String getName():獲取名稱
 public long length():獲取長度。位元組數
 public long lastModified():獲取最後一次的修改時間,毫秒值

 示例程式碼:

<span style="font-size:18px;"> public class FileDemo {
	public static void main(String[] args) {
		// 建立檔案物件
		File file = new File("demo\\test.txt");

		System.out.println("getAbsolutePath:" + file.getAbsolutePath());
		System.out.println("getPath:" + file.getPath());
		System.out.println("getName:" + file.getName());
		System.out.println("length:" + file.length());
		System.out.println("lastModified:" + file.lastModified());

		// 1432518063391
		Date d = new Date(1432518063391L);
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String s = sdf.format(d);
		System.out.println(s);
	}
}</span>
執行結果是:


7、高階獲取功能

public String[] list():獲取指定目錄下的所有檔案或者資料夾的名稱陣列
public File[] listFiles():獲取指定目錄下的所有檔案或者資料夾的File陣列
後者功能更為強大一點,因為返回的是File物件,所以可以得到File的各種屬性。


8.檔名稱過濾器——FilenameFilter

需求:判斷E盤目錄下是否有後綴名為.jpg的檔案,如果有,就輸出此檔名稱
public String[] list(FilenameFilter filter)
public File[] listFiles(FilenameFilter filter)

示例程式碼:

<span style="font-size:18px;">public class FileDemo {
	public static void main(String[] args) {
	
		//封裝e盤
		File file=new File("e:\\");	
		// 獲取該目錄下所有檔案或者資料夾的String陣列
		String[] list = file.list(new FilenameFilter() {			
			@Override
			public boolean accept(File dir, String name) {
				//如果是檔案並且是以.jpg結尾的,返回true,一旦返回true,name就會被放進String陣列中,
				return new File(dir, name).isFile() && name.endsWith(".jpg");
			}
		});
		for (String string : list) {
			System.out.println(string);
		}
	}
}</span>
雖然普通的方式也能實現,這樣看起來,程式碼更加優雅,現在我們來看看list(FilenameFilter filter)的原始碼
<span style="font-size:18px;">public String[] list(FilenameFilter filter) {
        String names[] = list();
        if ((names == null) || (filter == null)) {
            return names;
        }
        List<String> v = new ArrayList<>();
        for (int i = 0 ; i < names.length ; i++) {
            if (filter.accept(this, names[i])) {
                v.add(names[i]);
            }
        }
        return v.toArray(new String[v.size()]);
 }</span>

二、IO相關操作

    IO因為體系過於龐大,這裡並不面面俱到,撿其重點來說。上面簡單說了一下File的使用,File的讀取,寫入,複製。相信都是我們經常使用的功能。談起IO流,首先我們要對它有個大致的認識,也就是IO的分類,請看下面:
A:按流向分
輸入流讀取資料
輸出流寫出資料
B:按資料型別
位元組流
位元組輸入流
位元組輸出流
字元流
字元輸入流
字元輸出流

      如果你不知道什麼是位元組流,什麼是字元流,我告訴你,如果電腦記事本可以開啟的並且可以看得懂的,那就是可以用字元流操作的檔案,當然也可以用位元組流來操作,如果電腦記事本能開啟,但是我們看不懂的,那就是用位元組流來操作的檔案,比如MP3檔案、JPG檔案等,現在我們來看看IO是怎麼來操作File的。

1、位元組流操作檔案

1.1、FileOutputStream寫出資料

A:操作步驟
a:建立位元組輸出流物件
b:呼叫write()方法
c:釋放資源

B:示例程式碼:
FileOutputStream fos = new FileOutputStream("fos.txt");
fos.write("hello".getBytes());
fos.close();

1.2、FileInputStream讀取資料

FileInputStream讀取資料
A:操作步驟
a:建立位元組輸入流物件
b:呼叫read()方法
c:釋放資源

B:程式碼體現:
FileInputStream fis = new FileInputStream("fos.txt");
//方式1,我們一次讀取一個位元組
int by = 0;
while((by=fis.read())!=-1) {
System.out.print((char)by);
}
//方式2  我們一次讀取一個位元組陣列
byte[] bys = new byte[1024];
int len = 0;
while((len=fis.read(bys))!=-1) {
System.out.print(new String(bys,0,len));
}
fis.close();

1.3、四種方式複製檔案效率比較

FileInputStream、FileOutputStream這兩個類我們稱之為基本位元組流,相對於基本位元組流,IO中還有高效位元組流BufferedInputStream、BufferedOutputStream 
看完了這個,那麼問題來,哪種方式好呢?
下面我們來看一個例子:
需求:把G:\IO流(IO流小結圖解).avi複製到當前專案目錄下的copy.avi中
 位元組流四種方式複製檔案:
 * 基本位元組流一次讀寫一個位元組
 * 基本位元組流一次讀寫一個位元組陣列
 * 高效位元組流一次讀寫一個位元組
 * 高效位元組流一次讀寫一個位元組陣列

G:\IO流(IO流小結圖解).av的資訊如下:


示例程式碼:

<span style="font-size:18px;">public class CopyAVI {
	public static void main(String[] args) throws IOException {
		long start = System.currentTimeMillis();
		 method1("G:\\IO流(IO流小結圖解).avi", "copy1.avi");
		 //method2("G:\\IO流(IO流小結圖解).avi", "copy2.avi");
	        //method3("G:\\IO流(IO流小結圖解).avi", "copy3.avi");
		//method4("G:\\IO流(IO流小結圖解).avi", "copy4.avi");
		long end = System.currentTimeMillis();
		System.out.println("共耗時:" + (end - start) + "毫秒");
	}

	// 高效位元組流一次讀寫一個位元組陣列:
	public static void method4(String srcString, String destString)throws IOException {
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcString));
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destString));
		byte[] bys = new byte[1024];
		int len = 0;
		while ((len = bis.read(bys)) != -1) {
			bos.write(bys, 0, len);
		}

		bos.close();
		bis.close();
	}

	// 高效位元組流一次讀寫一個位元組:
	public static void method3(String srcString, String destString)throws IOException {
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcString));
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destString));

		int by = 0;
		while ((by = bis.read()) != -1) {
			bos.write(by);

		}
		bos.close();
		bis.close();
	}

	// 基本位元組流一次讀寫一個位元組陣列
	public static void method2(String srcString, String destString)throws IOException {
		FileInputStream fis = new FileInputStream(srcString);
		FileOutputStream fos = new FileOutputStream(destString);

		byte[] bys = new byte[1024];
		int len = 0;
		while ((len = fis.read(bys)) != -1) {
			fos.write(bys, 0, len);
		}
		fos.close();
		fis.close();
	}

	// 基本位元組流一次讀寫一個位元組
	public static void method1(String srcString, String destString)throws IOException {
		FileInputStream fis = new FileInputStream(srcString);
		FileOutputStream fos = new FileOutputStream(destString);
		int by = 0;
		while ((by = fis.read()) != -1) {
			fos.write(by);
		}
		fos.close();
		fis.close();
	}
}</span>
執行結果:

方法一執行:

方法二執行:

方法三執行:

方法四執行:

四種方式的效能差異,通過這個執行結果,想必大家也知道了 ,所以在實際開發中選擇哪一個來操作檔案,相信大家已經有了選擇了,反正我是喜歡用方法四!!

2、字元流操作檔案

      大家可能會問,操作檔案已經有了位元組流,為什麼還要說字元流呢,哎,誰叫咱們是中國人呢,漢字比較複雜,一個漢字站了兩個位元組,所以直接用字元流操作不夠方便所以就有了字元流。我們來看看字元流的體系結構。

 2.1字元流體系結構  

   |--字元流
|--字元輸入流
Reader
int read():一次讀取一個字元
int read(char[] chs):一次讀取一個字元陣列

|--InputStreamReader
|--FileReader
|--BufferedReader
String readLine():一次讀取一個字串
|--字元輸出流
Writer
void write(int ch):一次寫一個字元
void write(char[] chs,int index,int len):一次寫一個字元陣列的一部分

|--OutputStreamWriter
|--FileWriter
|--BufferedWriter
void newLine():寫一個換行符
void write(String line):一次寫一個字串


       字元流的讀寫是由基類Reader和Writer來完成的,它們各有兩個子類來幫助我們來操作檔案。Reader類的子類BufferedReader與Writer的子類BufferedWriter,我們一定可以猜到這是高效字元流操作檔案使用的!那麼,Reader類的子類InputStreamReader與Writer的子類OutputStreamWriter是幹什麼的呢?從類的名稱來來看,前面是位元組的,後面是字元的,我告訴你,這兩個類就是轉換流,把位元組流轉換成字元流,那麼這有什麼用呢?-----鍵盤錄入!!!一會你們就知道了。。。。。注意OutputStreamWriter還有子類FileWriter,InputStreamReader還有子類FileReader。


2.1、字元流操作檔案

我們先看一個字元流操作檔案的例子。
需求:把當前專案目錄下的a.txt內容複製到當前專案目錄下的b.txt中

 資料來源:
a.txt -- 讀取資料 -- 字元轉換流 -- InputStreamReader -- FileReader -- BufferedReader
 目的地:
b.txt -- 寫出資料 -- 字元轉換流 -- OutputStreamWriter -- FileWriter -- BufferedWriter

示例程式碼:

<span style="font-size:18px;">public class CopyFileDemo {
	public static void main(String[] args) throws IOException {
		// 封裝資料來源
		BufferedReader br = new BufferedReader(new FileReader("a.txt"));
		// 封裝目的地
		BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));

		// 兩種方式其中的一種一次讀寫一個字元陣列,當然此處你也可以一次讀取一個位元組的方法
		// char[] chs = new char[1024];
		// int len = 0;
		// while ((len = br.read(chs)) != -1) {
		// 	bw.write(chs, 0, len);
		// 	bw.flush();
		// }
		
		// 還有一種方式讀寫資料,這就是BufferedWriter、BufferedReader的強大之處。。。。
		String line = null;
		//下面三句話,我習慣連著寫,寫入、換行、重新整理,動作相當連貫有木有啊!!!!!!
		 while ((line = br.readLine()) != null) {
			 bw.write(line);
		     bw.newLine();
			 bw.flush();
		}
		
		// 釋放資源
		bw.close();
		br.close();
	}
}</span>
程式碼解讀: bw.newLine();這句話的意思是換行,呼叫的效果和我們寫“\r\n”是一樣的,但是newLine可以誇平臺的,這就是區別!
           readLine()這個方法有點厲害,因為它一次讀一行!!!
部落格寫到這裡,我們發現,複製檔案檔案起碼有5種方式,前面所講的1.基本位元組流一次讀寫一個位元組、2.基本位元組流一次讀寫一個位元組陣列、3.高效位元組流一次讀寫一個位元組、4.高效位元組流一次讀寫一個位元組陣列共4種,再加上上面的一種,共5種,而且第五種是最好的,個人感覺。。。。

3、IO程式設計實戰

3.1、複製單級資料夾

/*
 * 需求:複製指定目錄下的指定檔案,並修改後綴名。
 * 指定的檔案是:.java檔案。
 * 指定的字尾名是:.txt
 * 指定的目錄是:txt
 * 資料來源:G:\designModel
 * 目的地:G:\txt
 * 
 * 分析:
 * A:封裝目錄
 * B:獲取該目錄下的java檔案的File陣列
 * C:遍歷該File陣列,得到每一個File物件
 * D:把該File進行復制
 * E:在目的地目錄下改名
 */

 * 資料來源:G:\designModel如下圖所示:



 示例程式碼:

<span style="font-size:18px;">public class CopyFolder {

	public static void main(String[] args) throws IOException {
		 //A:封裝目錄
	     File srcFolder=new File("G:\\designModel");
	     //封裝目的地
	     File destFolder=new File("G:\\myNote");
	     //如果目的地檔案不存在,就建立一個
	     if(!destFolder.exists()){
	    	 destFolder.mkdir();
	     }
	     //B:獲取該目錄下的java檔案的File陣列     
	     File[] srcFiles = srcFolder.listFiles(new FilenameFilter() {		
			@Override
			public boolean accept(File dir, String name) {
				// 檔案過濾器,得到.java檔案,去除其他型別的檔案
				return new File(dir,name).isFile()&&name.endsWith(".java");
			}
		});
	     
	     // C:遍歷該File陣列,得到每一個File物件
	     
	      for (File file : srcFiles) {
	    	  String name = file.getName();
	    	  File newFile=new File(destFolder,name);
			  copyFile(file,newFile);
		  }
	      
	  	  //E:在目的地目錄下改名
	      for (File file :destFolder.listFiles()) {
			
	    	  String name = file.getName();
	    	  String newName = name.replace(".java", ".txt");
	    	  File newFile=new File(destFolder,newName);
			  file.renameTo(newFile);
		}
	      
	}

		//	D:把該File進行復制
	private static void copyFile(File file, File newFile) throws IOException {
		
		BufferedInputStream bis=new BufferedInputStream(new FileInputStream(file));
		BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(newFile));    
		byte [] bys=new byte[1024];
		int len=0;
		while((len=bis.read(bys))!=-1){
			bos.write(bys,0,len);
		}
		//關閉資源
		bis.close();
		bos.close();
	}
}</span>
執行結果:



3.2、複製多級資料夾

思路分析
  資料來源:G:\\
  目的地:G:\\


    A:封裝資料來源File
    B:封裝目的地File
    C:判斷該File是資料夾還是檔案
  a:是資料夾
                 就在目的地目錄下建立該資料夾
         獲取該File物件下的所有檔案或者資料夾File物件
         遍歷得到每一個File物件
          回到C
           b:是檔案
         複製(位元組流)

示例程式碼:

<span style="font-size:18px;">public class CopyFolders{
	public static void main(String[] args) throws IOException {
		// 封裝資料來源File
		File srcFile = new File("G:\\");
		// 封裝目的地File
		File destFile = new File("E:\\");
		// 複製資料夾的功能
		copyFolder(srcFile, destFile);
	}

	private static void copyFolder(File srcFile, File destFile)throws IOException {
		// 判斷該File是資料夾還是檔案
		if (srcFile.isDirectory()) {
			// 資料夾
			File newFolder = new File(destFile, srcFile.getName());
			newFolder.mkdir();
			// 獲取該File物件下的所有檔案或者資料夾File物件
			File[] fileArray = srcFile.listFiles();
			for (File file : fileArray) {
				copyFolder(file, newFolder);
			}
		} else {
			// 檔案
			File newFile = new File(destFile, srcFile.getName());
			copyFile(srcFile, newFile);
		}
	}

	//複製檔案
	private static void copyFile(File srcFile, File newFile) throws IOException {
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFile));
		byte[] bys = new byte[1024];
		int len = 0;
		while ((len = bis.read(bys)) != -1) {
			bos.write(bys, 0, len);
		}
		bos.close();
		bis.close();
	}
}</span>

3.3 鍵盤錄入5個學生資訊(姓名,語文成績,數學成績,英語成績),按照總分從高到低存入文字檔案

思路分析:
A:建立學生類
B:建立集合物件
    因為要排序,所以用TreeSet<Student>
C:鍵盤錄入學生資訊儲存到集合
D:遍歷集合,把資料寫到文字檔案


<span style="font-size:18px;">public class Student {
	// 姓名
	private String name;
	// 語文成績
	private int chinese;
	// 數學成績
	private int math;
	// 英語成績
	private int english;

	//構造方法和setter/getter方法省去了。。。。。。。。  
}</span>
<span style="font-size:18px;">public class StudentIO {
	public static void main(String[] args) throws IOException {
		// 建立集合物件(採用匿名內部類的方式,建立帶有比較器的集合物件)
		TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
			@Override
			public int compare(Student s1, Student s2) {
				int num = s2.getSum() - s1.getSum();
				int num2 = num == 0 ? s1.getChinese() - s2.getChinese() : num;
				int num3 = num2 == 0 ? s1.getMath() - s2.getMath() : num2;
				int num4 = num3 == 0 ? s1.getName().compareTo(s2.getName()): num3;
				return num4;
			}
		});

		// 鍵盤錄入學生資訊儲存到集合
		for (int x = 1; x <= 5; x++) {
			Scanner sc = new Scanner(System.in);
			System.out.println("請錄入第" + x + "個的學習資訊");
			System.out.println("姓名:");
			String name = sc.nextLine();
			System.out.println("語文成績:");
			int chinese = sc.nextInt();
			System.out.println("數學成績:");
			int math = sc.nextInt();
			System.out.println("英語成績:");
			int english = sc.nextInt();

			// 建立學生物件
			Student s = new Student();
			s.setName(name);
			s.setChinese(chinese);
			s.setMath(math);
			s.setEnglish(english);

			// 把學生資訊新增到集合
			ts.add(s);
		}

		// 遍歷集合,把資料寫到文字檔案
		BufferedWriter bw = new BufferedWriter(new FileWriter("students.txt"));
		//以下三行程式碼連寫
		bw.write("學生資訊如下:");
		bw.newLine();
		bw.flush();
	    bw.write("姓名\t語文成績\t數學成績\t英語成績");
		bw.newLine();
		bw.flush();
		for (Student s : ts) {
			StringBuilder sb = new StringBuilder();
			sb.append(s.getName()).append("\t").append(s.getChinese())
					.append("\t").append(s.getMath()).append("\t")
					.append(s.getEnglish());
			bw.write(sb.toString());
			bw.newLine();
			bw.flush();
		}
		// 釋放資源
		bw.close();
		System.out.println("學習資訊儲存完畢");
	}
}</span>
輸入:


執行結果:


好啦,如果上面的例子可以弄懂,IO就基本沒有什麼問題啦,哈哈。。。