本章目标
- 对象流
- 递归(掌握)
本章内容
一、对象流
如果想在JVM停止后,把这些对象保存到磁盘或者通过网络传输到另一远程机器,怎么办呢?
1、什么是对象流
所谓对象流也就是将对象的内容进行流化,能够输入输出对象的流称为对象流。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间
特点:在java.io包中对象的持续性,能够纪录自己的状态以便将来再生的能力
2、序列化
无论是何种类型的数据,都会以二进制序列形式在网络上传送,发送方需要把这个java对象转换为字节序列,才能在网络上传送,同时接收方则需要把字节序列再恢复为对象。
2.1、序列化分为:
序列化和反序列化。
- 序列化:就是将对象的内容分解成字节流,以便存储在文件中或在网络上传输。
- 反序列化:就是打开并读取字节流,且从该流中恢复该对象
2.2、为什么要序列化
如果想在JVM停止后,把这些对象保存到磁盘或者通过网络传输到另一远程机器,磁盘的硬件和网络等是不能认识Java对象,它们只认识二进制这些机器语言,所以我们就要把这些对象转化为字节数组,这个过程就是序列化。
2.3、如何实现序列化
只有实现java.io.Serializable
接口的类才可以启用序列化功能。未实现此接口的类将无法启用任何状态的序列化和反序列化.
- 如果某个类能够被序列化,其子类也可以被序列化。
static
修饰的字段是不会被序列化的,序列化保存的是对象的状态而非类的状态,static静态域被忽略transient
修饰符修饰的字段不会被序列化。在序列化某个类的对象时,不希望某个字段被序列化(比如这个字段存放的是隐私值,如:密码等),那这时就可以用transient修饰符来修饰该字段
3、对象流:
3.1、对象输入流(ObjectInputStream):
ObjectInputStream in = new ObjectInputStream(输入流对象);
in.readObject();
将需要被序列化的类实现Serializable接口,然后使用一个输出流(如:FileOutputStream等)来构造一个对象输出流ObjectOutputStream对象,使用该对象的writeObject(Object obj)方法,就可以将指定的对象obj写入到文件或传输于网络
3.2、对象输出流(ObjectOutputStream):
ObjectOutputStream out = new ObjectOutputStream(输出流对象);//对象创建成功便会往文件中写入八个字节的内容
out.writeObject(Object obj)
将需要被序列化的类实现Serializable接口,然后使用一个输入流(如:FileInputStream等)来构造一个对象输入流ObjectIntputStream对象,使用该对象的readObject()方法,获得对象输入流中的对象
4、示例
-
构造一个被序列的类
import java.io.Serializable; public class Student implements Serializable { private int id=1; private String name=“lecky”; private int age=27; …… }
-
将对象写在硬盘
public class StudentTest { public static void main(String[] args) { Student student = new Student(); ObjectOutputStream o = null; try { try { o = new ObjectOutputStream(new FileOutputStream(“date.ser”)); o.writeObject(student); } finally { o.close(); } } catch (Exception e) { System.out.println(e); } } }
-
将对象还原
package com.it; import java.io.FileInputStream; import java.io.ObjectInputStream; public class ObjectRecov { public static void main(String args[]) { try { FileInputStream fi = new FileInputStream(“date.ser”); ObjectInputStream si = new ObjectInputStream(fi); Student stu = (Student) si.readObject(); si.close(); fi.close(); System.out.println(stu.getAge()); } catch (Exception e) { System.out.println(e); } } }
二、递归(补充)
折成两个字来理解:递和归
1、递归简介
一个方法在它的方法体内调用它自身的情况称为递归调用,这是一种特殊的嵌套调用,称之为递归方法
1.1、递归应用场景
- 各种数学问题如:8皇后问题﹐汉诺塔,阶乘问题,迷宫问题,球和篮子的问题(google编程大赛)
- 各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等
- 将用栈解决的问题
1.2、遵守的重要规则:
- 递归一定要有结束条件,且向退出递归的条件靠近,否则就是无限递归,出现StackOverflowError(栈内存溢出)
- 虽然递归有结束条件,但是递归次数太多,也会发生栈内存溢出。
- 构造方法,禁止递归(直接编译报错,因为如果允许无限new对象,会导致堆内存溢出。)
1.3、栈帧(了解)
栈帧(stack frame)是在程序执行过程中,用来存储局部变量、函数参数、返回地址和其他与函数执行相关的信息的一种数据结构。栈帧通常与函数调用有关,每当函数被调用时,就会创建一个新的栈帧。
一个栈帧通常包含以下几个重要的组成部分:
- 局部变量:存储函数中定义的局部变量的值。
- 函数参数:存储函数调用时传递的参数值。
- 返回地址:存储函数执行完后需要返回的下一个指令的地址。
- 调用者保存的寄存器:保存调用者函数在调用当前函数之前需要使用的寄存器的值。
- 栈指针:指向当前栈帧的顶部。
当函数执行结束后,栈帧会被销毁,栈指针会回到上一个栈帧的位置,程序继续执行上一个函数的指令。栈帧的创建和销毁过程遵循先进后出的原则,类似于堆栈(stack)数据结构的操作。
1.4、递归执行原理:
- 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的,不会相互影响,比如n变量
- 如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据
- 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕
- 递归函数在实现的过程中,是一个调用栈的过程。函数在调用另一个函数时,当前函数会处于暂定、未完成的状态,暂停函数的所有变量的值仍然会保存在内存中,直到函数执行完毕,所占内存会被弹出栈
2、递归求解1~n的和
2.1、分析
- 根方法中调用时传入的参数是最大的那个值
- 首先我们要找到递归的结束条件是n是否等于1,如果等于1,则返回1
- 其次我们要找到递归的条件是如果n不等于1,返回n加上调用sum(n-1)的结果
2.2、代码实现
public class Main {
public static void main(String[] args) {
int n = 3;
int total = sum(n);
System.out.println(total);
}
public static int sum(int n) {
if (n == 1) {
return 1;
} else {
return n + sum(n - 1);
}
}
}
2.3、总结
1.每次递归的时候,这个方法只执行了一部分,就去执行另一部分了
2.归的时候,会把当前方法剩余部分执行完毕
3.递的次数和归的次数是一样的
4.从栈帧的角度来看:递是进栈,归是出栈
3、计算n的阶乘
n!可用下述公式递归表示:
n! = 1 (n = 0,1)
n! = n × (n-1)! (n>1)
3.1、分析
- 根方法中调用时传入的参数是最大的那个值
- 首先我们要找到递归的结束条件是n是否等于1,如果等于1,则返回1,或者如果等于0,也直接返回1
- 其次我们要找到递归的条件是如果n不等于1,返回n加上调用factorial(n-1)的结果
3.2、代码
public class Main {
public static void main(String[] args) {
int n = 5;
int total = factorial(n);
System.out.println(total);
}
public static int factorial(int n) {
if (n == 1 || n == 0) {
return 1;
} else {
return n*factorial(n - 1);
}
}
}
3.3、总结
4、案例(贯穿项目相关)
读取目录下所有文件
4.1、分析
File
类的对象来表示一个文件或目录。我们可以调用File
类的listFiles()
方法获取一个目录下的所有文件和目录,- 递归结束的条件是判断它是文件还是目录(
file.isDirectory()
),如果是文件,则将其加入到结果List中。 - 递归的条件是,如果是目录,则需要再次递归调用该函数,
- 在递归时使用绝对路径
file2.getAbsolutePath()
4.2、代码
public class Main {
public static void main(String[] args) {
List<String> fileList = new ArrayList<String>();
String dirPath = ".";
getAllFiles(dirPath, fileList);
for (String string : fileList) {
System.out.println(string);
}
}
public static void getAllFiles(String dirPath, List<String> fileList) {
File file = new File(dirPath);
File[] listFiles = file.listFiles();
for (File file2 : listFiles) {
if (file2.isDirectory()) {
getAllFiles(file2.getAbsolutePath(), fileList);
} else {
fileList.add(file2.getName());
}
}
}
}