Comparação entre armazenamento serializado de classe e armazenamento nativo de classe
A serialização Java é converter um objeto em uma string de matrizes de bytes binários e atingir o objetivo de persistência salvando ou transferindo esses dados de bytes. A essência da serialização é salvar as informações do estado atual do objeto e não há necessidade de salvar as informações estruturais completas.
O armazenamento nativo de classes Java é mapear objetos para o relacionamento do banco de dados. Uma classe de entidade corresponde a uma tabela no banco de dados e variáveis de membro na classe correspondem a campos no banco de dados. O objetivo da persistência de dados é alcançado por meio do contêiner do banco de dados.
Ambos os dois métodos acima podem atingir o objetivo de persistência de dados, então há alguma diferença de desempenho entre os dois?
Faremos um experimento comparativo para verificar se existe diferença de desempenho entre as duas, projetar uma classe Aluno com 12 atributos e implementar uma interface serializável, e projetar sua tabela de banco de dados correspondente e tabela para armazenamento de sua serialização, respectivamente. Observe a diferença em eficiência de tempo e eficiência de espaço entre os dois durante o processo de acesso aos registros do banco de dados.
Classe do aluno que implementa a interface de serialização
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 + '\'' +
'}';
}
}
Estrutura da tabela do aluno para armazenar classes de entidade
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)
);
Armazenar estrutura de tabela de objeto serializado
create table if not exists objtest(
id int primary key,
obj blob not null
);
Projete a classe DBHelper para implementar a operação do pool de conexão de banco de dados. Suas funções incluem configuração do pool de conexão de dados, reciclagem, inserção de tabela de banco de dados, consulta de tabela de banco de dados, esvaziamento de tabela de banco de dados e retorno do espaço ocupado pela tabela na memória.
Classe DBHelper
Configuração do pool de conexões do banco de dados
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();
}
}
Reciclagem do pool de conexão do banco de dados
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);
}
}
}
Armazenamento serializado do objeto do aluno
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));
}
Consulta de serialização de objeto do aluno, desserialização
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;
}
Armazenamento nativo do objeto do aluno
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");
}
Consulta nativa do objeto do aluno
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;
}
Limpar a tabela do banco de dados
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);
}
}
Espaço de memória ocupado por registros de consulta
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);
}
}
procedimento experimental
Projete a classe de teste Test para inserir e consultar repetidamente as classes de entidade do aluno com diferentes números de registros e conduza um experimento de controle observando os timestamps consumidos no processo de armazenamento e consulta no banco de dados.
amostra experimental
Defina o número de registros em cinco grupos como 1, 5, 200, 1.000, 5.000 e 10.000, repita o armazenamento e a consulta 5 vezes e obtenha o valor médio do tempo de execução e do espaço de memória.
classe de teste
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(); // 学生类对象查询
}
}
Resultados experimentais
Número de registros/artigo | Nativo (tempo médio/ms) | Serialização (tempo médio/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 |
Número de registros/artigo | Nativo (tempo médio/ms) | Serialização (tempo médio/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 |
Número de registros/artigo | Nativo (tamanho da memória/kb) | Serialização (tamanho da memória/kb) |
1 | 16 | 16 |
200 | 64 | 64 |
1000 | 192 | 496 |
5000 | 1552 | 2576 |
10000 | 2576 | 5648 |
Através de experimentos, verifica-se que o tempo de armazenamento dos dados serializados é semelhante ao do armazenamento original à medida que o número de registros aumenta, mas o tempo gasto no processo de consulta é cerca de duas vezes o da consulta original; em termos de memória espaço, com o aumento do número de registros, a memória ocupada pelos dados serializados também aumenta. Pode-se ver que, para o armazenamento persistente de objetos de entidade, o armazenamento nativo tem melhor desempenho em eficiência do que o armazenamento serializado.
As possíveis razões para a diferença na eficiência da serialização são:
1. Leva um certo tempo para serializar e desserializar a própria classe;
2. Os dados serializados são um fluxo binário, que ocupa um espaço maior (cerca de 412 bytes/item) e consome mais tempo durante a consulta ao banco de dados e o retorno entre domínios após a consulta.
opinião pessoal
Para os dois métodos de armazenamento persistente, é mais claro usar diretamente o mapeamento entre classes de entidade e tabelas de banco de dados, o que é conveniente para executar várias operações de consulta, modificação e exclusão e pode obter facilmente as condições de consulta necessárias; em termos de segurança de dados, não O mapeamento da classe de entidade criptografada na tabela do banco de dados será exibido na forma de texto simples e o objeto é serializado para obter um conjunto de fluxos de bytes incompreensíveis, mas ainda pode ser desserializado e restaurado ao original na JVM ambiente.Existe forma. No entanto, na comunicação entre processos, a transferência de objetos entre os processos é realizada e o fluxo de bytes gerado após a serialização pode mostrar as características de alta eficiência, conveniência e durabilidade durante a transmissão.