C++只覆盖文件的一段

C++ Overwriting only segment of file

本文关键字:一段 覆盖 文件 C++      更新时间:2023-10-16

我有一个非常大的1D数组,其中包含我的游戏的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

任何关于如何做到这一点的建议都将不胜感激

如果您绝对确定4位数字+每个元素正好有1个空格,并且文件中没有表或换行符,则可以使用seekp(n*5,ios_base::beg)将下一个写入定位在第n个元素上并覆盖它。

提示

如果使用ios::binary模式打开的文件使用这种定位更安全。

在这种情况下,您还可以考虑使用块函数read()/write()读取/写入二进制数据,并使用n*sizeof(tile)找到正确的位置。然后,该文件不再完全独立于平台,也不可能使用文本编辑器手动编辑,但性能会有所提高,尤其是在地形非常大的情况下,如果经常访问同一行中的连续元素,则性能会更高。

简单的方法是重新写入整个数组。特别是因为它很短。如果你知道每个元素正好是5个丁烯,你可以用seekp设置写位置,所以save.seekp((1 * 3 + 1) * 5),然后单独写这个值。但是,如果您的文件不是巨大的(实际文件仍将在至少1个扇区中更新,即硬盘上的512或4096字节(,则可能会付出更多的工作。

int loc=5;
save.seekg ((loc-1)*5, save.beg);
save << "0002";

试试那个家伙:(

在C++Fstream上选择的答案来替换特定的行?这似乎是一个很好的解释。

您想要做的是找到输出流的正确部分。

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;
}

我建议使用内存映射文件而不是fstream。你可以使用助推。增强库文档

有一个堆栈溢出线程,它涵盖了有关文件映射的信息。堆栈溢出Thred

这将是的一部分

save.seekp((row * number_columns + col)* size_of_element_data);
save.write(element_data, size_of_element_data);

但是,只需重新读取文件、编辑元素并重新写入整个文件会更容易、更安全。如果不向后移动文件的其余部分(这增加了清除文件结尾处现有空间的问题(或向前移动(这需要填充文件流的结尾,然后执行非破坏性移动(,则无法调整文件的大小或在文件中间插入元素。

我在这里使用内存映射文件,最好也使用二进制表示。

这样,您就可以只存储int数组,而不需要任何(反(序列化代码和/或查找。

例如

在Coliru上直播

#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                                     ..