对象的序列化(持久化)
就是为了保存在内存中的各种对象的状态,并且可以把保存的对象状态再读出来。
虽然你可以用你自己的各种各样的方法来保存Object States,但是Java给我们提供了一种应该比自己更好的保存对象状态的机制,那就是序列化。
简单说:把对象转换为字节序列的过程称为对象的 序列化。
把字节序列恢复为对象的过程称为对象的 反序列化。
操作对象
ObjectInputStream与ObjectOutputStream(两个类要一起使用)
被操作的对象需要实现Serializable (标记接口)以启用其序列化功能
该接口没有方法;没有方法的接口通常称为标记接口
writeObject();、readObject();操作对象
示例:通过序列化操作一个Person类
import java.io.*;
class ObjectStreamDemo
{
public static void main(String[] args) throws Exception
{
// writeObj();
readObj();
}
public static void readObj() throws Exception //反序列化
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
Person p = (Person)ois.readObject();//类型强转,引用了另一个。java文件的Person类,所以会抛异常ClassNotFoundException
System.out.println(p);
ois.close();
}
public static void writeObj() throws IOException //序列化
{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("xiaoqiang",22,"America"));
oos.close();
}
}
Person类
被操作的对象
import java.io.*;
class Person implements Serializable //标记接口
{
public static final long serialVersionUID = 42L;//1,自定义UID
private String name;//1,私有化的成员
transient int age;//3,修饰符,不会被序列化
static String country ="cn";//2,静态成员
Person(String name, int age, String country){
this.name = name;
this.age = age;
}
public String toString(){ //伪代码,没写set,get
return name+" : "+age+" : "+counrty;
}
}
1,将Person的成员私有化后就不能再被序列化,说明序列号是根据成员获取的
可以设置UID固定标识:ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
设置后Person的成员即使被私有化也能被序列化
2,静态的成员不能被私有化,例如静态的成员country序列化修改后依然是cn
因为序列化操作的是堆内存中的对象,而静态成员随着类加载,存在于栈中的方法区,所以执行不到
3,如果想让非静态成员不被序列化,就在该成员前面加上transient修饰
管道流
PipedInputStream和PipedOutputStream
一般流的输入输出没有太大关联,而管道流输入输出可以直接进行连接,必须相互连接后创建通信管道;通过结合多线程使用,单线程可能会死锁。
示例:
import java.io.*;
class Read implements Runnable //管道输入流线程
{
private PipedInputStream in;
Read(PipedInputStream in){ //构造函数
this.in = in;
}
public void run(){ //实现run方法
try{
byte[] buf = new byte[1024];//缓冲数组
System.out.println("阻塞...数据读取中...");
int len = in.read(buf); //字节输入流读取方法
System.out.println("读到数据,阻塞结束");
String s = new String(buf,0,len);//转成字符串
System.out.println(s);
in.close();
}
catch (IOException e){
throw new RuntimeException("管道流read读取失败!!!");
}
}
}
class Write implements Runnable //管道输出流线程
{
private PipedOutputStream out;
Write(PipedOutputStream out){ //构造函数
this.out = out;
}
public void run(){ //覆盖run方法
try{
System.out.println("开始写入...等待中。。。");
Thread.sleep(3000);
out.write("hello Piped !".getBytes());//字节读取流write方法,需要将字符转为字节
out.close();
}
catch (Exception e){
throw new RuntimeException("管道流write输出失败");
}
}
}
class PipedStreamDemo
{
public static void main(String[] args) throws IOException
{
PipedInputStream in = new PipedInputStream();
PipedOutputStream out=new PipedOutputStream(in);
// in.connect(out);//connect()创建通信管道,也可直接将in传入out构造参数列表
new Thread(new Read(in)).start();
new Thread(new Write(out)).start();
}
}
运行结果:
由运行结果可以看出:管道流输入输出两个线程在执行的过程中,输入流线程如果没有读到输出流的数据,就会一直处于阻塞状态,直到输出流write完毕输入流才开始read。
如果是单线程,极有可能会线程死锁,所以要和多线程配合使用。
RandomAccessFile
随机读写文件该类不算是IO体系的子类,直接继承自Object;
但RandomAccessFile是IO包中成员,具备文件读写功能,能够读写是其内部封装了字节输入流和输出流
能够随机是因为在内部封装了一个byte[] 数组,通过指针对数组的元素进行操作,同时可以通过getFilePointer获取指针位置,通过 seek改变指针位置。seek相当强大
通过其构造函数可以看出:该类只能操作文件
而且还有操作文件的模式:只读r,读写rw 等
如果模式为r,不会创建文件,而是读取一个已存在的文件,若该文件不存在会抛出异常
如果模式为rw,操作的文件不存在,会自动创建,如果存在则不会覆盖,而是直接在该文件上操作
示例:
import java.io.*;
class RandomAccessFileDemo
{
public static void main(String[] args) throws IOException
{
// writeFile(1);
// readFile(0);
writeFile_2(1);
}
public static void readFile(int x) throws IOException //随机获取
{
RandomAccessFile raf = new RandomAccessFile("raf.txt","r");//"r"只读权限
// raf.seek(8*x); //调整对象中指针,一个人的信息为8个字节,可以通过8的倍数来决定取哪个人的资料
raf.skipBytes(8*x);//跳过指定的字节数,只能往后跳
byte[] buf = new byte[4];
raf.read(buf); //一次读取四个字节,因为设置的名字是由2个字符(4个字节)组成
String name = new String(buf);//将该字节数组转换成字符串
int age = raf.readInt();//直接一次读取四个字节,并转成int型
sop("name: "+name);
sop("age: "+age);
raf.close();
}
public static void writeFile_2(int x)throws IOException //随机写入
{
RandomAccessFile raf = new RandomAccessFile("raf.txt","rw");
raf.seek(8*x); //调整指针,直接将信息插入到第五个信息栏,也可对指定位置信息修改(覆盖原有信息)
raf.write("王五".getBytes());
raf.writeInt(110);
raf.close();
}
public static void writeFile() throws IOException
{
RandomAccessFile raf = new RandomAccessFile("raf.txt","rw");//"rw"权限,读写
raf.write("小强".getBytes());//将字符转成字节
raf.writeInt(98); //write只写int型的最低八位(一个字节),该类提供了writeInt()方法,写入4个字节
raf.write("小明".getBytes());
raf.writeInt(99);
raf.close();
}
public static void sop(Object obj){
System.out.println(obj);
}
}
RandomAccessFile类具备强大的随机读写功能,可以对数据进行分段录入,如果引入多线程技术,可以大大提高效率,实际应用:P2P多线程下载
该类要做重点掌握!
一、专门操作基本数据类型的流对象
DataInputStream与DataOutputStream
import java.io.*;
class DataStreamDemo
{
public static void main(String[] args) throws IOException
{
// writeData();
// readData();
// writeUTFDemo();
readUTFDemo();
}
public static void readUTFDemo() throws IOException{
DataInputStream dis = new DataInputStream(new FileInputStream("utfData.txt"));
String s = dis.readUTF();
sop(s);
dis.close();
}
public static void writeUTFDemo() throws IOException{ //以 UTF-8 修改版格式写入此 String 的基本数据。
DataOutputStream dos = new DataOutputStream(new FileOutputStream("utfData.txt"));
dos.writeUTF("小强");
dos.close();
}
public static void readData() throws IOException{ //读取基本数据类型
DataInputStream dis = new DataInputStream(new FileInputStream("Data.txt"));
int n = dis.readInt();
boolean b = dis.readBoolean();
double d = dis.readDouble();
sop("n= "+n);
sop("b= "+b);
sop("d= "+d);
dis.close();
}
public static void writeData() throws IOException{ //写入基本数据类型
DataOutputStream dos = new DataOutputStream(new FileOutputStream("Data.txt"));
dos.writeInt(22); //4个字节
dos.writeBoolean(true); //1个字节
dos.writeDouble(3.1415926535);//8个字节
dos.close();
}
public static void sop(Object obj){
System.out.println(obj);
}
}
二、用于操作字节数组的流对象
用流的读写思想操作数组ByteArrayInputStream:在构造时需要接受数据源,数据源是一个字节数组
ByteArrayOutputStream:在构造时不用定义数据目的,因为该对象内部封装了可变长度的字节数组,该数组就是数据目的地。
因为这两个流对象都操作数组,并没有操作系统资源,所以不用close()关闭
流操作规律
源: 键盘System.in、硬盘FileStream、内存ArrayStream
目的: 控制台System.our、硬盘FileStream、内存ArrayStream
writeTo(OutputStream out);
示例:
import java.io.*;
class ByteArrayStream
{
public static void main(String[] args) //操作的是数组,一般不用抛异常
{
ByteArrayInputStream bais = new ByteArrayInputStream("ABCDEFG".getBytes());//数据源
ByteArrayOutputStream baos =new ByteArrayOutputStream(); //数据目的
int by = 0;
while ((by=bais.read()) !=-1){
baos.write(by);
}
System.out.println(baos.size()); //返回缓冲区大小length长度
System.out.println(baos.toString()); //将缓冲区字节
// baos.writeTo(new FileOutputStream("a.txt")); //该方法调用了底层资源,会抛异常
}
三、操作字符数组
CharArrayReader与CharArrayWrite
四、操作字符串
StringReader 与 StringWriter
这两个类与ByteArrayStream类似