Qt使用sqlite数据库及项目实战

一.sqlite使用介绍

在Qt中使用SQLite数据库非常简单,SQLite是一个轻量级的嵌入式数据库,不需要单独的数据库服务器,完全使用本地文件来存储数据。

当在Qt中使用SQLite数据库时,需要涉及到一些SQL语句以及Qt中的相关函数,接下来我将从头开始介绍所有涉及的语句以及相应的解释。

1. 连接到数据库:addDatabase

在Qt中,你可以使用QSqlDatabase来连接数据库。首先需要添加数据库类型,然后指定数据库的名称:

QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("mydatabase.db");
if(!db.open()) {qDebug() << "无法连接到数据库";return;
}

在这段代码中,我们首先创建了一个名为mydatabase.db的SQLite数据库,然后通过open()函数来打开数据库连接。如果连接失败,将会输出错误信息。

2. 创建表:CREATE TABLE

一般来说,你需要在数据库中创建表来存储数据。这可以通过执行CREATE TABLE语句来实现:

QSqlQuery query;
query.exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)");

这里我们使用exec()函数执行SQL语句,创建了一个名为users的表,包含id、name和age三个列。

3. 插入数据:INSERT

当需要在表中插入新的数据时,可以使用SQL的INSERT INTO语句。在Qt中,可以使用QSqlQuery对象的prepare()和bindValue()函数来执行预处理插入语句:

  • 常规的数据库插入语句
INSERT into users(id,name,age)VALUES(111,"name",12)
  • Qt中
QString name = "Alice";
int age = 25;
QSqlQuery query;
query.prepare("INSERT INTO users (name, age) VALUES (:name, :age)");
query.bindValue(":name", name);
query.bindValue(":age", age);
if(!query.exec()) {qDebug() << "插入数据失败:" << query.lastError().text();
}
  • prepare和bindValue函数介绍

当在Qt中进行数据库操作时,使用query.prepare()和query.bindValue()方法可以帮助你执行预处理SQL语句并绑定参数的值。这两个方法在插入操作中起到了重要作用,下面我将详细介绍它们的功能和作用:

  1. query.prepare():

在Qt中,query.prepare()方法用于准备要执行的SQL语句。它的主要作用是将SQL语句中的参数部分用占位符(如:variable)代替,从而形成一个预处理的SQL语句。通过这种方式,可以避免SQL注入攻击,并提高程序的安全性。

例如,在插入操作中,你可能会使用INSERT INTO table_name (column1, column2, …) VALUES (:value1, :value2, …)这样的SQL语句,其中的:value1、:value2是占位符,用于将实际的数值动态地填充进去。使用query.prepare()方法可以将这个SQL语句准备好,等待绑定参数值。

  1. query.bindValue():

一旦调用了query.prepare()方法准备好了SQL语句,接下来就可以使用query.bindValue()方法为每个占位符绑定具体的数值。这个方法的作用是设置预处理语句中的占位符的值,从而将真实的数据插入到SQL语句中。

例如,在前面的插入操作示例中,我们对name和age这两个占位符使用了query.bindValue()方法,将实际的name和age变量的值绑定到了这两个占位符上。

使用query.bindValue()方法可以方便地将变量的值插入到SQL语句中,同时还可以防止数据类型转换错误和SQL注入攻击。

综上所述,query.prepare()方法用于准备预处理SQL语句,并替换其中的占位符,而query.bindValue()方法则用于将实际的数值绑定到这些占位符上,从而完成对数据库的插入操作。这两个方法的结合使用可以简化代码编写,同时提高程序的安全性和可维护性。

4. 查询数据:SELECT

查询数据最常见的方式是使用SELECT语句。在Qt中,我们可以通过exec()函数执行SELECT语句,并使用next()函数来逐行获取结果:

QSqlQuery query;
query.exec("SELECT * FROM users");
while(query.next()) {int id = query.value(0).toInt();QString name = query.value(1).toString();int age = query.value(2).toInt();qDebug() << "ID:" << id << " Name:" << name << " Age:" << age;
}

这段代码遍历了users表中的所有行,并输出每一行的ID、名称和年龄。

  • query.next介绍

在Qt中,query.next()方法用于遍历查询结果集中的下一行数据。当执行SELECT查询语句并通过exec()方法获取结果集后,可以使用query.next()方法逐行访问查询结果中的数据。

每次调用query.next()方法,会将查询结果集中的指针移到下一行数据。如果存在下一行数据,则该方法返回true,并允许获取下一行数据;如果不存在下一行数据,则返回false,表示已经遍历完所有结果。

一般来说,可以在一个循环中使用query.next()方法来遍历整个查询结果集,逐行获取数据并进行相应的处理。在循环中,通常会结合query.value()方法用于获取特定列的值。

5. 更新数据:UPDATE

在 Qt 中使用 SQLite 实现更新(修改)数据库操作可以通过执行 SQL UPDATE 语句来实现。下面是一个简单的示例代码,演示了如何使用 Qt 和 SQLite 执行更新操作:

#include <QCoreApplication>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);// 连接到SQLite数据库QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");db.setDatabaseName("database.db"); // 指定SQLite数据库文件if (!db.open()) {qDebug() << "Error: Unable to connect to database.";return 1;}// 执行 UPDATE 操作QSqlQuery query;query.prepare("UPDATE student SET name = :newName WHERE id = :id");query.bindValue(":newName", "New Name"); // 设置要更新的新名称query.bindValue(":id", 1); // 设置要更新的记录的 IDif (query.exec()) {qDebug() << "Update successful";} else {qDebug() << "Update failed:" << query.lastError().text();}return app.exec();
}

6. 删除数据:DELETE

使用 QSqlQuery 类删除数据:

QSqlQuery query;
query.prepare("DELETE FROM your_table WHERE condition = :condition");  // 设置删除语句
query.bindValue(":condition", value);  // 绑定条件值
bool success = query.exec();  // 执行删除操作
if(success) {// 删除成功
} else {// 删除失败,处理错误qDebug() << "Error:" << query.lastError().text();
}

7. 更新数据库中的数据:submitAll

  • 这是对模型进行了修改,将数据提交回数据库中,以保持同步。
// 创建 QSqlTableModel 并设置数据库表QSqlTableModel model;model.setTable("student");model.select(); // 从数据库中选择数据// 创建 QTableView 并设置模型QTableView tableView;tableView.setModel(&model);// 创建一个按钮,当点击时提交更新QPushButton updateButton("Update Database");QObject::connect(&updateButton, &QPushButton::clicked, [&model](){if (model.submitAll()) {qDebug() << "Database update successful";} else {qDebug() << "Database update failed:" << model.lastError().text();}});
  • model.submitAll介绍

QSqlTableModel 中的 submitAll() 函数是用于将对模型数据的更改提交到数据库中的方法。当您在 QTableView 中对数据进行了修改、插入或删除等操作后,调用 submitAll() 函数可将这些更改应用到数据库中。

submitAll() 函数会执行以下操作:

  1. 检查模型中的每一行,如果该行的状态为 Inserted、Deleted 或 Modified,则将对应的更改提交到数据库中。
  2. 如果提交成功,会返回 true;如果提交失败,会返回 false。您可以通过检查返回值来确定提交是否成功。
  3. 如果提交失败,您可以调用 model.lastError().text() 来获取详细的错误信息。这将返回一个描述在提交过程中发生的错误的字符串。

在模型视图的结构中,对model是临时修改数据的中转站。

8. 更新QSqlQueryModel中的数据

QSqlQueryModel 是一个只读模型,无法直接编辑数据,因此更新数据的方式不同于可编辑的模型,比如 QSqlTableModel。如果您想要更新 QSqlQueryModel 中的数据,可以重新执行查询语句或者重新设置查询。

以下是更新 QSqlQueryModel 中数据的一种常见方法:

  1. 重新执行查询语句:您可以重新设置 QSqlQueryModel 的查询语句,然后调用 setQuery() 方法重新执行查询。这将重新从数据库中读取数据并更新模型中的数据。
QSqlQueryModel model;
model.setQuery("SELECT * FROM your_table");
  1. 使用 clear() 方法清空数据:如果需要清空 QSqlQueryModel 中的数据,您可以调用 clear() 方法。清空数据后,您可以重新设置查询或数据源,从而实现更新。
model.clear();

需要注意的是,QSqlQueryModel 是一个只读模型,不支持直接编辑数据,如果您需要在界面上进行编辑和更新操作,可以考虑使用 QSqlTableModel 或自定义的 QAbstractTableModel。

9. 对指定列进行排序

  • 这是对模型进行排序
// 创建 QSqlQueryModel 并设置查询语句
QSqlQueryModel *queryModel = new QSqlQueryModel;
queryModel->setQuery("SELECT * FROM your_table");// 创建 QSortFilterProxyModel 并设置源模型为 QSqlQueryModel
QSortFilterProxyModel *sortModel = new QSortFilterProxyModel(this);
sortModel->setSourceModel(queryModel);// 设置排序的列索引
int sortColumn = 1; // 假设要按第1列排序
sortModel->sort(sortColumn, Qt::AscendingOrder); // 进行升序排序// 将排序后的模型设置给视图
yourTableView->setModel(sortModel);
  • 对数据库进行排序
-- 以id列的升序方式进行排序
SELECT id, name, age
FROM your_table
ORDER BY id ASC;SELECT id ,name ,score  FROM student ORDER BY id DESC//对模型中的数据进行排序
model->setQuery("SELECT id, name, score FROM student ORDER BY id DESC");

二.成绩管理系统实战

1. 项目效果

在这里插入图片描述

  • 项目说明:底层存储数据用的是sqlite,界面展示数据用的是tableView和QSqlQueryModel的视图模型模式。

2. 项目源码:

  • 类定义
#ifndef STUDENTDLG_H
#define STUDENTDLG_H#include <QDialog>#include <QSqlDatabase> // 专用于连接、创建数据库
#include <QSqlQuery> // 专用于DML(数据操纵语言)、DDL(数据定义语言)
#include <QSqlQueryModel>//数据库模型,用来和tableview关联
#include <QSqlError>
#include <QDebug>
#include <QMessageBox>QT_BEGIN_NAMESPACE
namespace Ui { class StudentDlg; }
QT_END_NAMESPACEclass StudentDlg : public QDialog
{Q_OBJECTpublic:StudentDlg(QWidget *parent = nullptr);~StudentDlg();private slots:void on_pushButtonSort_clicked();void on_pushButton_INSERT_clicked();void on_pushButton_DELETE_clicked();void on_pushButton_UPDATE_clicked();void on_pushButton_SEARCH_clicked();
private: void CreateDatabaseFunc();  // 创建SQLite数据库void CreateTableFunc();     // 创建SQLite数据表void QueryTableFunc();      // 执行查询操作QSqlDatabase sqldb;         // 创建Qt和数据库链接QSqlQueryModel *model=nullptr;//用来存储数据
private:Ui::StudentDlg *ui;};
#endif // STUDENTDLG_H
  • 类实现
#include "studentdlg.h"
#include "ui_studentdlg.h"
#include<QSortFilterProxyModel>StudentDlg::StudentDlg(QWidget *parent): QDialog(parent), ui(new Ui::StudentDlg)
{ui->setupUi(this);// 调用函数创建且打开数据库CreateDatabaseFunc();// 调用函数创建数据表CreateTableFunc();
}StudentDlg::~StudentDlg()
{delete ui;
}void StudentDlg::CreateDatabaseFunc()  // 创建SQLite数据库
{// 1:添加数据库驱动sqldb=QSqlDatabase::addDatabase("QSQLITE");// 2:设置数据库名称sqldb.setDatabaseName("studentmis.db");// 3:打开此数据库是否成功if(sqldb.open()==true){QMessageBox::information(0,"正确","恭喜你,数据库打开成功!",QMessageBox::Ok);}else{QMessageBox::critical(0,"错误","数据库打开失败,请重新检测!",QMessageBox::Ok);}
}void StudentDlg::CreateTableFunc()     // 创建SQLite数据表
{QSqlQuery createquery;// 创建SQL语句QString strsql=QString("CREATE TABLE IF NOT EXISTS student(""id int primary key not null,""name text not null,""score real not null)");// 执行SQL语句if(createquery.exec(strsql)==false){QMessageBox::critical(0,"失败","数据表创建失败,请重新检查!",QMessageBox::Ok);}else{QMessageBox::information(0,"成功","恭喜你,数据表创建成功!",QMessageBox::Ok);}// 准备数据模型model = new QSqlQueryModel();model->setQuery("SELECT id, name, score FROM student"); // 执行查询// 将数据模型与QTableView绑定//QTableView *tableView = new QTableView();ui-> tableView->setModel(model);ui->tableView->resizeColumnsToContents(); // 调整列宽度使数据能够完全显示// 显示QTableViewui->tableView->show();}
void StudentDlg::QueryTableFunc()      // 执行查询操作
{QSqlQuery query;query.exec("SELECT * FROM users");while(query.next()) {int id = query.value(0).toInt();QString name = query.value(1).toString();double score = query.value(2).toDouble();qDebug() << "ID:" << id << " Name:" << name << " Score:" << score;}}void StudentDlg::on_pushButtonSort_clicked()
{//    QSortFilterProxyModel *sortModel = new QSortFilterProxyModel(this);
//    sortModel->setSourceModel(model);//    // 设置排序的列索引
//    int sortColumn = 1; // 假设要按第1列排序
//    sortModel->sort(sortColumn, Qt::DescendingOrder); // 进行升序排序//    // 将排序后的模型设置给视图
//    ui->tableView->setModel(sortModel);//    //使用sql语句进行操作
//    QSqlQuery query;
//    query.exec("SELECT id ,name ,score  FROM student ORDER BY id DESC");//更新数据库中的数据model->setQuery("SELECT id, name, score FROM student ORDER BY id DESC");//model->setQuery("SELECT * FROM student");ui->tableView->viewport()->update();
}void StudentDlg::on_pushButton_INSERT_clicked()
{QSqlQuery sqlquery;int id=ui->lineEdit_ID->text().toInt();if(id==0){QMessageBox::critical(this,"失败","提示:输入错误?学号不能为0?",QMessageBox::Ok);return ;}QString name=ui->lineEdit_NAME->text();if(name==""){QMessageBox::critical(this,"失败","提示:输入错误?姓名不能为空?",QMessageBox::Ok);return ;}double score=ui->lineEdit_SCORE->text().toDouble();if(score<0 || score>100){QMessageBox::critical(this,"失败","提示:输入错误?分数范围(0-100)?",QMessageBox::Ok);return ;}QString strs=QString("insert into student ""values(%1,'%2',%3)").arg(id).arg(name).arg(score);if(sqlquery.exec(strs)==false){QMessageBox::critical(0,"失败","向数据表插入记录失败,请重新检查!",QMessageBox::Ok);}else{QMessageBox::information(0,"成功","恭喜你,向数据表插入记录成功!",QMessageBox::Ok);}}void StudentDlg::on_pushButton_DELETE_clicked()
{int id = ui->lineEdit_ID->text().toInt();QSqlQuery query;query.prepare("DELETE FROM student WHERE id = :id");  // 设置删除语句query.bindValue(":id", id);  // 绑定条件值bool success = query.exec();  // 执行删除操作if(success) {// 删除成功} else {// 删除失败,处理错误qDebug() << "Error:" << query.lastError().text();}//更新数据库中的数据model->setQuery("SELECT * FROM student");ui->tableView->viewport()->update();
}void StudentDlg::on_pushButton_UPDATE_clicked()
{// 执行 UPDATE 操作QString New_Name=ui->lineEdit_NAME->text();int id=ui->lineEdit_ID->text().toUInt();QSqlQuery query;query.prepare("UPDATE student SET name = :newName WHERE id = :id");query.bindValue(":newName", New_Name); // 设置要更新的新名称query.bindValue(":id", id); // 设置要更新的记录的 IDif (query.exec()) {qDebug() << "Update successful";} else {qDebug() << "Update failed:" << query.lastError().text();}}void StudentDlg::on_pushButton_SEARCH_clicked()
{QSqlQuery query;query.exec("SELECT * FROM student");while(query.next()) {int id = query.value(0).toInt();QString name = query.value(1).toString();double score = query.value(2).toDouble();qDebug() << "ID:" << id << " Name:" << name << " Score:" << score;}//更新数据库中的数据model->setQuery("SELECT * FROM student");ui->tableView->viewport()->update();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/41876.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【海贼王的数据航海】ST表——RMQ问题

目录 1 -> RMQ问题 1.1 -> 定义 1.2 -> 解决策略 2 -> ST表 2.1 -> 定义 2.2 什么是可重复贡献问题 2.3 -> 预处理ST表 2.4 -> 处理查询 2.5 -> 实际问题 1 -> RMQ问题 1.1 -> 定义 RMQ (Range Minimum/Maximum Query)即区间最值查询…

黑马点评DAY2|Redis基本操作

Redis客户端 命令行客户端 进入到redis的安装目录&#xff0c;可以看到redis-cli文件&#xff0c;这就是redis的命令行客户端&#xff0c;在安装redis时自带的。 使用方式如下 redis-cli [options] [commonds]其中常见的options有&#xff1a; -h 127.0.0.1 &#xff1a;指…

电量监测与电量计基础知识

硬件之路学习笔记 ​-----前文导读----- ①、公众号主页点击发消息 ②、点击下方菜单获取系列文章 -----本文简介----- 主要内容包括&#xff1a; ①&#xff1a;简介 ②&#xff1a;省成本方式-电阻分压 ③&#xff1a;精确方式-电量计与阻抗跟踪技术 ----- 正文 ----…

JavaWeb--jquery篇

概述 jQuery是一个快速、简洁的JavaScript框架&#xff0c;是一个优秀的JavaScript代码库&#xff08;框架&#xff09;于2006年1月由John Resig发布。它封装JavaScript常用的功能代码&#xff0c;提供一种简便的JavaScript设计模式&#xff0c;优化HTML文档操作、事件处理、动…

2229:Sumsets

网址如下&#xff1a; OpenJudge - 2229:Sumsets 这题不是我想出来的 在这里仅做记录 代码如下&#xff1a; #include<iostream> using namespace std;const int N 1000000000; int dp[1000010]; int n;int main() {cin >> n;dp[0] 1;dp[1] 1;for (int i 2…

前端面试题7(单点登录)

如何实现单点登录 单点登录&#xff08;Single Sign-On&#xff0c;简称SSO&#xff09;是一种允许用户在多个应用系统中只需登录一次&#xff0c;就可以访问所有相互信任的应用系统的认证技术。实现前端单点登录主要依赖于后端的支持和一些特定的协议&#xff0c;如OAuth、Ope…

无法下载cuda

cuda下载不了 一、台式机电脑浏览器打不开cuda下载下面二、解决办法 一、台式机电脑浏览器打不开cuda下载下面 用360、chrome、Edge浏览器都打不开下载页面&#xff0c;有的人说后缀com改成cn&#xff0c;都不行。知乎上说是网络问题&#xff0c;电信换成换成移动/联通的网络会…

Selenium 切换 frame/iframe

环境&#xff1a; Python 3.8 selenium3.141.0 urllib31.26.19说明&#xff1a; driver.switch_to.frame() # 将当前定位的主体切换为frame/iframe表单的内嵌页面中 driver.switch_to.default_content() # 跳回最外层的页面# 判断元素是否在 frame/ifame 中 # 126 邮箱为例 # …

无人机云台类型及作用

无人机云台主要分为三种类型&#xff1a; 单轴云台&#xff1a;仅支持单向旋转&#xff0c;适合拍摄平滑的延时摄影和全景照片。 双轴云台&#xff1a;支持水平和垂直旋转&#xff0c;可用于拍摄流畅的视频和运动物体。 三轴云台&#xff1a;全面支持所有旋转轴&#xff0c;…

医院陪诊系统开发的关键技术与挑战

随着医疗服务需求的不断提升&#xff0c;传统的医院服务模式面临着巨大的压力和挑战。为了提升患者的就医体验和医疗服务的效率&#xff0c;医院陪诊系统应运而生。本文将探讨医院陪诊系统开发的关键技术与挑战&#xff0c;并结合具体的技术代码进行分析。 一、医院陪诊系统的…

悠律凝声环开放式耳机强者现身:集颜值和创新技术于一体的杰作

随着技术的飞速发展&#xff0c;蓝牙耳机已经成为人们生活中不可缺少的一环&#xff0c;外观、音质以及实用性已经成为人们在购买时最主要的考虑因素。悠律凝声环RingBuds Pro开放式蓝牙耳机&#xff0c;凭借其特有的轻奢时尚外观&#xff0c;斩获2024年度MUSE缪斯创意奖金奖&a…

Kotlin协程使用详解

协程是什么 协程是一种编程思想,并不局限于特定的语言。协程是轻量级的线程,基于线程池API,通俗的来说,就是官方提供的线程框架。协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。当我们在了解协程的时候,不可避免的会跟线程、进程作比较作分析,下面来贴个图…

数据可视化之智慧城市的脉动与洞察

在数字化转型的浪潮中,城市作为社会经济发展的核心单元,正经历着前所未有的变革。城市数据可视化大屏看板作为这一变革中的重要工具,不仅极大地提升了城市管理效率,还为公众提供了直观、全面的城市运行状态视图,成为智慧城市建设不可或缺的一部分。本文将深入探讨以“城市…

网安小贴士(9)网络解密

一、前言 网络解密技术的发展是一个不断进化的过程&#xff0c;它与加密技术的进展紧密相连。 二、定义 网络解密&#xff08;Network Decryption&#xff09;通常指的是在计算机网络环境中&#xff0c;将加密的数据转换回其原始可读格式的过程。这个过程需要使用正确的密钥…

ctfshow-web入门-文件包含(web88、web116、web117)

目录 1、web88 2、web116 3、web117 1、web88 没有过滤冒号 : &#xff0c;可以使用 data 协议&#xff0c;但是过滤了括号和等号&#xff0c;因此需要编码绕过一下。 这里有点问题&#xff0c;我 (ls) 后加上分号发现不行&#xff0c;可能是编码结果有加号&#xff0c;题目…

【反悔堆 优先队列 临项交换 决策包容性】630. 课程表 III

本文涉及知识点 贪心 反悔堆 优先队列 临项交换 Leetcode630. 课程表 III 这里有 n 门不同的在线课程&#xff0c;按从 1 到 n 编号。给你一个数组 courses &#xff0c;其中 courses[i] [durationi, lastDayi] 表示第 i 门课将会 持续 上 durationi 天课&#xff0c;并且必…

WordPress网站违法关键词字过滤插件下载text-filter

插件下载地址&#xff1a;https://www.wpadmin.cn/2025.html 插件介绍 WordPress网站违法关键词字过滤插件text-filter由本站原创开发,支持中英文关键字自动替换成**号&#xff0c;可以通过自定义保存修改按钮增加“预设关键字”&#xff0c;也可以导入定义好的txt文本形式的关…

实现模型贴图的移动缩放旋转

技术&#xff1a;threejscanvasfabric 效果图&#xff1a; 原理&#xff1a;threejs中没有局部贴图的效果&#xff0c;只能通过map 的方式贴到模型上&#xff0c;所以说换一种方式来实现&#xff0c;通过canvasfabric来实现图片的移动缩放旋转&#xff0c;然后将整个画布以map…

数据集 | 人脸公开数据集的介绍及下载地址

本文介绍了人脸相关算法的数据集。 1.人脸数据集详情 1.1.Labeled Faces in the Wild (LFW) 论文 下载地址&#xff1a;LFW Face Database : Main (umass.edu) 是目前人脸识别的常用测试集&#xff0c;其中提供的人脸图片均来源于生活中的自然场景&#xff0c;因此识别难度会…

DDR的拓扑与仿真

T型拓扑 vs Fly-by 由于T型拓扑在地址、命令和时钟都是同时到达每个DDR芯片&#xff0c;所以同步的切换噪声会叠加在一起&#xff0c;DDR越多这个信号上叠加的噪声越大&#xff0c;T型拓扑的优点是地址、命令和时钟都是同时到达&#xff0c;所以不需要做写均衡Write leveling。…