【问题标题】:C++ Overwriting only segment of fileC ++仅覆盖文件段
【发布时间】:2015-10-07 17:48:41
【问题描述】:

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

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

【问题讨论】:

    标签: c++ 2d fstream tiles overwrite


    【解决方案1】:

    如果您完全确定每个元素的 4 位数字 + 正好 1 个空格,并且文件中没有出现制表符或换行符,您可以使用 seekp(n*5,ios_base::beg) 将您的下一篇文章放在第 n 个元素并覆盖它。

    建议

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

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

    【讨论】:

    • 二进制模式推荐双加。如果您使用固定宽度数据类型定义您的 tile 结构,那么您应该遇到的唯一跨平台问题将是字节序翻转。
    【解决方案2】:

    简单的方法是再次写入整个数组。特别是因为它相当短。如果您知道每个元素恰好是 5 个butes,则可以使用seekp 设置写入位置,因此save.seekp((1 * 3 + 1) * 5) 然后单独写入该值。但是,如果您的文件不是 HUGE(实际文件仍将在至少 1 个扇区中更新,即硬盘上的 512 或 4096 字节),则工作量可能会大于其价值。

    【讨论】:

      【解决方案3】:
      int loc=5;
      save.seekg ((loc-1)*5, save.beg);
      save << "0002";
      

      试试那个人:)

      C++ Fstream to replace specific line? 上的选定答案似乎是一个很好的解释。

      【讨论】:

        【解决方案4】:

        您要做的是寻找我们输出流的正确部分。

        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】:

          我建议使用内存映射文件而不是 fstream。您可以为此使用 boost。 Boost library documentation

          有堆栈溢出线程,涵盖有关文件映射的信息。 Stack overflow Thred

          【讨论】:

            【解决方案6】:

            这将类似于

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

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

            【讨论】:

            • @Christophe 是的。我的错。
            【解决方案7】:

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

            这样您就可以只存储一个整数数组,而不需要任何(反)序列化代码和/或搜索。

            例如

            Live On 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                                     ..
            

            【讨论】:

              猜你喜欢
              • 2018-05-21
              • 2013-06-06
              • 2016-05-25
              • 2011-11-02
              • 2023-03-23
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多