一、BLOB数据类型简介
Blob(Binary Large Object)是一种用于存储二进制数据的数据类型,在数据库中常用于存储图片、音频和视频等大型(大数据量)的二进制数据[1-2]。需要注意的是,SQLite中BLOB类型的单对象最大存储字节数由预处理器宏SQLITE_MAX_LENGTH定义并可以提高或降低该值,但是当前的实现只支持最大长度为字节。
在SQLite的部分INSERT和SELECT语句处理过程中,数据库中每一行的完整内容被编码为单个BLOB数据类型对象,所以SQLITE_MAX_LENGTH宏定义决定了数据库中一行的最大字节数[3]。
二、C++标准库之bitset简介[4-6]
C++标准库中的bitset库,是一个模板类(模板参数定义了位集合的大小)且提供了一种方式来操作固定大小的位集合。位集合是一个由位(bit)组成的数组,每个位可以是0或1,因此bitset类型非常适合于需要表示二进制数据或进行位操作的场景。
位运算的优势在于可以大量减少运行开销,优化算法。其运算速度快的原因是直接使用计算机底层的二进制机器操作指令。较为详细的位运算理论理解与实际用例可参考资料[6]。
二、C++中基本数据类型实例与其二进制存储形式探索[7-10]
计算机中的所有数据,无论是文本、图像、音频还是视频,最终都会被转换为二进制形式进行存储和处理。比特/位(bit)是计算机内存中的最小/原子单位,也是数据传输的最小单位;字节(Byte)是计算机系统中最小的存储单位,是计算机记忆体(memory)存储资料的基本单位,每个字节由八个比特位组成。
C++基本内置数据类型在计算机内部存储数据时使用二进制,但在向用户显示时使用八进制、十进制与十六进制等进制。需要注意的是,整数类型是按照二进制补码的方式来存储数值(非负整数直接使用二进制表示,无符号位),其最高位(最左边一位)用于表示符号(0表示正数,1表示负数),剩下的位数表示数值部分;浮点数类型是按照IEEE 754标准来存储数据,然后根据浮点数数值转换公式进行数值计算。详细叙述可参考资料[7]。
计算机中的字符按照字符集编码表示,常见的字符集编码有ASCII编码、ANSI编码、Unicode编码以及UTF-8编码。其中,ASCII码(American Standard Code for Information Interchange,美国信息交换标准码),一个英文字母(不分大小写)占一个字节的空间。ASCII码字符代码表可参考资料[10]。
(一)实验一 基于字符串的逐字符二进制编码转换
(二)实验二 长整型向量的逐一元素二进制编码转换
(三)实验三 字符型向量的逐一元素二进制编码转换
实验总结:
1. C++基本内置数据类型在计算机内部存储数据时使用二进制;
2. 基于标准库bitset可以将存储数据以二进制的形式显化展示;
3. 字符类型(char)占一个字节,其不仅可以基于ASCII码存储字符,还可以在单字节限制下存储有限大小的整型数据(实质上,char与int之间存在隐式转换)。
三、基于SQLite实现图像数据库管理(图像的存取实例)[11-12]
该示例代码仅实现了图像数据的数据库/表创建及数据插入,未实现数据库图像的读取与二进制码解析。数据库图像的读取与二进制码解析概要代码可参考资料[12]。
#include <iostream>
#include <string>
#include <vector>
#include "opencv2/opencv.hpp"
#include "sqlite3.h"
using namespace std;
// 实验数据
string imagePath = "./icon.jpg";
int main()
{
// 读取图像文件并检查图像是否读取成功
cv::Mat image = cv::imread(imagePath);
if (image.empty()) {
cout << "无法读取图像文件。" << endl;
return -1;
}
// 创建图像存储表
/*
图像存储表设计(该表包含"id"、"name"、"size"、"type"、"data"五个字段):
1. "id"字段作为主键,自动递增;
2. "name"字段用于存储图像的名称;
3. "size"字段用于存储图像的大小(以字节为单位);
4. "type"用于存储图像的类型(如.jpg和.png等);
5. "data"字段为BLOB类型,用于存储图像的二进制类型
*/
sqlite3* db;
int rc = sqlite3_open("./imageDB.db", &db);
if (rc)
{
std::cerr << "无法打开数据库: " << sqlite3_errmsg(db) << std::endl;
return 1;
}
else
{
std::cout << "数据库打开成功" << std::endl;
}
// 创建数据库表
char* errMsg = 0;
const char* sql = "CREATE TABLE IF NOT EXISTS images"
"(id INTEGER PRIMARY KEY AUTOINCREMENT,"
"name TEXT NOT NULL,"
"size INTEGER NOT NULL,"
"type TEXT NOT NULL,"
"data BLOB);";
rc = sqlite3_exec(db, sql, 0, 0, &errMsg);
if (rc != SQLITE_OK)
{
std::cerr << "SQL错误: " << errMsg << std::endl;
sqlite3_free(errMsg);
sqlite3_close(db);
return -1;
}
else
{
std::cout << "表创建成功" << std::endl;
}
// 处理图像数据(将图像数据转换为二进制数据)
vector<uchar> buffer;
cv::imencode(".jpg", image, buffer); // 该函数将图像以设定图片格式编码存储到buffer
// 该函数将图像数据编码转换为指定格式的字节流,这个字节流本质上就是二进制数据
size_t size = sizeof(buffer);
uchar* dataPtr = buffer.data();
// 基于参数化方式将图像数据存入表
/*
参数化方式的原理:
参数化将SQL语句的结构与数据分开。SQL语句中的参数位置用?占位符表示,
然后通过专门的绑定函数将实际数据绑定到这些占位符上。
参数化方式的优势:
这样可以有效避免SQL注入攻击。
因为SQL语句的语法结构是固定的,用户输入的数据不会被解释为SQL语法的一部分
*/
const char* sql1 = "INSERT INTO images (id,name,size,type,data)"
"VALUES (?, ?, ?, ?, ?);";
sqlite3_stmt* stmt;
int rc1 = sqlite3_prepare_v2(db, sql1, -1, &stmt, nullptr);
if (rc1 != SQLITE_OK) {
std::cerr << "准备插入语句出错: " << sqlite3_errmsg(db) << std::endl;
sqlite3_close(db);
return -1;
}
sqlite3_bind_int(stmt, 1, 0);
sqlite3_bind_text(stmt, 2, "icon", -1, SQLITE_TRANSIENT);
sqlite3_bind_int(stmt, 3, size);
sqlite3_bind_text(stmt, 4, "jpg", -1, SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 5, dataPtr, -1, SQLITE_TRANSIENT);
int rc2 = sqlite3_step(stmt);
if (rc2 != SQLITE_DONE) {
std::cerr << "执行插入语句出错: " << sqlite3_errmsg(db) << std::endl;
}
cout << "图像数据插入成功" << endl;
sqlite3_finalize(stmt);
return 0;
}
参考资料:
[1] Datatypes In SQLite
[2] SQLite Blob 数据类型详解|极客笔记 (deepinout.com)
[3] SQLite 的实现限制 | SQLite中文网 (readdevdocs.com)
[4] C++ 数据类型 | 菜鸟教程 (runoob.com)
[5] C++之std::bitset使用精讲(全)-CSDN博客
[6] 位运算的运用场景使用总结_位运算的使用场景-CSDN博客
[7] C++内置数据类型与二进制存储 | 我有昕想法 | Peter_Matthew的博客 (zhangkai.xin)
[8] 计算机科学导论:第三章 数据存储 - 知乎 (zhihu.com)
[9] 在 C++ 中将字符串转换为二进制序列 | D栈 - Delft Stack
[10] ASCII 表 | 菜鸟教程 (runoob.com)