首页 > 代码库 > JAVA学习第五十六课 — IO流(十)对象的序列化和反序列化 & RandomAccessFile

JAVA学习第五十六课 — IO流(十)对象的序列化和反序列化 & RandomAccessFile

操作对象

ObjectInputStream、ObjectOutputStream

被操作的对象需要实现Serializable(标记接口)

ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。

对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态和非静态字段的值。其他对象的引用(瞬态和静态字段除外)也会导致写入那些对象。可使用引用共享机制对单个对象的多个引用进行编码,这样即可将对象的图形恢复为最初写入它们时的形状。  

对象的序列化

	public static void writeobj()throws IOException {
		
		//如果是txt文件的话,是一堆。。。,一般后缀名都是object
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.object"));
		//很多框架都是存储的,创建会很麻烦,一般都是读取
		oos.writeObject(new Perman("a",1));//仅仅是把对象存储出硬盘,让其生命周期延长
		oos.close();
		//完成了对象的序列化,被序列化的对象必须实现Serializable
		
	}

注意writeObject写入的:

将指定的对象写入 ObjectOutputStream。对象的类、类的签名,以及类及其所有超类型的非瞬态非静态字段的值都将被写入。


对象的反序列化

存的目的是为了读。FileInputStream可以读出来对象的数据,但是无法拼成一个对象

ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。

也就是说:ObjectInputStream只能读ObjectOutputStream写入的

readobject()一次,读一个对象

public static void readobj()throws IOException, ClassNotFoundException {
		
		//如果是txt文件的话,是一堆。。。,一般后缀名都是object
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.object"));
		
		Perman p = (Perman)ois.readObject();//必须抛异常
		
		System.out.println(p.getName()+":"+p.getAge());
		ois.close();
	}

一般用于特定的数据库连接对象,不想建立,想留存的对象


关于Serializable

序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。

一个实现该接口后,对象会被序列化,带着一个ID号,当类改变时(private int age : public int age),读取的时候,会判断接收的类对象(修改后)与读取到的类对象(修改前)是否是同一个版本,不是会抛异常。InvalidClassException,而ID号是根据类的特征和签名,完成了一个ID号的定义


PS:Serializable接口作用:用于给被序列化的类加入ID,判断和对象是否是同一个版本


如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。 


可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的long 型字段)显式声明其自己的 serialVersionUID:

 ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
任意访问修饰符

class Perman implements Serializable
	{
		private static final long serialVersionUID = 845645641123231l;//瞎写+l
		//如果对象想要序列化,就必须实现接口,标记
		private String name;
		private int age;
		//.....
		
	}
	public static void readobj()throws IOException, ClassNotFoundException {
		
		//如果是txt文件的话,是一堆。。。,一般后缀名都是object
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.object"));
		
		Perman p = (Perman)ois.readObject();//必须抛异常
		
		System.out.println(p.getName()+":"+p.getAge());
		ois.close();
	}


这样修改后,类改变也没有问题,因为ID号没变,照样可以读取出来,存储的对象


关键字transient(暂时的)

在写入对象的时候,对象中的一些数据不想写如硬盘,使用该关键字

class Perman implements Serializable
{
	private static final long serialVersionUID = 845645641123231l;//瞎写+l
	
	private transient String name;
	private int age;
       ......
}

transient:非静态的数据,被序列化


IO包的其他类:

RandomAccessFile:随机访问文件,自身具备读写的方法。

通过skipBytes(int x),seek(int x)来达到随机访问。

管道流:PipedInputStream 和 PipedOutputStream

输入输出可以直接进行连接,通过集合线程使用


RandomAccessFile

不是IO体系中的子类。

特点:

1.既能读,又能写

2.该对象中内存维护了一个数组,通过指针可以操作数组中的元素

3.可以通过getFilePointer方法获取指针的位置,通过seek方法设置指针的位置

4.因为是byte数组,其实该对象就是字节输入流和输出流封装

5.局限性,从构造函数可以看出,源和汇只能是文件

此类的实例支持对随机访问文件读取写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过getFilePointer 方法读取,并通过 seek 方法设置。 

构造方法摘要
RandomAccessFile(File file,String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。
RandomAccessFile(String name,String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。

mode 参数指定用以打开文件的访问模式。允许的值及其含意为:

含意

"r"以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException
"rw"打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
"rws"打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
"rwd" 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。 
写入

	public static void RandomAccessFileDemo() throws IOException {
		
		//文件不存在,创建,存在,不创建
		RandomAccessFile raf = new RandomAccessFile("ran.txt", "rw");//抛异常
		raf.write("ASD".getBytes());
		//使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
		raf.writeInt(97);//可以写基本数据类型字节,int占4个字节  三个空格+a
		raf.close();
	}

读取和随机读取

public static void read()throws IOException {
		RandomAccessFile raf = new RandomAccessFile("ran.txt", "r");
		
		byte[] by = new byte[6];
		raf.read(by);
		
		String name = new String(by);
		int t = raf.readInt();
		
		System.out.println("name"+name);
		System.out.println("t = "+t);
		raf.seek(2*7);//设置指针的位置,可以实现随机读取
		System.out.println("pointer : "+raf.getFilePointer());
		
		raf.close();
	}
	public static void RandomAccessFileDemo() throws IOException {
		
		//文件不存在,创建,存在,不创建
		RandomAccessFile raf = new RandomAccessFile("ran.txt", "rw");//抛异常
		raf.write("阿萨德".getBytes());
		//使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
		raf.writeInt(97);//可以写基本数据类型字节,int占4个字节  三个空格+a
		raf.write("阿德".getBytes());
		raf.writeInt(98);
		raf.close();
	}

随机写入和细节


public static void randomwrite() throws IOException{
		RandomAccessFile raf = new RandomAccessFile("ran.txt", "rw");//抛异常
		/*raf.write("覆盖".getBytes());
		raf.write(102);
		raf.close();//如果直接写,会覆盖原来的     阿萨的值
		*/
		raf.seek(3*8);//想往哪写就写,也就意味着可以修改某个值
		//因为有些人名的字数不同,但是最多16字节	
		raf.write("覆盖".getBytes());
		raf.write(102);
		raf.close();
		//PS:一般是集合多线程同时往文件中写入数据,控制每个线程写入不同位置的信息(t1线程写[1-100]字节内,t2线程写[101-200]字节)
	}




JAVA学习第五十六课 — IO流(十)对象的序列化和反序列化 & RandomAccessFile