可通过继承 std::streambuf 重写 sgetn/sputn 实现流级移位加密,将自定义 streambuf 绑定到 istream/ostream 后,所有读写操作自动经移位处理,避免大文件内存全载入。

用 std::streambuf 做流级移位加密,避免内存全载入
移位加密(比如凯撒密码)本身很简单,但直接读整个文件进内存再处理,对大文件不友好,也违背“流过滤”的本意。C++ 标准库没提供现成的加密 streambuf,但可以继承 std::streambuf 自定义——核心是重写 sgetn(输入)和 sputn(输出)来插入手动移位逻辑。
关键不是“怎么写移位”,而是“怎么让 std::istream/std::ostream 透明地走你的加密逻辑”。你得把自定义 streambuf 绑到流上,之后所有 read()、operator>>、write()、operator 都自动经过移位。
- 移位操作必须在字节粒度做,别碰宽字符或编码边界(UTF-8 下一个字符可能 1–4 字节,直接移位会乱码;只对纯 ASCII 文本或二进制数据安全)
- 不要在
underflow()里做移位——那是单字符回填逻辑,效率低且易错;sgetn/sputn才是批量处理的正路 - 加密/解密用同一个移位值,但方向相反:写入时 +3,读取时 −3(模 256),否则文件打不开
std::ofstream 写加密文件时,rdbuf() 替换必须在构造后立即做
很多人试过这样写:std::ofstream ofs("data.enc"); ofs.rdbuf(new MyEncryptBuf(ofs.rdbuf(), 3));——这会崩溃或静默失败。因为 ofstream 构造时已绑定默认 streambuf,后续调用 rdbuf() 替换,但内部状态(如打开标志、locale)可能没同步过去。
- 正确做法:先构造空流,再用
rdbuf()设置自定义缓冲区,最后调用open() - 示例:
MyEncryptBuf encBuf(originalBuf, 3); // originalBuf 是底层文件 buf std::ofstream ofs; ofs.rdbuf(&encBuf); ofs.open("data.enc", std::ios::binary); - 如果用
std::filebuf*作底层,记得open()成功后再传给加密streambuf,否则sputn调用sputn到空指针
读取加密文件报 ios_base::failure 或内容全乱码?检查移位是否作用于换行符和 EOF
常见错误是把 \n、\r、<p>常见错误是把 <code>\n、\r、\0 当普通字节移位,导致文本解析失败;更隐蔽的是在 sgetn 中没正确处理底层 streambuf 返回的字节数(比如读到 EOF 时返回值小于请求长度),却仍对整块缓冲区执行移位,把未读区域也“加密”了。
sgetn 中没正确处理底层 streambuf 返回的字节数(比如读到 EOF 时返回值小于请求长度),却仍对整块缓冲区执行移位,把未读区域也“加密”了。立即学习“C++免费学习笔记(深入)”;
- 移位前先判断当前字节是否在有效读取范围内(用实际返回长度截断)
- 二进制模式(
std::ios::binary)必须显式开启,否则 Windows 下\r\n会被自动转成\n,破坏移位一致性 - 不要在移位逻辑里跳过控制字符——移位是对称操作,加密时跳过,解密时也得跳过,否则偏移量错位;统一全字节处理最稳
性能差、卡顿?别在 sputn 里反复 new/delete 缓冲区
有些实现每次 sputn 都分配临时内存存移位后数据,再写到底层 buf,小文件看不出来,大文件 I/O 次数翻倍、内存碎片严重。
- 复用一块内部缓冲区(比如 4KB),在
sputn开头 memcpy 原始数据进去,就地移位,再一次性sputn到下游streambuf - 注意:
sputn参数s指向的数据生命周期由调用方管理,不能保存指针,只能拷贝 - 移位运算用查表法(256 元素数组)比每次
(b + shift) % 256快,尤其在嵌入式或高频场景下
真正麻烦的从来不是移位公式,而是 streambuf 状态同步、错误传播、以及二进制与文本模式的隐式转换——这些地方一错,文件就再也读不回来了。



















