序列化和反序列化
- 序列化:Serialize ,将内存中的Java对象存储到文件中的过程
Java实现序列化有两种方式:
- 实现serializable接口:直接实现Serializable接口的类是JDK自动把这个类的对象序列化
- 实现externaliazble接口:如果实现public interface Externalizable extends Serializable的类则可以自己控制对象的序列化,建议能让JDK自己控制序列化的就不要让自己去控制
transient关键字修饰的属性是透明的,序列化时候就不需要考虑这个属性了
-
反序列化:DeSerialize,将文件中的数据重新读取到内存中,恢复成Java对象的过程
-
Java是通过什么机制来确定是否是一个类的?
第一,首先通过类名来确定,类名不一样,肯定不是一个类。
第二,类名一样,通过序列化版本号进行区分。
IO流
IO流的简介和分类
通过IO可以完成硬盘文件的读和写,IO流又称输入输出流,输入和输出均是以内存作为参照。IO流按照流的方向可以分为:输入(读),输出(写)。按照读取数据方式不同进行分类:
-
字节流: 按照字节的方式读取数据,一次读写一个字节(Byte),等同于一次读取8个二进制位,这种流是万能的,什么文件都可以读取,包括:文本文件,图片,声音文件,视频文件等。
例如: a中国bo账单fe
第一次读取,一个字节,刚好读取‘a’,
第二次读取,一个字节,读取”中“字符的一半,
第三次读取,一个字节,读取”中“字符的另一半 -
字符流: 按照字符的方式读取数据,一次读取一个字符,这种流是为了方便普通文本文件而存在的,字符流不能读取图片、声音、视频文件。只能读取存文本文件。
例如:a中国bo账单fe
第一次读取,‘a’字符,
第二次读取,‘中’字符 -
节点流、包装流(处理流):
节点流:当一个流的构造方法需要一个流的时候,这个被传入的流就叫做节点流。
包装流(处理流):外部负责包装的流就叫做包装流,也称为处理流。
包装流关闭资源时候,只需要关闭外部包装流即可,内部节点流会自动关闭。
Java IO流四大家族
Java IO有四大家族,均是抽象类,分别是:
java.io.InputStream :字节输入流
java.io.OutputStream: 字节输出流
java.io.Reader:字符输入流
java.io.Writer:字符输出流
所有流均实现了java.io.Closeable接口,都是可关闭的,都有close()方法。
所有输出流也实现了java.io.Flushable接口,都是可刷新的, 都有flush()方法,输入流输出之后,一定要进行flush()操作,保证将管道、通道中剩余未输出的数据输出完成,清空管道。
Java中类名以”Stream“结尾的都是字节流,以”Reader、Weiter“结尾的都是字符流。
要掌握的16个流
文件专属
java.io.FileInputStream,字节输入流
任何形式文件都能读取,但是一个字节一个字节的读取,内存和硬盘的交互太频繁了,所以一般采取一次读取一个字节数组(byte[])的方式进行读取。
int read():一次读取一个字节,读取长度为-1时候,就是最后一次读取
int read(byte[] b): 一次读取b.length个字节,当读取长度小于b.length时候,就是最后一次读取
int available(): 返回六种剩余没有读取的字节数量
long skip(long n):跳过几个字节不读
FileInputStream fileInputStream = null;
try{
fileInputStream = new FileInputStream("C:\\Users\\Administrator\\Desktop\\temp.txt");
// 准备读取byte数组,一次读取1024个字节
byte[] bytes = new byte[1024];
// 定义读取长度
int readCount;
while((readCount = fileInputStream.read(bytes))!=-1) {
System.out.println(new String(bytes,0,readCount));
}
}catch (Exception e) {
e.printStackTrace();
}finally {
// 关闭流
if(fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
java.io.FileOutputStream,字节输出流,
负责写,从内存到硬盘,为了减少内存和硬盘的交互,一般也是采用一次写入一个字节数据(byte[])方式进行写入。以不清空源文件的方式写入,要选择追加的方式写入们需要在创建对象时候设置参数true,用来表示追加写入。
FileOutputStream fileOutputStream = null;
try{
// 设置true 表示追加
fileOutputStream = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\temp.txt",true);
// 定义写入数组
//byte[] bytes = new byte[1024];
String s = "FileOutputStreamDemo" + Calendar.getInstance();
fileOutputStream.write(s.getBytes());
}catch (Exception e) {
e.printStackTrace();
}finally {
try {
fileOutputStream.flush();
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
java.io.FileReader(文件字符输入流)
FileReader reader = null;
try{
// 创建文件字符输入流
reader = new FileReader("C:\\Users\\Administrator\\Desktop\\temp.txt");
// 开始读
char[] chars = new char[10];
int readCounts = 0;
while((readCounts = reader.read(chars)) != -1){
System.out.print(new String(chars,0,readCounts));
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
java.io.FileWriter(文件字符输入流)
FileWriter out = null;
try {
// 初始化一个文件字符输出流对象
out = new FileWriter("C:\\Users\\Administrator\\Desktop\\temp03.txt",true); // 不想清空一直累加的话 可以在此处加true
// 开始写
char[] chars = {'我','是','中','国','人'};
// 刷新
out.write(chars);
out.write(chars,0,2);
out.write("我是一名java工程师!");
}catch (Exception e) {
e.printStackTrace();
}finally {
try {
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
转换流(将字节流转换成字符流)
-
java.io.InputStreamReader
-
java.io.OutputStreamWriter
缓冲流专属
java.io.BufferedReader,带有缓冲区的字符输入流
使用的时候不需要创建数组了,自带缓冲区
FileReader fileReader = null;
BufferedReader bufferedReader = null;
try{
fileReader = new FileReader("C:\\Users\\Administrator\\Desktop\\temp01.txt");
// 当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做: 节点流.
// 外部负责包装的这个流,叫做:包装流,还有一个名字叫做 : 处理流
// 像当前这个程序来说: FileReader就是一个节点流。BufferedReader就是包装流/处理流。
bufferedReader = new BufferedReader(fileReader);
String s ;
while((s = bufferedReader.readLine())!=null){
System.out.println(s);
}
}catch (Exception e ) {
e.printStackTrace();
} finally {
// 关闭流
// 对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭!
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
java.io.BufferedWriter,带有缓冲去的字符输出流
BufferedWriter bw = null;
try{
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\temp04.txt",true)));
bw.write("hello world");
bw.write("\n");
bw.write("xxliao");
}catch (Exception e){
e.printStackTrace();
}finally {
try {
bw.flush();
// 关闭最外层
bw.close();
}catch (Exception e) {
e.printStackTrace();
}
}
java.io.BufferedInputStream
java.io.BufferedOutputSteam
数据流专属
java.io.DataInputStream,数据字节写入流
对于DataOutputStream写入的文件,只能使用DataInputStream去读取,并且读的时候需要提前知道写入的顺序,只有写和读取的顺序一致,才能正确取出数据。
DataInputStream dis = new DataInputStream(new FileInputStream("data"));
// 开始读
Byte b = dis.readByte();
Short s = dis.readShort();
int i = dis.readInt();
Float f = dis.readFloat();
Boolean sex = dis.readBoolean();
Double d = dis.readDouble();
System.out.println(b);
System.out.println(s);
System.out.println(i);
System.out.println(f);
System.out.println(sex);
System.out.println(d);
java.io.DataOutputStream,数据字节输出流
这个流可以将数据、以及该数据的数据类型一同写入文件,但是这个文件不是普通的文本文件。
// 创建数据专属的字节输出流
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));
// 写数据
byte b =100;
short s =200;
int i =300;
long l = 400L;
float f =3.0f;
double d = 3.14;
boolean sex = false;
char c = 'a';
// 写
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.writeFloat(f);
dos.writeBoolean(sex);
dos.writeDouble(d);
dos.flush();
dos.close();
标准输出流
java.io.PrintWriter,标准字节输出流
默认输出到控制台上,也就是System.out.println()方法
PrintStream ps = null;
try {
// 指向一个日志文件
ps = new PrintStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\log.txt",true));
// 改变流的输出方向
System.setOut(ps);
// 获取系统当前时间
Date nowTime = new Date();
// 日期格式化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime = sdf.format(nowTime);
System.out.println(strTime+": "+msg);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
ps.close();
}
java.io.PrintStream
对象流
java.io.ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students"));
// 开始反序列化 从硬盘中读到内存当中
Object obj = ois.readObject();
// 反序列化回来是一个学生对象 所以会调用学生对象的toString方法
System.out.println(obj);
ois.close();
ObjectInputStream oos = new ObjectInputStream(new FileInputStream("users"));
// 自己测试的时候 用 instanceOf 判断过返回的对象是 list集合
List<User> userList = (List<User>)oos.readObject();
for(User user : userList){
System.out.println(user);
}
oos.close();
java.io.ObjectOutputStream
// 实现可序列化的接口 接口中无任何内容 只是一个标志性接口
public class Student implements Serializable {
/**
* Java虚拟机看到Serializable接口后,会自动生成一个序列化版本号
* 这里没有手动写出来,java虚拟机会吗,默认提供这个序列化版本号
* 建议将序列化版本号写出来,不建议自动生成
*/
private static final long serialVersionUID = 1L; // 手动写出序列化版本号
private int no;
private String name;
// 过了很久,Student这个类源代码改动了
// 源代码改动之后,需要重新编译,编译之后生成了全新的字节码文件。
// 并且class文件再次运行的时候,java虚拟机生成的序列化版本号也会发生相应的改变。
private int age;
}
// 创建java对象
Student s = new Student(111, "张三");
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students"));
// 序列化对象
oos.writeObject(s);
// 刷新
oos.flush();
// 关闭
oos.close();