递归是一种编程思想,对于我这种愚人来说,理解起来实在是够费劲的,可以这样说,至今为止,我依然不懂递归,也只是对它有一点粗略的、表面的了解,实在是惭愧!
递归的概述
递归就是函数自身调用自身,即函数内部又使用到了该函数功能。虽然知道是这么一个意思,但一写代码遇到递归就懵逼了,所以这里我就举几个例子来详述一下它。
递归的应用之一—— 遍历指定目录下的内容(包含子目录中的内容)
现有这样一个需求:列出指定目录下的文件或者文件夹,包含子目录中的内容。也就是列出指定目录下的所有内容。
分析:因为目录中还有目录,只要使用同一个列出目录功能的函数完成即可。在列出过程中出现的还是目录的话,还可以再次调用本功能。也就是函数自身调用自身,这种表现形式或者编程手法,称为递归。
首先定义一个列出目录功能的递归函数,如下:
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删除时是不走回车站的,除此之外,还须注意两点,如下:
- 隐藏目录——无法访问就不能删除,返回的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);
}
}
此综合练习用到了比较多的知识点,做起来还蛮不错的,知识点都串联起来了。