序列化和反序列化

/ 默认分类 / 0 条评论 / 849浏览

一.前言

内存是计算机组成中运行速度很快的部分,所以我们的程序都是从磁盘先拷贝到内核缓冲区,之后从内核缓冲区拷贝到用户进程缓冲区,这里的缓冲区可以理解为内存,然后才开始执行,但是内存有一个特点,断电或者杀死相应的进程后,如果数据没有保存的操作就会丢失,但是磁盘不会,数据如果拷贝到磁盘后,没有主动清除磁盘上的数据,数据是会一直存在的,所以当我们的程序在内存中运行时,比如下面这段程序:

        Student student = new Student("xiaoming");
        System.out.println(student);

打印结果

Student{name='xiaoming', id='null', sex='null'}

程序执行完毕后,jvm中没有任何一个非守护线程,所以jvm退出,此时刚刚的student对象就丢失了,那要怎样保存student对象,保证它在程序结束后也不会丢失呢? 一个对象在程序的运行过程中产生,该对象可以理解为一种数据结构,要对对象中的属性(数据)进行持久化保存,可以通过orm框架存储到数据库,在需要的时候再次从数据库中查询封装到对象中,其实这也是对象存储的方式,并且这种方式也是具备对象和存储数据直接的转换,当然我们今天说的序列化技术,也是支持对象序列化为字节序列进行存储以及从字节序列反序列化为对象。

当然,对象的存储持久化只是序列化技术的一个小的作用,在web开发中,序列化主要的作用还是在web数据传输方面.说到网络数据传输的数据格式,我们可以想到的有xml,json,protobuffer等,这些都是常用的数据传输格式,也是一种数据的序列化方式,也是一种数据序列化和反序列化协议(方法).

上面说了对象的存储持久化可以使用序列化和反序列化技术,那么在网络传输方面是怎样体现的呢?

在网络数据传输过程中,如果我们希望将本机上运行的进程的对象传递给另一台云服务机器上运行的进程,并且让云服务机器上的程序可以使用该对象,那么这个过程就需要序列化和反序列的支持,首先数据的传输都是走的二进制数据流,怎样保证对象转为字节序列传输给另一个进程并且另一个进程还能解析该字节序列还原为原来的对象呢?如果两个进程都是java程序,那么就可以使用java中的序列化和反序列化技术。

二.java中的序列化

1. java对象持久化存储

在jdk中可以使用Serializable接口来实现

        ////程序运行过程中在堆内存产生的对象数据
        Student student = new Student("xiaoming");
        System.out.println(student);
        //连接本地文件的输出流
        OutputStream outputStream = new FileOutputStream("F:\\file\\2\\src\\test\\java\\com\\1\\test\\test2.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        //将对象序列化写入文件
        objectOutputStream.writeObject(student);
        //从本地文件读取的输入流
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("F:\\file\\2\\src\\test\\java\\com\\1\\test\\test2.txt"));
        //将本地文件中的序列化数据读取出来并反序列化为对象
        Object o = objectInputStream.readObject();
        System.out.println((Student)o);

可以看到java对象的序列化持久化存储和反序列化为对象

2. java对象网络传入

下面使用的程序时之前写的传统io服务端和客户端程序修改的

public class MyClient {
    public static void main(String[] args)  {
        Student student = new Student("zuohui");
        Socket socket = null;
        for (int i = 0; i < 5; i++) {
            try{
                socket = new Socket("127.0.0.1",8080);
                System.out.println(socket+"客户端已经连接上服务端,准备发送数据...");
            }catch(Exception e)
            {
                System.out.println(socket+"服务端sync queue已满,"+e.getMessage());
            }
            Socket finalSocket = socket;
            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                    //获取连接服务端的输出流
                    OutputStream outputStream = finalSocket.getOutputStream();
                    ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
                    //向服务端发送序列化后的对象
                    objectOutputStream.writeObject(student);
                    System.out.println(finalSocket+"客户端已经发送数据完毕");
                    finalSocket.close();
                } catch (Exception e) {
                    System.out.println(finalSocket+"客户端发送数据失败,"+e.getMessage());
                }
            }).start();
        }
    }
}
public class MyServer {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("服务端已创建");
        while (true){
            System.out.println("等待客户端连接");
            Socket socket = serverSocket.accept();
            System.out.println("客户端已经连接,新开线程处理");
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName()+"线程准备读取客户端数据");
                    //获取连接到客户端socket的输入流  
                    InputStream inputStream = socket.getInputStream();
                    ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
                    //读取客户端发来的数据并反序列化为对象
                    Student student = (Student) objectInputStream.readObject();
                    System.out.println(student);
                    System.out.println(Thread.currentThread().getName()+"读取数据段数据完毕");
                } catch (IOException | ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
Client: 
Socket[addr=/127.0.0.1,port=8080,localport=49699]客户端已经连接上服务端,准备发送数据...
Socket[addr=/127.0.0.1,port=8080,localport=49700]客户端已经连接上服务端,准备发送数据...
Socket[addr=/127.0.0.1,port=8080,localport=49701]客户端已经连接上服务端,准备发送数据...
Socket[addr=/127.0.0.1,port=8080,localport=49702]客户端已经连接上服务端,准备发送数据...
Socket[addr=/127.0.0.1,port=8080,localport=49703]客户端已经连接上服务端,准备发送数据...
Socket[addr=/127.0.0.1,port=8080,localport=49701]客户端已经发送数据完毕
Socket[addr=/127.0.0.1,port=8080,localport=49702]客户端已经发送数据完毕
Socket[addr=/127.0.0.1,port=8080,localport=49703]客户端已经发送数据完毕
Socket[addr=/127.0.0.1,port=8080,localport=49699]客户端已经发送数据完毕
Socket[addr=/127.0.0.1,port=8080,localport=49700]客户端已经发送数据完毕



Server: 
服务端已创建
等待客户端连接
客户端已经连接,新开线程处理
等待客户端连接
Thread-0线程准备读取客户端数据
客户端已经连接,新开线程处理
等待客户端连接
Thread-1线程准备读取客户端数据
客户端已经连接,新开线程处理
等待客户端连接
Thread-2线程准备读取客户端数据
客户端已经连接,新开线程处理
等待客户端连接
Thread-3线程准备读取客户端数据
客户端已经连接,新开线程处理
等待客户端连接
Thread-4线程准备读取客户端数据
Student{name='zuohui', id='null', sex='null'}
Student{name='zuohui', id='null', sex='null'}
Thread-1读取数据段数据完毕
Student{name='zuohui', id='null', sex='null'}
Thread-0读取数据段数据完毕
Student{name='zuohui', id='null', sex='null'}
Thread-4读取数据段数据完毕
Student{name='zuohui', id='null', sex='null'}
Thread-3读取数据段数据完毕
Thread-2读取数据段数据完毕

可以看到,java对象通过序列化和反序列化的方式在两个进程之间传输

ObjectOutputStream是可以列化写出对象的输出流,ObjectInputStream是可以反序列化读取出对象的输入流

上面使用的就是java的序列化和反序列化方式,但是这种方式有几个致命缺点

所以,在java中对象传输一般都是使用的其他序列化工具,像xml,json,protobuffer都是属于数据序列化的一种方式

ps: 上面的Student需要实现Serializable接口,在java中Serializable接口没有任何方法和变量,按照经验这就是一个标识接口,类似于Cloneable接口,只有实现了该接口的类才支持使用java序列化和反序列化

三.xml和json以及protobuf

xml是一种通用的重量级数据交换格式,是以文本形式存储的
json是一种通用的轻量级数据交换格式,也是以文本形式存储的 protobuf是一种独立的轻量级数据交换格式,是以二进制文件形式存储的

通用的表示该协议属于属于标准的开放协议,任何语言可以直接实现,比如java使用fastjson序列化对象后可以被c++的程序使用jsoncpp进行反序列化出对象
xml序列化后的对象,有很多冗余的标识,使得数据占用空间很大,json相对来说体积小很多,他们都是以文本形式存储的,并且序列化后具有很好的可阅读性

protobuf使用的是私有协议,只有google的protoc支持,该框架可以根据编写的.proto文件生成适用于不同语言的代码文件,来进行对象的序列化和反序列化
但是其是将对象序列化为二进制数据,占用空间更小,传输速度更快

文本文件打开方式和二进制打开方式的区别: 首先无论哪种存储打开方式,存在磁盘上的数据都是以二进制的形式存在的,只不过文本形式是将数据的终端形式作为二进制数据进行存储,二进制形式是指将数据在内存中的原数据存储在磁盘上.文本形式只能是字符,而二进制形式可以是int,short等,二进制存储的文件大小会比文本形式占用空间小,比如一个字符65531,使用文本形式,只能是字符,需要占用5个字节,如果使用二进制存储,只需要一个字节.