1. 程式人生 > >第三十九講 遞迴

第三十九講 遞迴

遞迴是一種程式設計思想,對於我這種愚人來說,理解起來實在是夠費勁的,可以這樣說,至今為止,我依然不懂遞迴,也只是對它有一點粗略的、表面的瞭解,實在是慚愧!

遞迴的概述

遞迴就是函式自身呼叫自身,即函式內部又使用到了該函式功能。雖然知道是這麼一個意思,但一寫程式碼遇到遞迴就懵逼了,所以這裡我就舉幾個例子來詳述一下它。

遞迴的應用之一—— 遍歷指定目錄下的內容(包含子目錄中的內容)

現有這樣一個需求:列出指定目錄下的檔案或者資料夾,包含子目錄中的內容。也就是列出指定目錄下的所有內容。

分析:因為目錄中還有目錄,只要使用同一個列出目錄功能的函式完成即可。在列出過程中出現的還是目錄的話,還可以再次呼叫本功能。也就是函式自身呼叫自身,這種表現形式或者程式設計手法,稱為遞迴。

首先定義一個列出目錄功能的遞迴函式,如下:

public static void getAllFiles(File dir) {
	System.out.println("dir : " + dir);
	//1. 獲取該目錄的檔案物件陣列。
	File[] files = dir.listFiles();
	
	//2. 對陣列進行遍歷。
	for (File file : files) {
		
		if (file.isDirectory()) {
			getAllFiles(file);
		} else {
			System.out.println("file : " + file)
; } } }

然後呼叫以上遞迴函式,呼叫程式碼如下:

public static void main(String[] args) {
	/*
	 * 遍歷指定目錄下的內容(包含子目錄中的內容)。
	 * 
	 * 遞迴:函式自身呼叫自身。函式內部又使用到了該函式的功能。
	 * 什麼時候使用?
	 * 功能被重複使用,但是每次該功能使用的參與運算的資料不同時,可以考慮遞迴方式來解決!
	 */
	File dir = new File("F:\\Java\\JavaSE_code");
	getAllFiles(dir);
}

執行結果截圖如下:
這裡寫圖片描述
最後,我們似乎可以知道了遞迴什麼時候使用了?——功能被重複使用,但是每次該功能使用參與運算的資料不同時,可以考慮遞迴方式解決。

遞迴的應用之二—— 求和

這裡,直接給出示例程式碼。

public class RecursionDemo {

	public static void main(String[] args) {
		
		/*
		 * 遞迴:函式自身呼叫自身。函式內部又使用到了該函式的功能。
		 * 什麼時候使用?
		 * 功能被重複使用,但是每次該功能使用的參與運算的資料不同時,可以考慮遞迴方式來解決!
		 * 
		 * 使用時,一定要定義條件。
		 * 注意遞迴次數過多,會出現棧記憶體溢位(java.lang.StackOverflowError)。
		 */
		int sum = getSum(60000);
		System.out.println("sum = " + sum);
	}
	
	public static int getSum(int num) {
		if (num == 1) {
			return 1;
		}
		return num + getSum(num - 1);
	}

}

執行結果截圖如下:
這裡寫圖片描述
從以上兩個例子我們可以得出如下結論:

遞迴要注意:

  1. 限定條件。
  2. 要注意遞迴的次數,儘量避免記憶體溢位(java.lang.StackOverflowError)。

遞迴的應用之三—— 遞迴刪除帶內容的目錄

現在我們來思考一個問題——刪除一個帶內容的目錄的過程是如何進行的呢?

分析:在Windows中,刪除目錄從裡面往外刪除的,既然是從裡往外刪除,就需要用到遞迴了。

這裡,就直接給出程式程式碼了,如下:

import java.io.*;

public class RemoveDir {
    public static void main(String[] args) {
        File dir = new File("f:\\test");
        removeDir(dir);
    }

    public static void removeDir(File dir) {
        File[] files = dir.listFiles();
        for (int x = 0; x < files.length; x++) {
            if(!files[x].isHidden() && files[x].isDirectory()) // 避開隱藏檔案
                removeDir(files[x]);
            else
                System.out.println(files[x].toString()+":-file-:"+files[x].delete());
        }
        System.out.println(dir+"::dir::"+dir.delete());
    }
}

執行結果截圖如下:
這裡寫圖片描述
此處須注意Java刪除時是不走回車站的,除此之外,還須注意兩點,如下:

  1. 隱藏目錄——無法訪問就不能刪除,返回的files為空,會導致空指標異常。
  2. 系統中的有些檔案雖然看上去是一個檔案,其實是一個目錄,或反之。

遞迴的應用之四——獲取一個想要的指定檔案的集合,例如獲取F:\Java\JavaSE_code目錄下(包含子目錄)的所有的.java的檔案物件,並存儲到集合中

現有這樣一個需求:獲取一個想要的指定檔案的集合,例如獲取F:\Java\java_bxd目錄下(包含子目錄)的所有的.java的檔案物件,並存儲到集合中。
我的分析如下:

  1. 對指定的目錄進行遞迴。
  2. 在遞迴的過程中需要過濾器,獲取指定過濾器條件的.java檔案物件。
  3. 將滿足指定過濾器條件的.java檔案物件都新增到集合中。

於是,首先編寫一個對指定的目錄進行遞迴的函式,可寫成如下,以供參考。

/**
 * 對指定的目錄進行遞迴。
 * 
 * 注意:多級目錄下,都要用到相同的集合和過濾器,那麼不要在遞迴方法中定義,而是不斷地進行傳遞。
 * 
 * @param dir 需要遍歷的目錄。
 * @param list 用於儲存符合條件的物件。
 * @param filter 接收指定的過濾器。
 */
public static void getFileList(File dir, List<File> list, FileFilter filter) {
	//1. 通過listFiles()方法,獲取dir當前下的所有的檔案和資料夾物件。
	File[] files = dir.listFiles();
	//2. 遍歷陣列。
	for (File file : files) {
		//3. 判斷是否是檔案,如果是,遞迴,如果不是,那就是檔案,就需要對檔案進行過濾。
		if (file.isDirectory()) {
			getFileList(file, list, filter);
		} else {
			//4. 通過過濾器對檔案進行過濾。
			if (filter.accept(file)) {
				list.add(file);
			}
		}
	}
}

在編寫該函式時一定要注意:多級目錄下都要用到相同的集合和過濾器,那麼不要在遞迴方法中定義,而是不斷地進行傳遞
然後我們再定義一個獲取指定過濾器條件的檔案的集合的函式,可寫成如下,以供參考。

/**
 * 定義一個獲取指定過濾器條件的檔案的集合。
 */
public static List<File> fileList(File dir, String suffix) {
	//1. 定義集合。
	List<File> list = new ArrayList<File>();
	
	//2. 定義過濾器。
	FileFilter filter = new FileFilterBySuffix(suffix);
	
	getFileList(dir, list, filter);
	
	return list;
}

從以上函式可看出,需要用到檔案過濾器,所以我們還需要定義一個檔案過濾器,這裡我設計了一個——FileFilterBySuffix.java,其內容如下:

public class FileFilterBySuffix implements FileFilter {

	private String suffix;
	
	public FileFilterBySuffix(String suffix) {
		super();
		this.suffix = suffix;
	}

	@Override
	public boolean accept(File pathname) {
		return pathname.getName().endsWith(suffix);
	}

}

最後,我們編寫測試程式碼進行測試。

public static void main(String[] args) {

    File dir = new File("F:\\Java\\JavaSE_code");

    List<File> list = fileList(dir, ".java");

    for (File file : list) {
        System.out.println(file);
    }

}

此綜合練習用到了比較多的知識點,做起來還蠻不錯的,知識點都串聯起來了。