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());
            }
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,358评论 0 17
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,320评论 8 265
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 先让我感慨一下 一个人该是有多么的爱另一个人,或者说,有多么的绝望。能一个人追踪凶手从一个城市,到另一个城市,再转...
    饕餮思文阅读 733评论 2 3
  • 北方隆冬的窗外实在没什么看头,尤其是我窗外的景色,除了挡眼的楼房之外,再就是愈渐将地面挤得水泄不通却几乎个个顶着满...
    简小佛阅读 287评论 0 0