第三十九講 遞迴
遞迴是一種程式設計思想,對於我這種愚人來說,理解起來實在是夠費勁的,可以這樣說,至今為止,我依然不懂遞迴,也只是對它有一點粗略的、表面的瞭解,實在是慚愧!
遞迴的概述
遞迴就是函式自身呼叫自身,即函式內部又使用到了該函式功能。雖然知道是這麼一個意思,但一寫程式碼遇到遞迴就懵逼了,所以這裡我就舉幾個例子來詳述一下它。
遞迴的應用之一—— 遍歷指定目錄下的內容(包含子目錄中的內容)
現有這樣一個需求:列出指定目錄下的檔案或者資料夾,包含子目錄中的內容。也就是列出指定目錄下的所有內容。
分析:因為目錄中還有目錄,只要使用同一個列出目錄功能的函式完成即可。在列出過程中出現的還是目錄的話,還可以再次呼叫本功能。也就是函式自身呼叫自身,這種表現形式或者程式設計手法,稱為遞迴。
首先定義一個列出目錄功能的遞迴函式,如下:
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);
}
}
執行結果截圖如下:
從以上兩個例子我們可以得出如下結論:
遞迴要注意:
- 限定條件。
- 要注意遞迴的次數,儘量避免記憶體溢位(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刪除時是不走回車站的,除此之外,還須注意兩點,如下:
- 隱藏目錄——無法訪問就不能刪除,返回的files為空,會導致空指標異常。
- 系統中的有些檔案雖然看上去是一個檔案,其實是一個目錄,或反之。
遞迴的應用之四——獲取一個想要的指定檔案的集合,例如獲取F:\Java\JavaSE_code目錄下(包含子目錄)的所有的.java的檔案物件,並存儲到集合中
現有這樣一個需求:獲取一個想要的指定檔案的集合,例如獲取F:\Java\java_bxd目錄下(包含子目錄)的所有的.java的檔案物件,並存儲到集合中。
我的分析如下:
- 對指定的目錄進行遞迴。
- 在遞迴的過程中需要過濾器,獲取指定過濾器條件的.java檔案物件。
- 將滿足指定過濾器條件的.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);
}
}
此綜合練習用到了比較多的知識點,做起來還蠻不錯的,知識點都串聯起來了。