类序列化存储与类原生存储比较

类序列化存储与类原生存储比较

        Java序列化就是将一个对象转化成一串二进制表示的字节数组,通过保存或转移这些字节数据来达到持久化的目的。进行序列化,实质是保存对象的当前状态信息,不需要保存完整的结构信息。

        Java类原生存储就是将对象映射到数据库的关系上,一个实体类对应数据库中的一张表表,类中的成员变量对应数据库中的字段,通过数据库这个容器来达到数据持久化的目的。

以上两种方式均能达到数据持久化的目的,那么在性能上二者是否存在差异?

        我们将通过一个对比实验来验证二者在性能上是否存在差异,设计一个具有12个属性的Student类并实现可序列化接口,并分别设计其相应的数据库表和存储其序列化的表,在对数据库存取记录的过程中观察二者在时间效率和空间效率上的差异。

实现序列化接口的Student类


import java.io.Serializable;
import java.util.Date;

/*
 * 实现可序列化接口
 */
public class Student implements Serializable{
    private Integer id;

    private String name;

    private int age;

    private int gender;

    private String dept;

    private String tel;

    private String email;

    private String hobby;

    private String school;

    private double wallet;

    private int zodiac;

    private String address;

    public Student() {
    }

    public Student(Integer id, String name, int age, int gender, String dept, String tel, String email, String hobby, String school, double wallet, int zodiac, String address) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.dept = dept;
        this.tel = tel;
        this.email = email;
        this.hobby = hobby;
        this.school = school;
        this.wallet = wallet;
        this.zodiac = zodiac;
        this.address = address;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getGender() {
        return gender;
    }

    public void setGender(int gender) {
        this.gender = gender;
    }

    public String getDept() {
        return dept;
    }

    public void setDept(String dept) {
        this.dept = dept;
    }

    public String getTel() {
        return tel;
    }

    public void setTel(String tel) {
        this.tel = tel;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getHobby() {
        return hobby;
    }

    public void setHobby(String hobby) {
        this.hobby = hobby;
    }

    public String getSchool() {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }

    public double getWallet() {
        return wallet;
    }

    public void setWallet(double wallet) {
        this.wallet = wallet;
    }

    public int getZodiac() {
        return zodiac;
    }

    public void setZodiac(int zodiac) {
        this.zodiac = zodiac;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                ", dept='" + dept + '\'' +
                ", tel='" + tel + '\'' +
                ", email='" + email + '\'' +
                ", hobby='" + hobby + '\'' +
                ", school='" + school + '\'' +
                ", wallet=" + wallet +
                ", zodiac=" + zodiac +
                ", address='" + address + '\'' +
                '}';
    }
}

存储实体类的student表结构

create table if not exists student (
	id int primary key,
	name varchar(10),
	age int,
	gender int,
	dept varchar(10) not null,
	tel varchar(11) unique,
	email varchar(18) unique,
	hobby varchar(10),
	school varchar(10) not null,
	wallet double(10,2),
	zodiac int,
	address varchar(20)
);

存储序列化的objtest表结构

create table if not exists objtest(
    id int primary key,
    obj blob not null
);

        

        设计DBHelper类来实现数据库连接池的操作,其功能实现有数据连接池配置、回收、数据库表插入、数据库表查询、数据库表清空和返回表在内存中所占的空间大小。

        

DBHelper类

        数据库连接池配置

public class DBHelper {
    private static Connection conn;
    private static PreparedStatement pres;
    private static ResultSet rs;
    private static ResourceBundle bundle = ResourceBundle.getBundle("jdbc");

    static {
        try {
            Class.forName(bundle.getString("Driver"));
            String url = bundle.getString("url");
            String user = bundle.getString("user");
            String password = bundle.getString("password");

            conn = DriverManager.getConnection(url, user, password);
            if (conn != null) {
                System.out.println("数据库连接成功");
            } else System.out.println("数据库连接失败");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

        数据库连接池回收

public static void free(Connection conn, PreparedStatement pres, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (pres != null) {
            try {
                pres.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

        学生对象序列化存储

public static void save(List<Student> students){
        long startTime = System.currentTimeMillis();
        String sql = "insert into objtest(obj) values(?)";
        try {
            pres=conn.prepareStatement(sql);
            for(int i = 0; i < students.size(); i ++){
                pres.setObject(1, students.get(i));
                pres.addBatch();
            }
            pres.executeBatch();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            free(conn, pres, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("序列化存储运行时间:" + (endTime - startTime));
    }

        学生对象序列化查询、反序列化

public static List<Student> read(){
        long startTime = System.currentTimeMillis();
        List<Student> list = new ArrayList<Student>();
        String sql="select obj from objtest";

        try {
            pres = conn.prepareStatement(sql);

            rs = pres.executeQuery();
            while(rs.next()){

                Blob blob = rs.getBlob(1);
                InputStream is = blob.getBinaryStream();
                BufferedInputStream buffer = new BufferedInputStream(is);

                byte[] buff = new byte[(int) blob.length()];
                while((buffer.read(buff, 0, buff.length)) != -1) {
                    ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buff));
                    Student student = (Student) in.readObject();
                    list.add(student);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            free(conn, pres, rs);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("序列化读取运行时间:" + (endTime - startTime));
        return list;
    }

        学生对象原生存储

public static void saveStudent(List<Student> students) {
        long startTime = System.currentTimeMillis();
        String sql = "insert into student(id, name, age, gender, dept, tel, email, hobby, school, wallet, zodiac, address) values (?,?,?,?,?,?,?,?,?,?,?,?)";
        try {
            pres = conn.prepareStatement(sql);
            for (Student student : students) {
                pres.setInt(1, student.getId());
                pres.setString(2, student.getName());
                pres.setInt(3, student.getAge());
                pres.setInt(4, student.getGender());
                pres.setString(5, student.getDept());
                pres.setString(6, student.getTel());
                pres.setString(7, student.getEmail());
                pres.setString(8, student.getHobby());
                pres.setString(9, student.getSchool());
                pres.setDouble(10, student.getWallet());
                pres.setInt(11, student.getZodiac());
                pres.setString(12, student.getAddress());
                pres.addBatch();
            }
            pres.executeBatch();

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("直接存储运行时间:" + (endTime - startTime) + "ms");
    }

        学生对象原生查询

public static List<Student> readStudent() {
        long startTime = System.currentTimeMillis();
        List<Student> list = new ArrayList<Student>();
        String sql = "select * from student";

        try {
            pres = conn.prepareStatement(sql);
            rs = pres.executeQuery();
            while (rs.next()) {
                Integer id = rs.getInt("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");
                int gender = rs.getInt("gender");
                String dept = rs.getString("dept");
                String tel = rs.getString("tel");
                String email = rs.getString("email");
                String hobby = rs.getString("hobby");
                String school = rs.getString("school");
                double wallet = rs.getDouble("wallet");
                int zodiac = rs.getInt("zodiac");
                String address = rs.getString("address");
                Student student = new Student(id, name, age, gender, dept, tel, email, hobby, school, wallet, zodiac, address);
                list.add(student);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("直接读取运行时间:" + (endTime - startTime) + "ms");
        return list;
    }

        清空数据库表

public static void cleanTable() {
        String sql1 = "truncate table student";
        String sql2 = "truncate table objtest";
        try {
            pres = conn.prepareStatement(sql1);
            pres.executeUpdate();
            pres = conn.prepareStatement(sql2);
            pres.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            free(conn, pres, null);
        }
    }

        查询记录所占内存空间

public static void getMemory(String tableName) {
        double memory = 0;
        int rows = 0;
        String sql = "SELECT TABLE_NAME,DATA_LENGTH+INDEX_LENGTH memory,TABLE_ROWS FROM TABLES WHERE TABLE_SCHEMA='jdbc' AND TABLE_NAME=?";
        try {
            pres = conn.prepareStatement(sql);
            pres.setString(1, tableName);
            rs = pres.executeQuery();
            if (rs.next()) {
                memory = rs.getDouble("memory");
                rows = rs.getInt("TABLE_ROWS");
            }
            System.out.println("表中记录数: " + rows + " 占用内存空间:" + memory / 1024 + "KB");
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

        

实验过程

        设计Test测试类分别对不同记录数的学生实体类进行重复插入和查询,通过观察其在数据库中存储和查询的过程中所消耗时间戳进行对照实验。

        

实验样本

        设置五组记录数分别为1、5、200、1000、5000、10000条,重复存储和查询5次,对运行时间和内存空间取平均值。

        

        Test类

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int count = scanner.nextInt();
        List<Student> list = new ArrayList<>();
        DBHelper.cleanTable(); // 清空数据库表
        for (int i = 1; i <= count; i++) {
            list.add(new Student(i, "zhangsan", 18, 1, "计算机系", "10086", "[email protected]", "basketball", "温州大学", 3.8, 5, "温州大学计算机与人工智能学院"));
        }
        System.out.println("记录数为 "+ count +"条时:");
        DBHelper.save(list); // 学生类序列化存储
        DBHelper.saveStudent(list); // 学生类对象存储
        DBHelper.read(); // 学生类序列化查询
        DBHelper.readStudent(); // 学生类对象查询
    }
}

        

实验结果 

存储数据
记录数/条 原生(平均时间/ms) 序列化(平均时间/ms)
1 10.8 27.2
200 666.4 734.2
1000 3038.4 3211.8
5000 14080.8 14384.2
10000 27909.4 28020.4
查询数据
记录数/条 原生(平均时间/ms) 序列化(平均时间/ms)
1 4.6 24.0
200 48.2 95.0
1000 120.2 195.6
5000 226.0 440.6
10000 320.4 759.4

内存空间
记录数/条 原生(内存大小/kb) 序列化(内存大小/kb)
1 16 16
200 64 64
1000 192 496
5000 1552 2576
10000 2576 5648

           通过实验发现序列化后的数据随着记录数的增加存储所耗费的时间跟原生存储相近,但在查询的过程中其所耗费的时间大约是原生查询的两倍;在内存空间上,随着记录数的增加序列化后的数据所占用的内存也随之增大。由此可见,对于实体对象的持久化存储,原生存储比序列化后存储在效率上表现的更加良好。

        可能造成序列化在效率上差异的原因在于:

        1、类自身序列化和反序列化的过程中需要耗费一定的时间;

        2、序列化后的数据为二进制流,所占用的空间更为庞大(约412bytes/条),在数据库查询时,和查询后跨域回传过程中耗费更多时间。

 个人观点

        对于两种持久化存储方式,直接采用实体类与数据库表之间的映射更加清晰,方便执行各种查询、修改和删除操作,能够轻松的获取所需查询的条件;在数据安全方面,未经过加密处理的实体类在数据库表上的映射更多将以明文的形式显示,而对象经过序列化得到理解的一组难以理解的字节流,但在JVM环境下仍然可以反序列化恢复到原有形式。但在进程间通信,实现进程间的对象传送,序列化后产生字节流在传输就能展现出高效、便捷、可持久化的特性。

 参考文章

(45条消息) MySQL8 版本后 系统表 information_schema.tables 行数与实际数据表行数不准确 处理_AllenLeungX的博客-CSDN博客

猜你喜欢

转载自blog.csdn.net/qq_43500084/article/details/127639090