Java的遞迴、如何與流相結合
遞迴技術
需求:掃描D:\test所有子資料夾及子子資料夾下的.jpg檔案。 我們如果用迴圈來做這件事,我們不知道迴圈的結束條件,也不知道到底有多少層,所以比較麻煩。 我們可以用一種新的思想:遞迴。 遞迴舉例: 從前有一座山,山裡有座廟,廟裡有個老和尚,老和尚在給小和尚講故事: 從前有一座山,山裡有座廟,廟裡有個老和尚,老和尚在給小和尚講故事: 從前有一座山,山裡有座廟,廟裡有個老和尚,老和尚在給小和尚講故事: 。。。。。。。
故事如何才能結束:小和尚還俗了。廟塌了。山崩了。
Java中的遞迴: 在方法的函式體中又呼叫了方法自己本身。遞迴呼叫的細節:必須要求遞迴中有可以讓函式呼叫的結束條件。否則函式一直呼叫,就會導致記憶體溢位。
遞迴演示
練習1:需求:計算1~5的和值,不許使用迴圈。 分析和步驟: 1)定義一個DiGuiDemo測試類; 2)在這個類中的main函式中呼叫自定義函式,5作為函式的引數,使用一個變數sum來接收返回的和值,並輸出和值sum; 3)自定義add()函式接收傳遞的引數5; 4)在自定義函式中書寫if語句判斷i是否大於1,如果大於1,則使用return返回i+add(i-1); 5)否則i<=1時,返回1;
package cn.xuexi.digui.demo; /* * 練習1:需求:計算1~5的和值,不許使用迴圈。 * 遞迴演示 */ public class DiGuiDemo { public static void main(String[] args) { //呼叫自定義函式求1~5之間的和值 int sum=add(5); System.out.println(sum); } /* * 自定義函式求1~5之間的和值 * 5+4+3+2+1 */ public static int add(int n) { /* * 判斷n是否大於1 */ if(n>1) { //這裡的add函式呼叫了函式自己本身的做法就是函式的遞迴呼叫 return n+add(n-1); } //返回1作為遞迴的結束條件 return 1; } }
上述程式碼圖解如下圖所示:
1.png
練習2:需求:求5的階乘!! 分析:
普及一下數學知識: 方式1:5! = 5 * 4 * 3 * 2 * 1 = 120;
方式1 迴圈方式: 程式碼如下所示: 步驟: 1)定義一個DiGuiDemo2測試類; 2)在這個類中的main函式中呼叫自定義函式jc(),5作為函式的引數,使用一個變數result來接收返回的階乘的值,並輸出結果result; 3)自定義jc()函式接收傳遞的引數5; 4)在自定義函式中定義一個變數result=1,使用for迴圈來實現方式1求階乘的結果,並返回result階乘的值;
package cn.xuexi.digui.demo; /* * 練習2:需求:求5的階乘!! * 方式1:5!=5*4*3*2*1=120 */ public class DiGuiDemo2 { public static void main(String[] args) { int result=jc(5); //輸出階乘後的結果 System.out.println(result);//120 } //使用方式一來求5的階乘 public static int jc(int n) { //定義 一個變數來接收階乘後的結果 int result =1; for (int i = 2; i <= n; i++) { result=result*i; } return result; } }
方式2:使用遞迴思想來完成
5! = 5 * 4!
4! = 4 * 3!
3! = 3 * 2!
2! = 2 * 1!
1! = 1*0!
找規律:n! = n * (n-1)! 補充:0!等於1 結束條件:if(n <= 1) return 1;
方式2 遞迴方式: 程式碼如下所示: 步驟: 1)定義一個DiGuiDemo3測試類; 2)在這個類中的main函式中呼叫自定義函式jc2(),5作為函式的引數,使用一個變數result來接收返回的階乘的值,並輸出結果result; 3)自定義jc2()函式接收傳遞的引數5; 4)在自定義函式中書寫if語句判斷n是否小於等於1,如果小於等於1,則使用return返回1; 5)否則n>1時,使用return返回n * jc2(n - 1);
package cn.xuexi.digui.demo;
/*
* 方式2:使用遞迴思想來解決5的階乘
*方式一: 5!=5*4*3*2*1;
*方式二:5!=5*4!
* 4!=4*3!
* 3!=3*2!
* 2!=2*1!
* 1!=1*0!
*找規律:n!=n*(n-1)!
*找結束條件:
* if(n<=1) return 1;
*/
public class DiGuiDemo3 {
public static void main(String[] args) {
// 呼叫函式求5的階乘
int result=jc2(5);
System.out.println(result);//120
}
//自定義函式求5的階乘
public static int jc2(int n) {
// 結束條件
if(n<=1)
{
return 1;
}
return n*jc2(n-1);
}
}
上述程式碼記憶體圖解如下所示:
2.png
遞迴注意事項
1)遞迴必須有結束條件,否則棧記憶體會溢位,稱為死遞迴!棧炸了。
3.png
棧記憶體溢位報的異常如下所示:
4.png
2)遞迴次數不能太多,否則棧溢位。炸了
5.png
棧記憶體溢位報的異常如下所示:
6.png
3)建構函式不能遞迴,記憶體溢位,編譯直接報錯。
7.png
總結:遞迴容器容易導致記憶體溢位。即使遞迴呼叫中有結束條件,但是如果遞迴的次數太多,也會發生記憶體溢位。
所以在開發中使用函式的遞迴呼叫時需謹慎。
遞迴練習
斐波那契數列或者叫做黃金分割數列或者叫做兔子數列: 不死神兔問題:有1對兔子,從出生的第3個月開始,每個月都生1對兔子,假如兔子都不死,問第n個月有幾對兔子。 斐波那契數列思想的圖解如下圖所示:
8.png
斐波那契數列思想如下所示: 找規律: 月份(n): 1 2 3 4 5 6 7 8 9 10 ..... 兔子對數(f): 1 1 2 3 5 8 13 21 34 55 ..... f(n) = f(n-1) + f(n-2)
找出口: if(n <= 2) return 1;
程式碼實現如下所示: 分析和步驟: 1)定義一個測試類DiguiDemo4 ; 2)在DiguiDemo4類中呼叫自定義函式countRabbits(),指定月份作為引數,就是想看第幾個月有多少對兔子,使用一個整數變數接收返回來的兔子的對數num,輸出列印結果; 3)自定義函式countRabbits()根據使用者指定的月份輸出對應月份的兔子的對數; 4)使用判斷結構判斷月份n是否小於等於2,如果是返回1對; 5)如果月份大於2,分別遞迴呼叫函式countRabbits(n-1)+countRabbits(n-2),並將兔子的對數返回給呼叫者;
package cn.xuexi.digui.demo;
/*
* 遞迴練習,斐波那契數列
* 月份 : 1 2 3 4 5 6 7 8 9 10.。。。
* 兔子對數: 1 1 2 3 5 8 13 21 34 55.。。。
* 求第n個月的兔子對數
*/
public class DiGuiDemo4 {
public static void main(String[] args) {
// 定義一個函式計算兔子的對數
int num=countRabbits(6);//6表示月份
//輸出第n個月兔子的對數
System.out.println(num);
}
//自定義函式統計第n個月兔子的對數
public static int countRabbits(int n) //n表示月份
{
// 結束條件
if(n<=2)
{
return 1;
}
//規律
return countRabbits(n-1)+countRabbits(n-2);
}
}
綜合練習
練習1:掃描D:\test所有子資料夾及子子資料夾下的.jpg檔案,輸出其絕對路徑 需求:掃描D:\test所有子資料夾及子子資料夾下的.jpg檔案,輸出其絕對路徑。 分析: 首先我們可以拿到D:\test下的所有兒子,我們判斷兒子是不是資料夾,如果是,再次掃描兒子的資料夾,然後獲取兒子下面的所有子檔案和子資料夾,即就是D:\test的孫子,然後我們再判斷孫子是不是資料夾等等,以此類推,最後我們輸出其絕對路徑;
思路: A:封裝父目錄的File物件; B:獲取父目錄下的所有兒子的File陣列; C:迴圈遍歷,獲取每個兒子; D:判斷是否是資料夾 是:回到步驟B 繼續獲取孫子 否:判斷是否是.jpg 結束遞迴的條件 是:列印 否:不管 我們發現:B到D的過程是不斷重複。我們可以封裝成遞迴的函式。
步驟: 1)建立測試類FileTest1; 2)在FileTest1類的main函式中封裝父目錄D:\test的物件parent; 3)呼叫遞迴掃描的函式scanFolders(parent); 4)自定義函式scanFolders(),使用父目錄物件parent呼叫listFiles()函式獲得父目錄下所有兒子的File陣列files; 5)迴圈遍歷,獲取每個兒子物件file; 6)使用file物件呼叫isDirectory()函式判斷是否是資料夾; 7)如果是資料夾,則遞迴呼叫scanFolders(file)函式; 8)如果不是資料夾,肯定是檔案,使用file物件呼叫getName()函式獲得檔案的全名,呼叫endsWith()函式判斷後綴名是否是.jpg,如果是輸出檔案所屬的絕對路徑;
package cn.xuexi.file.test;
import java.io.File;
/*
* 需求:掃描D:\\test所有子檔案及子子檔案下的.jpg檔案,輸出其絕對路徑。
* 思路:
* A:建立父目錄
* B:呼叫函式查詢檔案或者資料夾
* C:通過父目錄物件呼叫函式獲取所有兒子的File陣列
* D:迴圈遍歷所有的兒子,dir表示父目錄D:\\test的每一個兒子
* a:判斷獲取的兒子dir是否是資料夾 如果是 執行步驟B
* b:不是,判斷後綴名是否是.jpg,是,輸出絕對路徑
*
* 我們發現:B到D的過程是不斷重複。我們可以封裝成遞迴的函式。
*/
public class FileTest1 {
public static void main(String[] args) {
//封裝父目錄的物件
File parent = new File("D:\\test");
//呼叫函式查詢檔案或者資料夾
scanFolderAndFile(parent);
}
//自定義函式掃描檔案或者資料夾
public static void scanFolderAndFile(File parent) {
//通過父目錄物件呼叫函式獲取所有兒子的File陣列
File[] dirs = parent.listFiles();
//迴圈遍歷所有的兒子,dir表示父目錄D:\\test的每一個兒子
for (File dir : dirs) {
/*
* 判斷獲取的兒子dir是否是資料夾
* 如果是資料夾,那麼繼續掃描或者查詢兒子下面的所有檔案或者資料夾
* 以此類推
* 如果不是資料夾,那麼肯定是檔案,判斷後綴名是否是.jpg
* 如果是.jpg 則輸出其絕對路徑
*/
if(dir.isDirectory())
{
//說明是資料夾 繼續找兒子下面的檔案或者資料夾 執行掃描函式
scanFolderAndFile(dir);
}else
{
/*
* 說明不是資料夾,是檔案,我們判斷是否是.jpg
* dir.getName()表示獲取檔案的名字 mm.jpg
*/
if(dir.getName().endsWith(".jpg"))
{
//說明檔案的字尾名是.jpg 輸出其絕對路徑
System.out.println(dir.getAbsolutePath());
}
}
}
}
}
帶健壯性的程式碼:
上述程式碼不安全, 1)比如在呼叫scanFolders(parent)函式時,如果parent變成null,那麼scanFolders(File parent) 接收引數時,parent也為null,就會報空指標異常; 判斷parent是否為null,如果為null就拋異常; 2)比如建立父目錄物件時,指定的父目錄在計算機中不存在,那麼拋異常; 使用parent物件呼叫exit()函式判斷建立的資料夾是否存在,如果不存在,則拋異常。 3)比如建立父目錄物件時,指定的父目錄是檔案,那麼拋異常; 使用parent物件呼叫isFile()函式,如果是檔案,則拋異常。
說明:除了上述三個問題,其他程式碼和上述程式碼一樣。
程式碼如下:
//自定義函式掃描資料夾
public static void scanFolderAndFile(File parent)
{
//判斷parent是否為null
if(parent==null)
{
throw new RuntimeException("不能傳遞null");
}
//判斷資料夾是否存在
if(!parent.exists())
{
throw new RuntimeException("資料夾不存在");
}
//傳遞的是資料夾,不能是檔案
if(parent.isFile())
{
throw new RuntimeException("只能是資料夾");
}
// 獲取父目錄下面的所有兒子
File[] files = parent.listFiles();
//遍歷上述陣列
for (File file : files) {
/*
* 如果file物件中封裝的是資料夾,那麼可以把此資料夾當成父類
* 在掃描其下面的兒子
*/
if(file.isDirectory())
{
//說明獲得的是資料夾 再次回到scanFolderAndFile()函式處執行該函式
scanFolderAndFile(file);
}else
{
/*
* 說明file中封裝的是檔案
* 獲得檔名字,然後判斷後綴名是否是.jpg
* 如果是,則獲取絕對路徑
*/
boolean boo = file.getName().endsWith(".jpg");
if(boo)
{
//說明後綴名是.jpg的檔案 輸出絕對路徑
System.out.println(file.getAbsolutePath());
}
}
}
}
練習2:使用過濾器實現練習 演示:需求:掃描D:\test所有子資料夾及子子資料夾下的.jpg檔案,輸出其絕對路徑。 要求:使用過濾器實現。 思路: A:建立父目錄的File物件 B:獲取父目錄下的符合條件的兒子 條件是什麼? 可以是資料夾 也可以是.jpg檔案 C:迴圈遍歷,取出每個兒子 取出的兒子有兩種情況:資料夾、.jpg檔案 D:判斷是否是資料夾 是:回到B 否:列印 B到D封裝成遞迴函式
步驟: 1)建立測試類FileTest2; 2)在FileTest2類的main函式中封裝父目錄D:\test的物件parent; 3)呼叫遞迴掃描的函式scanFolders(parent); 4)自定義函式scanFolders(),使用父目錄物件parent呼叫listFiles(new FileFilter())函式獲得父目錄下所有兒子的File陣列files; 5)使用過濾器FileFilter介面的匿名內部類作為listFiles()函式的引數,匿名內部類實現accept函式; 6在accept函式體中使用父目錄的兒子物件呼叫isDirectory()函式和getName().endsWith(".jpg")函式來判斷父目錄 的兒子是否是資料夾或者字尾名是.jpg的檔案; 7)使用判斷結構判斷files陣列是否有檔案或者資料夾; 8)如果有,則迴圈遍歷陣列files; 9)使用file物件呼叫isDirectory()函式判斷是否是資料夾; 10)如果是資料夾,則遞迴呼叫scanFolders(file)函式; 11)如果不是資料夾,肯定是檔案,使用file物件呼叫getName()函式獲得檔案的全名,呼叫endsWith()函式判斷後綴名是否是.jpg,如果是輸出檔案所屬的絕對路徑;
package cn.xuexi.file.test;
import java.io.File;
import java.io.FileFilter;
/*
* 思路:
A:建立父目錄的File物件
B:獲取父目錄下的符合條件的兒子
條件是什麼?
可以是資料夾 也可以是.jpg檔案
C:迴圈遍歷,取出每個兒子
取出的兒子有兩種情況:資料夾、.jpg檔案
D:判斷是否是資料夾
是:回到B
否:列印
B到D封裝成遞迴函式
*/
public class FileTest3 {
public static void main(String[] args) {
// 封裝父目錄的物件
File parent = new File("D:\\test");
// 呼叫函式查詢檔案或者資料夾
scanFolderAndFile(parent);
}
public static void scanFolderAndFile(File parent) {
// 獲取父目錄下的符合條件的兒子 這裡需要使用過濾器
File[] files=parent.listFiles(new FileFilter() {
public boolean accept(File pathname) {
// 條件是資料夾或者字尾名是.jpg的檔案
return pathname.isDirectory() || pathname.getName().endsWith(".jpg");
}
});
//迴圈遍歷,取出每個兒子
for (File file : files) {
// 判斷是否是資料夾
if(file.isDirectory())
{
//遞迴呼叫函式
scanFolderAndFile(file);
}else
{
System.out.println(file.getAbsolutePath());
}
}
}
}
1、具有1-5工作經驗的,面對目前流行的技術不知從何下手,
需要突破技術瓶頸的可以加。
2、在公司待久了,過得很安逸,
但跳槽時面試碰壁。
需要在短時間內進修、跳槽拿高薪的可以加。
3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,
常用設計思想,常用java開發框架掌握熟練的,可以加。
4、覺得自己很牛B,一般需求都能搞定。
但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。
5. 群號:高階架構群 Java進階群:180705916.備註好資訊!送架構視訊。
6.阿里Java高階大牛直播講解知識點,分享知識,
多年工作經驗的梳理和總結,帶著大家全面、
科學地建立自己的技術體系和技術認知!