1. 程式人生 > >Java的遞迴、如何與流相結合

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高階大牛直播講解知識點,分享知識,

多年工作經驗的梳理和總結,帶著大家全面、

科學地建立自己的技術體系和技術認知!