必须用std::ios::binary模式读取图片,否则含\0的二进制流会被文本模式截断或误判eof;正确做法是open后检查is_open()、seekg到末尾获取大小、再seekg回开头,用vector接收并验证gcount()等于预期字节数。

图片读取必须用 std::ios::binary 模式
直接用文本模式打开图片文件,读出来的数据会被截断或损坏,因为图片二进制流里包含 \0 字节,而文本流默认以 \0 为字符串终止符。不加 binary 标志,std::ifstream 会误判 EOF 或丢弃控制字符。
正确做法:
std::ifstream file("photo.jpg", std::ios::in | std::ios::binary);
if (!file.is_open()) {
// 处理错误
}
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
<p>std::vector<unsigned char> buffer(size);
file.read(reinterpret_cast<char>(buffer.data()), size);
</char></unsigned></p>
-
file.read()的第二个参数必须是size_t类型字节数,不能传buffer.size()以外的值,否则可能读不满 - 务必检查
file.gcount()是否等于预期大小,尤其在某些文件系统或网络挂载卷上,read()可能只读部分数据 - 不要用
std::string存二进制数据——它不是为\0设计的;std::vector<unsigned char></unsigned>更安全、语义更清晰
MySQL 中推荐用 MEDIUMBLOB 而非 BLOB
BLOB 最大只支持 64KB,一张普通手机照片就很容易超限;MEDIUMBLOB 支持 16MB,覆盖绝大多数场景;真要存更大图(如原始 RAW),才需考虑 LONGBLOB。
建表时字段定义示例:
CREATE TABLE images (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
data MEDIUMBLOB,
mime_type VARCHAR(64)
);
-
mime_type字段强烈建议一并保存(如"image/jpeg"),否则取出后无法可靠判断格式 - 避免把整张图塞进 SQL 字符串拼接——容易触发
mysql_real_escape_string的边界问题或 SQL 注入风险;必须用预处理语句(mysql_stmt_bind_param)或现代库(如libpqxx、soci)的二进制绑定接口 - MySQL 默认
max_allowed_packet通常为 4MB,如果插入 >4MB 的图,需同步调大该配置,否则报错Packet too large
SQLite 用 INSERT 绑定 SQLITE_BLOB 类型参数
SQLite 不区分“二进制字段类型”,但写入时必须显式指定参数类型为 SQLITE_BLOB,否则驱动可能尝试 UTF-8 解码,导致数据损坏。
使用 C API 示例:
sqlite3_stmt* stmt; sqlite3_prepare_v2(db, "INSERT INTO images(name, data) VALUES(?, ?)", -1, &stmt, nullptr); sqlite3_bind_text(stmt, 1, "photo.jpg", -1, SQLITE_STATIC); sqlite3_bind_blob(stmt, 2, buffer.data(), buffer.size(), SQLITE_STATIC); // 关键:用 bind_blob sqlite3_step(stmt); sqlite3_finalize(stmt);
-
sqlite3_bind_blob第四个参数决定内存管理方式:SQLITE_STATIC表示你保证buffer生命周期长于语句执行;若 buffer 是局部std::vector,要用SQLITE_TRANSIENT让 SQLite 自行复制 - 不要用
sqlite3_bind_text传二进制数据——即使内容不含\0,它仍会按 UTF-8 验证,失败则截断 - SQLite 写入大 blob 性能一般,频繁读写单张 >1MB 图片时,考虑改用文件系统存储路径 + 数据库存路径的方案
取出图片后必须原样写回文件,不能经 std::string 中转
从数据库取出 blob 后,常见错误是用 std::string 接收再写文件——只要 blob 里含 \0,std::string.c_str() 就提前截断。
正确流程:
- 用对应数据库 API 的二进制获取接口(如 MySQL 的
mysql_stmt_bind_result+MYSQL_TYPE_TINY_BLOB等类型绑定,或 SQLite 的sqlite3_column_blob) - 拿到裸指针和长度后,直接用
std::ofstream以std::ios::binary模式写入 - 验证写入字节数是否等于读出字节数,否则说明某处隐式转码或截断
最容易被忽略的是:数据库驱动层可能对 blob 做自动编码转换(比如某些 ODBC 驱动默认启用 ANSI 转换),务必查清所用库的文档,关闭所有自动文本处理选项。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











