2d — C ++ перезаписывает только сегмент файла

У меня есть очень большой одномерный массив, содержащий значения тайлов для моей игры. Каждое значение представляет собой четырехзначное число, представляющее тип плитки (грязь, трава, воздух).

Я сохраняю значения в файле, используя 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

Любые советы о том, как это сделать, будут оценены

1

Решение

Если вы абсолютно уверены в 4 цифрах + ровно 1 пробел для каждого элемента, и в файле не встречаются символы табуляции или новой строки, вы можете использовать seekp(n*5,ios_base::beg) расположить следующую запись на n-м элементе и просто перезаписать ее.

Suggesions

Если использование такого рода позиционирования безопаснее с файлами, открытыми с ios::binary Режим.

В этом случае вы также можете рассмотреть возможность чтения / записи двоичных данных с помощью функций блока. read()/write() и используя n*sizeof(tile) найти правильную позицию. После этого файл больше не будет полностью независимым от платформы, и его невозможно будет редактировать вручную с помощью текстового редактора, но у вас будет улучшенная производительность, особенно если у вас очень большая территория, и даже больше, если вы часто получаете доступ к последовательным элементам в та же строка

2

Другие решения

ПРОСТОЙ способ — просто снова записать весь массив. Тем более, что он довольно короткий. Если вы ЗНАЕТЕ, что каждый элемент равен 5 байтам, вы можете установить положение записи с помощью seekp, так save.seekp((1 * 3 + 1) * 5) а затем напишите это значение в одиночку. Но это, вероятно, больше работы, чем стоит, если ваш файл не ОГРОМНЫЙ (фактический файл все равно будет обновлен как минимум в 1 секторе, который составляет 512 или 4096 байт на жестком диске)

1

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

попробуй этого парня 🙂

Выбранный ответ на C ++ Fstream для замены конкретной строки? кажется довольно хорошим объяснением.

0

То, что вы хотите сделать, это искать правильную часть нашего потока вывода.

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

Я предлагаю использовать файлы с отображением в памяти вместо fstream. Вы можете использовать повышение для этого. Увеличить документацию библиотеки

Существует поток переполнения стека, который охватывает информацию о сопоставлении файлов.
Переполнение стека Тред

0

Это будет что-то вроде

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

Но будет намного проще и безопаснее просто прочитать файл обратно, отредактировать элемент и записать весь файл обратно. Вы не можете изменить размер или вставить элемент в середине файла, не перетасовывая оставшуюся часть файла назад (что добавляет проблему очистки теперь неиспользуемого пространства в конце файла) или вперед (что требует дополнения конец файлового потока, а затем выполнить неразрушающее перемещение).

0

Я бы использовал здесь отображенный в память файл и, предпочтительно, также использовал бы двоичное представление.

Таким образом, вы можете просто хранить массив целых чисел и не нуждаться в (де) коде сериализации и / или поиске.

Например.

Жить на Колиру

#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                                     ..
0
По вопросам рекламы [email protected]