C在二进制文件的中间写入而不覆盖任何现有内容

Fre*_*ong 8 c file-io binaryfiles

今天的问题是我需要在起始位置的二进制文件中编写一个数字数组.我有它应该开始的位置,我不想在此之后覆盖值,只是想将数组插入文件的起始位置.例如:

12345
Run Code Online (Sandbox Code Playgroud)

让我们在第2位推456:

12456345
Run Code Online (Sandbox Code Playgroud)

我知道可能我必须自己实现它,但我想知道你对如何尽可能有效地实现它有什么看法.

Jon*_*ler 12

这是一个extend_file_and_insert()或多或少完成工作的功能.

#include <sys/stat.h>
#include <unistd.h>

enum { BUFFERSIZE = 64 * 1024 };

#define MIN(x, y) (((x) < (y)) ? (x) : (y))

/*
off_t   is signed
ssize_t is signed
size_t  is unsigned

off_t   for lseek() offset and return
size_t  for read()/write() length
ssize_t for read()/write() return
off_t   for st_size
*/

static int extend_file_and_insert(int fd, off_t offset, char const *insert, size_t inslen)
{
    char buffer[BUFFERSIZE];
    struct stat sb;
    int rc = -1;

    if (fstat(fd, &sb) == 0)
    {
        if (sb.st_size > offset)
        {
            /* Move data after offset up by inslen bytes */
            size_t bytes_to_move = sb.st_size - offset;
            off_t read_end_offset = sb.st_size; 
            while (bytes_to_move != 0)
            {
                ssize_t bytes_this_time = MIN(BUFFERSIZE, bytes_to_move);
                ssize_t rd_off = read_end_offset - bytes_this_time;
                ssize_t wr_off = rd_off + inslen;
                lseek(fd, rd_off, SEEK_SET);
                if (read(fd, buffer, bytes_this_time) != bytes_this_time)
                    return -1;
                lseek(fd, wr_off, SEEK_SET);
                if (write(fd, buffer, bytes_this_time) != bytes_this_time)
                    return -1;
                bytes_to_move -= bytes_this_time;
                read_end_offset -= bytes_this_time; /* Added 2013-07-19 */
            }   
        }   
        lseek(fd, offset, SEEK_SET);
        write(fd, insert, inslen);
        rc = 0;
    }   
    return rc;
}
Run Code Online (Sandbox Code Playgroud)

(注意添加的新行2013-07-19;这是一个只显示缓冲区大小小于要复制文件的数据量的错误.感谢malat指出错误.代码现在测试了BUFFERSIZE = 4.)

这是一些小规模的测试代码:

#include <fcntl.h>
#include <string.h>

static const char base_data[] = "12345";
typedef struct Data
{
    off_t       posn;
    const char *data;
} Data;
static const Data insert[] =
{
    {  2, "456"                       },
    {  4, "XxxxxxX"                   },
    { 12, "ZzzzzzzzzzzzzzzzzzzzzzzzX" },
    { 22, "YyyyyyyyyyyyyyyY"          },
};  
enum { NUM_INSERT = sizeof(insert) / sizeof(insert[0]) };

int main(void)
{
    int fd = open("test.dat", O_RDWR | O_TRUNC | O_CREAT, 0644);
    if (fd > 0)
    {
        ssize_t base_len = sizeof(base_data) - 1;
        if (write(fd, base_data, base_len) == base_len)
        {
            for (int i = 0; i < NUM_INSERT; i++)
            {
                off_t length = strlen(insert[i].data);
                if (extend_file_and_insert(fd, insert[i].posn, insert[i].data, length) != 0)
                    break;
                lseek(fd, 0, SEEK_SET);
                char buffer[BUFFERSIZE];
                ssize_t nbytes;
                while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0)
                    write(1, buffer, nbytes);
                write(1, "\n", 1);
            }
        }
        close(fd);
    }
    return(0);
}
Run Code Online (Sandbox Code Playgroud)

它产生输出:

12456345
1245XxxxxxX6345
1245XxxxxxX6ZzzzzzzzzzzzzzzzzzzzzzzzZ345
1245XxxxxxX6ZzzzzzzzzzYyyyyyyyyyyyyyyYzzzzzzzzzzzzzzZ345
Run Code Online (Sandbox Code Playgroud)

它应该在一些较大的文件上测试(比BUFFERSIZE更大的文件,但是使用比64 KiB小得多的BUFFERSIZE进行测试是明智的;我使用了32个字节,它似乎没问题).我只关注结果,但模式旨在让人们很容易看出它们是正确的.代码不检查任何lseek()调用; 这是一个小风险.


Joh*_*nck 5

首先,使用ftruncate()将文件放大到最终大小.然后将所有内容从旧端复制到新端,继续前进到插入点.然后用要插入的数据覆盖中间内容.我认为这是有效的,因为文件系统通常不会在文件中间提供真正的"插入".

  • @FredericoSchardong:写一下吧.学习一两件事. (3认同)