1. IO流
1)概述:I 即 Input/read;O 即 Output/write。IO流主要是用于实现数据(文件)的读写(以二进制的形式)。
2)分类:
· 按照流的方向(以内存为参照物)
往内存中去的称为Input(Read);
从内存中出来称为Output(Write);
· 按照一次提取的是字符/字节:
一次读取一个字节的称为字节流;
一次读取一个字符的称为字符流;
3)Java中的IO流:
Java中的所有IO流都已经写好,并存放在java.io.*下。
四大IO流(父类):
· java.io.InputStream // 字节输入流
· java.io.OutputStraem // 字节输出流
· java.io.Reader // 字符输入流
· java.io.Writer // 字符输出流
注意:
1)在Java中以Stream结尾的都是字节流,以上的四大IO流都是抽象类; 2)所有的流都实现了 java.io.Closable 接口,都是可关闭的(重写了close()方法);由于流是一个介于内存和硬盘之间的管道,用完之后一定要关闭,否则会耗费很多资源。
3)所有的输出流(OutputStream/Writer)都实现了java.io.flushable 接口,都是可刷新的(重写了flush()方法);一个比较好的习惯是,在输出流最终输出之后,通过调用flush()方法刷新一下,这个刷新表示将管道中未输出的数据强行输出完(即清空管道),不刷新可能导致数据丢失。
比较重要的流:
文件专属流:
java.io.FileInputStream
java.io.FileOutputStream
java.io.FileReader
java.io.FileWriter
对象专属流:
java.io.ObjectInputStream
java.io.ObjectOutputStream
注意:
1)FileInputStream/FileOutputStream是字节流、实体流,一般用来处理二进制文件,通过path(路径)的方式指定目录/文件(数据源);
2)ObjectInputStream/ObjectInputStream是装饰流,装饰流不能直接处理数据源,而是需要与其他的装饰流/实体流一起使用,例如ObjectInputStream对象就常用FileOutputStream对象作为构造方法的输入参数。ObjectInputStream/ObjectInputStream最重要的功能是通过序列化与反序列化实现数据的持久化。
2. File类
1) 概述:
File类的父类是Object类,File类不能完成文件的读和写;
File类对象代表的是文件和目录的抽象表示形式(路径),一个File对象既可以是目录,也可以是文件。
例如:
C:\Drivers 即为一个File对象(目录)
C:\Drivers\Lan\Realtek\Readme.txt 也是一个File对象(文件)
2)File的常用方法:
· 构造方法 File(String PathName)
· boolean exists() // 判断File对象是否存在
· boolean createNewFile() // 将File对象以文件的形式创建出来
· boolean mkdir() // 将File对象以目录的形式创建出来
· String getName() // 获取File对象的文件名
· boolean isDirectory() // 判断File对象是不是目录
· boolean isFile() // 判断File对象是不是文件
· String getAbsolutePath() // 获取File对象的绝对路径
· File[] listFiles() // 获取当前目录下所有的子文件
3. 序列化与反序列化
1)什么是序列化与反序列化:
序列化(Serialize)就是将一对象及其状态转换为字节序列(二进制),保存起来(可以保存在数据库,内存,文件等),ObjectOutputStream负责序列化,然后可以在适当的时候再将其状态恢复(也就是反序列化,Deserialize),ObjectInputStream负责反序列化。
2)为什么要进行序列化:
· 持久化:在Java中对象是保持在“堆”中的,堆是一个内存空间,不能长期保持,程序关闭对象就会被回收。但是有时候,我们是需要保存对象里面的信息的,这个时候就需要对对象进行持久化,对象序列化其实就是持久化。
· 微服务:各个服务之间调用对象就需要把对象序列化用于在网络上传输。
· 传输:Java 对象本质上是class 字节码,很多机器都不能根据这个字节码识别出该 Java 对象。但是序列化是序列成二进制流,二进制在计算机世界是通用的。
3)涉及的API:
· java.io.ObjectOutputStream(对象输出流),它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
· java.io.ObjectInputStream(对象输入流),它的readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。
4)序列化的步骤:
· 序列化:
// 序列化一个Person对象
public static void main(String[] args) throws IOException {
//创建java对象(java对象必须支持Serializable接口才可以序列化)
Person p = new Person("Tony",25,'M');
//序列化
//在当前路径下创建一个名为name的文件并传入对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("preson1"));
//序列化对象
oos.writeObject(p);
//刷新
oos.flush();
//关闭
oos.close();
}
· 反序列化:
// 反序列化Person对象
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建反序列化流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("preson1"));
// 开始反序列化
// readObject() 以Object对象的形式返回流中的对象
Object obj = ois.readObject();
// 反序列化回来以后是一个Preson对象,会调用Person对象的toString方法
System.out.println(obj);
// 关闭
ois.close();
}
5)可序列化接口 Serializable
只有当一个类实现了 Serializable 接口时,这个类的实例才是可序列化的。Serializable 接口是一个标识接口,用于标识一个对象是否可被序列化,该接口不包含任何数据域和方法。但是Serializable接口起到了标识的作用,java虚拟接看到这个类实现了该接口之后,会自动生成一个序列化版本号。
如果试图对一个没有实现 Serializable 接口的类的实例进行序列化,会抛出 NotSerializableException 异常。
特点:如果某个类能够被序列化,其子类也可以被序列化。声明为static和transient类型的成员数据(transient关键字只能修饰数据域,不能修饰类/方法)不能被序列化。因为static代表类的状态, transient代表对象的临时数据(可以用transient声明不需要被序列化的数据域)。
如果一个对象的类实现了 Serializable 接口,但是包含一个不可序列化的数据域,则该对象不可序列化。为了使该对象可序列化,需要给不可序列化的数据域加上关键字 transient。
6)serialVersionUID:
作用:java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常。
生成方法:
· JVM编译时默认生成:根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段;
· 手动设置:手动在代码中设置serialVersionUID,比如:private static final long serialVersionUID = 1L;
核心功能:保证版本兼容性,当类的实例Serialized存到硬盘上面后,可是后来你却更改了该类的数据域field(增加或减少或改名),将硬盘上的数据Deserialize时,就会出现Exception的,这样就会造成不兼容性的问题。但当serialVersionUID相同时,它就会将不一样的field以type的预设值Deserialize,可避开不兼容性问题。