C ++仅覆盖文件段
C++ Overwriting only segment of file
我有一个非常大的一维数组,其中包含游戏的 Tile 值。每个值都是一个 4 位数字,代表一种瓷砖(泥土、草地、空气)。
我像这样使用 fstream 将值保存在文件中:
std::fstream save("game_save", std::fstream::out | std::fstream::in);
假设我有一张小地图。 3 格宽和 3 格高,全是泥土(泥土值为 0001)。
在游戏中看起来像
0001 0001 0001
0001 0001 0001
0001 0001 0001
在文件中看起来像(只是一维)
0001 0001 0001 0001 0001 0001 0001 0001 0001
如果我想转到第 5 个值(第 2 行第 2 列)并仅将该值更改为 0002,我该怎么办?这样当我再次 运行 游戏并读取它看到的文件时:
0001 0001 0001 0001 0002 0001 0001 0001
任何有关如何执行此操作的建议将不胜感激
int loc=5;
save.seekg ((loc-1)*5, save.beg);
save << "0002";
试试那个人 :)
在 C++ Fstream to replace specific line? 上选择的答案似乎是一个很好的解释。
如果您完全确定每个元素的 4 位数字 + 恰好 1 space 并且文件中没有制表符或换行符出现,您可以使用 seekp(n*5,ios_base::beg)
将您的下一个写作定位在第 n 个元素上并覆盖它。
建议
如果用ios::binary
模式打开文件,使用这种定位更安全。
在那种情况下,您还可以考虑 reading/writing 使用块函数 read()
/write()
的二进制数据并使用 n*sizeof(tile)
找到正确的位置.该文件不再完全独立于平台,并且无法使用文本编辑器手动编辑它,但是您会提高性能,尤其是在地形非常大的情况下,如果您经常访问同一条线。
您要做的是寻找输出流的正确部分。
fstream save;
...
save.seekp(POSITION_IN_FILE);
这是一个完整的例子:
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
using namespace std;
#define BYTES_PER_BLOCK 5
void save_to_file(fstream& save, int value, int x, int y);
string num2string(int val);
int main(){
fstream save("game_save", std::fstream::out | std::fstream::in);
save_to_file(save, 2, 1, 1);
save.close();
return 0;
}
void save_to_file(fstream& save, int value, int x, int y){
int pos = (y * 3 + x) * BYTES_PER_BLOCK;
save.seekp(pos);
save << num2string(value);
}
string num2string(int val){
string ret = "";
while (val > 0){
ret.push_back('0'+val%10);
val /= 10;
}
while (ret.length() < 4){
ret.push_back('0');
}
reverse(ret.begin(), ret.end());
return ret;
}
最简单的方法是再次写入整个数组。特别是因为它相当短。如果您知道每个元素恰好是 5 个比特,则可以使用 seekp
设置写入位置,因此 save.seekp((1 * 3 + 1) * 5)
然后单独写入该值。但是,如果您的文件不是很大(实际文件仍将在至少 1 个扇区中更新,即硬盘上的 512 或 4096 字节)
我建议使用内存映射文件而不是 fstream。您可以为此使用升压。 Boost library documentation
有覆盖文件映射信息的堆栈溢出线程。
Stack overflow Thred
这将与
类似
save.seekp((row * number_columns + col)* size_of_element_data);
save.write(element_data, size_of_element_data);
但是只读回文件,编辑元素,然后写回整个文件会更容易和更安全。如果不向后移动文件的其余部分(这增加了清除文件末尾现在未使用的 space 的问题)或向前(这要求您填充文件流的末尾,然后执行非破坏性移动)。
我会在这里使用内存映射文件,最好也使用二进制表示。
这样你就可以只存储一个整数数组而不需要任何(反)序列化代码 and/or 寻找。
例如
#include <stdexcept>
#include <iostream>
#include <algorithm>
// memory mapping
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <cstring>
#include <cassert>
template <size_t N, size_t M, typename T = int>
struct Tiles {
Tiles(char const* fname)
: fd(open(fname, O_RDWR | O_CREAT))
{
if (fd == -1) {
throw std::runtime_error(strerror(errno));
}
auto size = N*M*sizeof(T);
if (int err = posix_fallocate(fd, 0, size)) {
throw std::runtime_error(strerror(err));
}
tiles = static_cast<T*>(mmap(nullptr, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0));
if (MAP_FAILED == tiles) {
throw std::runtime_error(strerror(errno));
}
}
~Tiles() {
if (-1 == munmap(tiles, N*M*sizeof(T))) {
throw std::runtime_error(strerror(errno));
}
if (-1 == close(fd)) {
throw std::runtime_error(strerror(errno));
}
}
void init(T value) {
std::fill_n(tiles, N*M, value);
}
T& operator()(size_t row, size_t col) {
assert(row >= 0 && row <= N);
assert(col >= 0 && col <= M);
return tiles[(row*M)+col];
}
T const& operator()(size_t row, size_t col) const {
assert(row >= 0 && row <= N);
assert(col >= 0 && col <= M);
return tiles[(row*M)+col];
}
private:
int fd = -1;
T* tiles = nullptr;
};
int main(int argc, char** argv) {
using Map = Tiles<3, 3, uint16_t>;
Map data("tiles.dat");
if (argc>1) switch(atoi(argv[1])) {
case 1:
data.init(0x0001);
break;
case 2:
data(1, 1) = 0x0002;
break;
}
}
打印:
clang++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && set -x; for cmd in 0 1 2; do ./a.out $cmd; xxd tiles.dat; done
+ for cmd in 0 1 2
+ ./a.out 0
+ xxd tiles.dat
0000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000010: 0000 ..
+ for cmd in 0 1 2
+ ./a.out 1
+ xxd tiles.dat
0000000: 0100 0100 0100 0100 0100 0100 0100 0100 ................
0000010: 0100 ..
+ for cmd in 0 1 2
+ ./a.out 2
+ xxd tiles.dat
0000000: 0100 0100 0100 0100 0200 0100 0100 0100 ................
0000010: 0100 ..
我有一个非常大的一维数组,其中包含游戏的 Tile 值。每个值都是一个 4 位数字,代表一种瓷砖(泥土、草地、空气)。
我像这样使用 fstream 将值保存在文件中:
std::fstream save("game_save", std::fstream::out | std::fstream::in);
假设我有一张小地图。 3 格宽和 3 格高,全是泥土(泥土值为 0001)。
在游戏中看起来像
0001 0001 0001
0001 0001 0001
0001 0001 0001
在文件中看起来像(只是一维)
0001 0001 0001 0001 0001 0001 0001 0001 0001
如果我想转到第 5 个值(第 2 行第 2 列)并仅将该值更改为 0002,我该怎么办?这样当我再次 运行 游戏并读取它看到的文件时:
0001 0001 0001 0001 0002 0001 0001 0001
任何有关如何执行此操作的建议将不胜感激
int loc=5;
save.seekg ((loc-1)*5, save.beg);
save << "0002";
试试那个人 :)
在 C++ Fstream to replace specific line? 上选择的答案似乎是一个很好的解释。
如果您完全确定每个元素的 4 位数字 + 恰好 1 space 并且文件中没有制表符或换行符出现,您可以使用 seekp(n*5,ios_base::beg)
将您的下一个写作定位在第 n 个元素上并覆盖它。
建议
如果用ios::binary
模式打开文件,使用这种定位更安全。
在那种情况下,您还可以考虑 reading/writing 使用块函数 read()
/write()
的二进制数据并使用 n*sizeof(tile)
找到正确的位置.该文件不再完全独立于平台,并且无法使用文本编辑器手动编辑它,但是您会提高性能,尤其是在地形非常大的情况下,如果您经常访问同一条线。
您要做的是寻找输出流的正确部分。
fstream save;
...
save.seekp(POSITION_IN_FILE);
这是一个完整的例子:
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
using namespace std;
#define BYTES_PER_BLOCK 5
void save_to_file(fstream& save, int value, int x, int y);
string num2string(int val);
int main(){
fstream save("game_save", std::fstream::out | std::fstream::in);
save_to_file(save, 2, 1, 1);
save.close();
return 0;
}
void save_to_file(fstream& save, int value, int x, int y){
int pos = (y * 3 + x) * BYTES_PER_BLOCK;
save.seekp(pos);
save << num2string(value);
}
string num2string(int val){
string ret = "";
while (val > 0){
ret.push_back('0'+val%10);
val /= 10;
}
while (ret.length() < 4){
ret.push_back('0');
}
reverse(ret.begin(), ret.end());
return ret;
}
最简单的方法是再次写入整个数组。特别是因为它相当短。如果您知道每个元素恰好是 5 个比特,则可以使用 seekp
设置写入位置,因此 save.seekp((1 * 3 + 1) * 5)
然后单独写入该值。但是,如果您的文件不是很大(实际文件仍将在至少 1 个扇区中更新,即硬盘上的 512 或 4096 字节)
我建议使用内存映射文件而不是 fstream。您可以为此使用升压。 Boost library documentation
有覆盖文件映射信息的堆栈溢出线程。 Stack overflow Thred
这将与
类似save.seekp((row * number_columns + col)* size_of_element_data);
save.write(element_data, size_of_element_data);
但是只读回文件,编辑元素,然后写回整个文件会更容易和更安全。如果不向后移动文件的其余部分(这增加了清除文件末尾现在未使用的 space 的问题)或向前(这要求您填充文件流的末尾,然后执行非破坏性移动)。
我会在这里使用内存映射文件,最好也使用二进制表示。
这样你就可以只存储一个整数数组而不需要任何(反)序列化代码 and/or 寻找。
例如
#include <stdexcept>
#include <iostream>
#include <algorithm>
// memory mapping
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <cstring>
#include <cassert>
template <size_t N, size_t M, typename T = int>
struct Tiles {
Tiles(char const* fname)
: fd(open(fname, O_RDWR | O_CREAT))
{
if (fd == -1) {
throw std::runtime_error(strerror(errno));
}
auto size = N*M*sizeof(T);
if (int err = posix_fallocate(fd, 0, size)) {
throw std::runtime_error(strerror(err));
}
tiles = static_cast<T*>(mmap(nullptr, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0));
if (MAP_FAILED == tiles) {
throw std::runtime_error(strerror(errno));
}
}
~Tiles() {
if (-1 == munmap(tiles, N*M*sizeof(T))) {
throw std::runtime_error(strerror(errno));
}
if (-1 == close(fd)) {
throw std::runtime_error(strerror(errno));
}
}
void init(T value) {
std::fill_n(tiles, N*M, value);
}
T& operator()(size_t row, size_t col) {
assert(row >= 0 && row <= N);
assert(col >= 0 && col <= M);
return tiles[(row*M)+col];
}
T const& operator()(size_t row, size_t col) const {
assert(row >= 0 && row <= N);
assert(col >= 0 && col <= M);
return tiles[(row*M)+col];
}
private:
int fd = -1;
T* tiles = nullptr;
};
int main(int argc, char** argv) {
using Map = Tiles<3, 3, uint16_t>;
Map data("tiles.dat");
if (argc>1) switch(atoi(argv[1])) {
case 1:
data.init(0x0001);
break;
case 2:
data(1, 1) = 0x0002;
break;
}
}
打印:
clang++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && set -x; for cmd in 0 1 2; do ./a.out $cmd; xxd tiles.dat; done
+ for cmd in 0 1 2
+ ./a.out 0
+ xxd tiles.dat
0000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000010: 0000 ..
+ for cmd in 0 1 2
+ ./a.out 1
+ xxd tiles.dat
0000000: 0100 0100 0100 0100 0100 0100 0100 0100 ................
0000010: 0100 ..
+ for cmd in 0 1 2
+ ./a.out 2
+ xxd tiles.dat
0000000: 0100 0100 0100 0100 0200 0100 0100 0100 ................
0000010: 0100 ..