一,递归
- 概述:方法自己调用自己的现象叫做递归
- 递:传递 传递数据
- 归:回归【回到起点的意思】
- 传递数据到原点。每传递一个数据完成回到原点传递下一个数据;传递动作是重复, 只不过数据发生了变化而已。功能是唯一的。
- 把重复的动作封装成为一个方法,传递数据回归到原点的时候;再次调用方法传入新的数据就可以。这样的操作我们命名为递归。
- 特点描述:数据传递过程中最后传递数据会得到一个固定值,不需要再调用方法了。
- 特点体现:
- 方法自己调用自己。
- 肯定要有一个出口【要让递归停下来】
- 肯定有数据传递
- 弊端:不停的调用方法压栈,需要占用空间,如果内存空间小,递归没有执行完,程序结束
- 好处:代码简单,逻辑容易理解
- 所有的递归都可以被普通的逻辑取代,只不过设计起来比较难,容易错。
-
练习一
- 计算n这个数的1 ~ n的和【曾经求1~n的和使用的循环+计数思想】
- 分析:
n ------> 结果 方法【f(n)】 方法变形 最终变形
1 -----> 1 f(1) 1 1
2 ------> 1+2 f(2) f(1)+2 f(n-1)+n
3 ----------> 1+2+3 f(3) f(2)+3 f(n-1)+n
n -----------> 1+2+3+.....+n f(n) f(n-1)+n f(n-1)+n
n=1的时候返回1,其他的时候返回n加 n-1的累和
代码示例
public class Demo01 {
public static void main(String[] args) {
//method_For();
//使用递归完成累和
int sum = getSum(100);
System.out.println(sum);// 5050
int sum2 = getSum(1);
System.out.println(sum2);// 1
}
//定义一个求数累和的方法
public static int getSum(int n) {
if (n == 1) {
return 1;
}else {
//getSum(n-1)就是上一个数的累和
return getSum(n-1)+n;
}
}
private static void method_For() {
//定义一个计数器
int sum = 0 ;
//使用计数思想【循环】完成1到100的累和
for (int i = 1; i <= 100; i++) {
sum+=i;
}
System.out.println(sum);
}
}
-
练习二
- 计算n的阶乘 例如:5!= 5 * 4 * 3 * 2 * 1
- 分析:
1 1
2 2=1*2 2的累乘是 1的累乘乘以 2
3 6=1*2*3
3的累乘是 2 的累乘 乘以3
每个数的阶乘是使用方法求出的,除了1的阶乘 其他数的阶乘都是上一个数的阶乘*这个数
n (n-1)的阶乘 * n
定义一个求数的阶乘的方法 getMul(int n )
n为1返回1,
其他数的返回 getMul(n-1)*n【上一个数的阶乘乘以数自己 上一个数的阶乘使用方法可以求出来】
代码示例
public class Demo02 {
public static void main(String[] args) {
//调用方法求5的阶乘
int mul = getMul(5);
System.out.println(mul);
}
//求阶乘的方法
public static int getMul(int n) {
if( n==1){
return 1;
}else {
return getMul(n-1)*n;
}
}
}
-
练习三
- 键盘录入一个文件夹路径,打印该文件夹中所有的文件的绝对路径
- 分析:
得到文件夹的file对象
文件夹里面的文件有的是文件有的是文件夹
如果是文件直接输出绝对路径,是文件夹就进一步获取这个文件夹的文件重复的判断里面的文件是不是文件是输出,不是再获取文件判断.....一直到没有文件夹为止
所以可以定义一个方法来获取文件夹文件并判断文件是不是文件,是输出路径,不是重新调用该方法
代码示例
import java.io.File;
import java.util.Scanner;
public class Demo03 {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入文件夹路径:");
String path =sc.nextLine();
File file= new File(path); //创建一个文件对象
printPath(file);
}
//定义一个方法获取文件夹的所有文件并判断是文件就打印绝对路径,不是进一步获取文件
public static void printPath(File file ) {
if(file.isDirectory()) {
//是文件夹的话需要获取文件夹里面的所有文件
File[] files=file.listFiles();
//使用遍历获取每一个文件
for (File file2 : files) {
//如果是文件就输出绝对路径
if(file2.isFile()) {
System.out.println(file2.getAbsolutePath());
}
//不是文件的话就是文件夹,需要重复获取里面的文件进行判断打印绝对路径[本身定义的这个方法就可以完成]
else {
printPath(file2);
}
}
}else {
//是文件直接输出文件的绝对路径
System.out.println(file.getAbsolutePath());
}
}
}
-
练习四
- 使用递归实现一个下列数列的数组
- 1,1,2,3,5,8,13,21,34,55【这样的数叫做斐波那契数;同时也叫作不死神兔规律】
- [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
- 分析:
1 1
2 1
3 2=1+1
4 3=1+2
5 5=3+2
假设每个位置上面的数使用相同方法得到
n ( n-1位置上的数)+(n-2位置上面的数) f(n-1)+f(n-2)
假设求一个位置上面的数定义为一个方法 getNum(int n):方法体内部如何求:
如果n等于 1 或者2 数就是 1
如果是其他的数:就是前一个位置的数和期两个位置的数的和
getNum(n-1)+ getNum(n-2);
代码示例
import java.util.Arrays;
public class Demo04 {
//定义一个方法
public static int getSum(int n) {
if(n==1||n==2) {
return 1;
}else {
return getSum(n-1)+getSum(n-2);
}
}
public static void main(String[] args) {
//创建一个动态数组,数组的长度为10
int [] arr = new int[10];
for (int i = 0; i < arr.length; i++) {
//i表示的索引,i+1表示索引对应的位置的数,索引是从0开始,所以位置数+1
//将产生的数添加到数组里面
arr[i] = getSum(i+1);
}
//Arrays.toString 展示数组中的内容
System.out.println(Arrays.toString(arr));
}
}
-
练习五
- 打印一个文件夹中后缀名全部为.mp4的文件名称
- 分析:
先拿到文件夹的对象
获取文件夹里面的所有文件
对这些文件进行判断
是文件就判断后缀名是不是.mp4
是文件夹调用方法自己
代码示例
import java.io.File;
import java.util.Scanner;
public class Demo05 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入文件夹路径:");
String path = sc.next();
File file = new File(path);
printPath(file); // 调用方法
}
// 定义一个方法
public static void printPath(File file) {
// 判断是不是文件夹
if (file.isDirectory()) {
// 如果是文件夹需要获得里面所有的文件
File[] files = file.listFiles();
// 通过遍历获得每一个文件
for (File file2 : files) {
// 如果是文件
if (file2.isFile()) {
// 判断文件是否是以mp4结尾的文件
if (file2.getName().endsWith(".mp4")) {
// 如果是以mp4结尾的文件,则打印文件夹名称
}
System.out.println(file2.getName());
} else {
// 否则继续查找文件夹
// 使用自己的方法调用方法
printPath(file2);
}
}
} else {
// 如果是文件判断是不是以.mp4结尾的
if (file.getName().endsWith(".mp4")) {
System.out.println(file.getName());
} else {
System.out.println("没有找到符合条件的文件");
}
}
}
}
- 递归:方法自己调用自己
二,IO流【输入输出流】
- 概述: 流可以来承载东西。java中io流也是用来承载运输数据的。
- I: input 的缩写 进入的意思
- O: output 的缩写 出去的意思
- 流: 指流动的物体,可以承载其他的物体,达到转移物体的的效果。
- 字面意思:进出的流,可以承载物体,进行物体转移。在java中转移的东西就是数据可以去转移数据的一种方式。
- 分类:
- 按流向分:参考内存【进入内存叫做输入 出内存叫做输出】
- 输入流:承载数据进入内存的流叫做输入流
- 输出流:承载数据从内存中出来到磁盘中的流叫做输出流
- 按功能分:【操作的数据的组成类型】
- 字节流:流承载的数据是 字节数据类型【操作的数据是字节】
- 字符流:流承载的数据是 字符数据类型【操作的数据是字符】
- 开发使用的时候,这两种分类相互结合的使用
- IO流的基本体系:【以功能为主,以流向为辅】
- 字节流:
- 字节输入流【InputStream】
- 字节输出流【OutputStream】
- 字符流:
- 字符输入流【Reader】
- 字符输出流【Writer】
- 使用流程:
- 导包
- 创建对象
- 调用相应的功能要处理该功能带来的异常
- 关闭流对象【如果不关闭,用的多了占用内存后期会影响程序的运行效率】
三,字节流
- 概述:每次传输的数据是以字节为单位进行传输的流,叫做字节流。
- 一切皆为字节:计算机可以存储各种各样的资源,其本质是使用数字【二进制】,所有的资源对应的都有一个数字在计算机中,这数字就理解为是字节。
-
字节输入流【InputStream】
- 概述:以字节为单位把数据放到内存中的流
- 常用方法:
- read(): 一次读一个字节的数据到内存中
- read(byte[] bytes): 一次使用bytes去读取 多个字节数据到内存中。
- inputStream 是一个字节输入流的顶层抽象类,不能创建对象,他的行为就不能被使用,只能去他的子类对象来使用这些行为。他的子类会根据不能的设备有不一样的子类。常用的子类FileInputStream
-
子类:
-
FileInputStream:
- 概述:他是字节输入流用于内存和磁盘文件之间交互数据的一个字节输入流
- 构造方法:
- FileInputStream(String path):创建一个以path地址为目标的文本字节输入流
- FileInputStream(File file):创建一个以file对象为目标的文本字节输入流
- 常用方法
- read(): 每次读取一个字节到内存中。注意事项:同一个字节输入流对象每次读完字节,光标【指针】向下个位置移动。
- read(byte[] bytes): 每次读取多个字节到数组,再把数组中的字节数一次性读取到内存中。
- 注意事项:
- 使用字节数组读取内容,不能保证每次都能数组读满,取读到的数据的时候只取每次读到的有效数据个数。使用返回值。
- 当读数据读到文章的末尾,再去读取数据由于已经没有数据可以读取了,直接返回-1.【读数据读到的是-1意味着读完了】
- 返回值对比:
- 使用数组读取内容的返回是int 数字,代表的是每次读取到的有效字节个数
- 一次只读取一个字节的时候返回值是int数字,代表的是读到的字节数据本身。
代码示例
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Demo_IO02 {
public static void main(String[] args) throws IOException {
// 创建一个输入流对象,参数就是流将来要做的目标文件,90%的IO流都有异常
FileInputStream fis=new FileInputStream("a/number.txt");
//读取文件中的数据到内存中,number.text里面存入内容:abcdefjhilmn
//变量i来接收流所承载的数据,数据从目标文件里面来
int i= fis.read();
//变量i 存放在内存中
System.out.println(i); //97, 97是从目标文件里面来,文件在磁盘目录下,文件里面的一个a读取到i变量里来了。
int j =fis.read();
System.out.println(j); //98, 98是同一个字节输入流对象每次读完字节,光标【指针】向下个位置移动的输出内容。
/* read()方法一次读到一个字节,返回值就是读到的内容值 */
//定义一个byte数组
byte [] b = new byte[5];
int k = fis.read(b);
System.out.println(k); //5,每次读取多个字节到数组,再把数组中的字节数一次性读取到内存中。
/* read(byte[] b)方法一次读取多个字节,把读到的内容放到了字节数组中,返回值就是读到的内容值,读到几个返回几个 */
//打印数组当中的内容
System.out.println(new String (b,0,k)); //cdefg, 从c开始读取5个有效长度的字符串
int k1= fis.read(b);
System.out.println(k1); //5
System.out.println(new String (b,0,k1)); //hijkl, 从h开始读取5个有效长度的字符串
int k2= fis.read(b);
System.out.println(k2); //2
System.out.println(new String (b,0,k2)); //mn,从零角标开始读只读到两个有效的内容换为字符串
int k3= fis.read(b);
System.out.println(k3); //-1
System.out.println(new String (b,0,k3));//-1意味着文章的数据已经读取完毕了
}
}
-
字节输出流【OutputStream】
- 概述:他是字节输出流的顶层抽象类,他是内存和外界文件进行数据交互的输出流。以字节为操作单位的。
- 常用方法:
- write(int bytes): 一次只写出一个字节的数据到磁盘文件【目标文件】中
- write(byte[] bytes): 一次写出数组中全部的字节的数据到磁盘文件中【借助于字节数组】
- write(byte[] bytes,int starIndex,int len): 一次性写出字节数组中指定的多个字节数据到磁盘文件中,
starIndex: 从数组的那个索引开始【开始的索引】
len: 写多少个字节出去【字节的个数】
- OutputStream 是一个抽象类,不能创建对象,不能直接使用这些行为,只能找他的子类。
- 根据设备的不同会有不同的子类,学习其中的一个 FileOutputStream
-
子类
-
FileOutputStream
- 概述:他是一个 OutputStream 的子类,用于内存和磁盘设备之间进行数据交互的输出流
- 构造方法:
- FileOutputStream(String path):创建以path地址为目标文件的输出流对象
- FileOutputStream(File file):创建以file对象为目标文件的输出流对象
- 基本功能:
- write(int bytes): 一次只写出一个字节的数据到磁盘文件【目标文件】中
- write(byte[] bytes): 一次写出数组中全部的字节的数据到磁盘文件中【借助于字节数组】
- write(byte[] bytes,int starIndex,int len): 一次性写出字节数组中指定的多个字节数据到磁盘文件中,
- starIndex: 从数组的那个索引开始【开始的索引】
- len: 写多少个字节出去【字节的个数】
- 注意事项:使用输出流的时候,创建流对象会去判断指定的目标文件是否存在,存在直接格式化目标文件,不存在,自动给我们创建一个目标文件
代码示例
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo_02 {
public static void main(String[] args) throws IOException {
//创建自己输出流对象
FileOutputStream fos = new FileOutputStream("a/b.txt");// a文件夹下没有b.txt,刷新后就有了b.txt文件
FileOutputStream fos1 = new FileOutputStream("a/number.txt");// a文件夹下面的number.txt的内容消失了被格式化了
/* 创建输出流对象的时候,参数给定的地址或文件对象对应的文件如果没有存在,自动创建文件
* 如果存在,格式化该文件
*/
//写出数据
//写出单个字节数据
fos.write(98); //把98字节对应的内容(b)写到a/b.txt文件中去了
fos.write("我喜欢你".getBytes());//将“我爱你”内容写到a/b.txt文件中去了
byte[] bs= "abcdefg".getBytes();
fos.write(bs,1,5);//bcdef的内容写到a/b.txt文件中去了
byte[] bs1= "我非常喜欢你".getBytes();
System.out.println(bs1.length);
fos.write(bs1,3,9); //非常喜, 以字节个数为操作单位,中文汉字在utf-8中1个汉字为3个字节
//输出换行
fos.write("
".getBytes()); //windows 系统的换行
fos.write(97);
fos.write("
".getBytes()); //Linux 系统的换行(mac)
fos.write(97);
fos.write("
".getBytes()); //Unix 系统的换行
fos.write(97);
fos.close();//关闭流
}
}
-
文件续写
- 构造方法:
- FileOutputStream(Sring path,true): 续写构造
- FileOutputStream(File file,true): 续写构造
- 构造方法的参数加了true,创建输出对象的时候就不会格式化已经存在的目标文件
代码示例:
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo_IO03 {
public static void main(String[] args) throws IOException {
//要把 我是中国人 写到b.txt中去而且保留里面的数据
FileOutputStream fos = new FileOutputStream("a/b.txt",true);//如果是false 就等于默认格式化了
fos.write("我是中国人".getBytes());
fos.close();
}
}
-
文件拷贝
- 复制图片【将一张图片复制到另一个盘符下】
- 分析:
- 复制其实是一个文件中的数据备份一份到另一个文件中,数据不可能直接从一个磁盘空间到另一个磁盘空间中;只能借助于内存,内存使用输入流把文件中的数据读到内存中,再使用输出流把数据写出到另一个文件
- 步骤:
- 创建文件字节输入流、文件字节输出流
- 对文件中的数据进行读取
- 把读取到的数据输出到另一个文件中
- 【由于文件数据一次性可能读取不完,需要多次的读取,需要每读取一次就及时的输出一次。需要循环完成】
代码示例
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyImg {
public static void main(String[] args) throws IOException {
//1、创建文件字节输入流、文件字节输出流
FileInputStream fis = new FileInputStream("img\timg3.jpg");
FileOutputStream fos = new FileOutputStream("a\timg3.jpg");
//2、对文件中的数据进行读取
//定义一个变量来承载每次读到的内容
int i = 0;
while((i = fis.read()) != -1) {// 小括号中是对每次读到的内容进行判断 不是-1 就读到内容需要写出去否则结束复制
//3、把读取到的数据输出到另一个文件中
fos.write(i);// 正确的
//fos.write(fis.read());// 错误的 会出现数据丢失 读了两次写出了第二次的数据
}
fos.close();
fis.close();
}
}
作业:使用一次读取多个字节来复制图片【做不出来不要着急】
- 注意事项:
使用数组复制时:
1、数组的大小不是越大越好,也不是越小越好,开发经验得到一般数组的大限使用1024的倍数,比较常用的大小1024*8这么大。
2、每次写出的时候要写出读到的有效字节个数。