基于mmap的读写工具封装案例

news2024/9/23 19:25:15

文章目录

      • 注意事项
      • C++封装示例
      • 添加构造函数重载以支持追加模式
      • 支持文件大小动态变化
      • 异常安全性和资源泄漏预防
      • 提供更高级的数据访问接口
      • 示例代码改进

在很多高性能应用中,直接使用内存映射文件(mmap)进行文件的读写操作可以显著提高效率,尤其是处理大文件时。以下是一个基于C++的简单封装示例,展示了如何利用 mmap进行文件读写。这个封装旨在提供一个更易用的接口来隐藏底层细节。

注意事项

  • 平台兼容性:本示例主要针对POSIX兼容系统(如Linux、macOS等)。Windows系统有类似的API但实现方式不同。
  • 错误处理:为了保持示例简洁,错误处理相对基础。实际应用中应更全面地处理可能的错误情况,如内存不足、文件不存在等。
  • 资源管理:使用RAII(Resource Acquisition Is Initialization)原则自动管理资源,确保在对象生命周期结束时正确释放资源。

C++封装示例

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <stdexcept>

class MmapFile {
public:
    MmapFile(const std::string& filePath, bool readOnly = true)
        : filePath_(filePath), readOnly_(readOnly) {
        fd_ = open(filePath.c_str(), readOnly_ ? O_RDONLY : (O_RDWR | O_CREAT), 0644);
        if (fd_ == -1) {
            throw std::runtime_error("Failed to open file");
        }

        struct stat sb;
        if (fstat(fd_, &sb) == -1) {
            close(fd_);
            throw std::runtime_error("Failed to get file size");
        }
        fileSize_ = sb.st_size;

        data_ = mmap(nullptr, fileSize_, readOnly_ ? PROT_READ : (PROT_READ | PROT_WRITE), MAP_SHARED, fd_, 0);
        if (data_ == MAP_FAILED) {
            close(fd_);
            throw std::runtime_error("Failed to map file");
        }
    }

    ~MmapFile() {
        munmap(data_, fileSize_);
        close(fd_);
    }

    char* data() const {
        return static_cast<char*>(data_);
    }

    size_t size() const {
        return fileSize_;
    }

    // 示例:读取数据
    std::string readContent() const {
        return std::string(data_, fileSize_);
    }

    // 示例:写入数据(仅当初始化为可写时可用)
    void writeContent(const std::string& content) {
        if (!readOnly_) {
            if (content.size() > fileSize_) {
                throw std::length_error("Content exceeds file size");
            }
            memcpy(data_, content.data(), content.size());
            // 如果内容变短,这里应有逻辑去截断文件,但为了简化未包含
        } else {
            throw std::logic_error("File opened in read-only mode");
        }
    }

private:
    int fd_ = -1;
    char* data_ = nullptr;
    size_t fileSize_ = 0;
    std::string filePath_;
    bool readOnly_;
};

int main() {
    try {
        // 读取文件示例
        {
            MmapFile readFile("example.txt");
            std::cout << "File content: " << readFile.readContent() << std::endl;
        }

        // 写入文件示例(如果文件不存在,会根据O_CREAT创建)
        {
            MmapFile writeFile("output.txt", false); // 可写模式
            writeFile.writeContent("Hello, mmap!");
        }
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

这段代码定义了一个MmapFile类,用于封装对文件的内存映射操作。它支持以读或读写模式打开文件,并提供了读取文件内容和写入内容的简单方法。请根据具体需求调整错误处理和功能实现。

为了进一步完善这个封装,我们可以添加一些额外的功能和优化点,使其更加健壮和灵活。下面是一些建议的改进:

添加构造函数重载以支持追加模式

有时候,我们可能希望在现有文件末尾追加内容而不是覆盖原有内容。可以通过在打开文件时使用O_APPEND标志来实现这一点。

支持文件大小动态变化

当前的实现假设文件大小在映射后不会改变。对于可写的映射,如果文件大小需要动态增长或缩小,我们需要额外的逻辑来处理文件截断和重新映射。

异常安全性和资源泄漏预防

确保在构造函数中捕获所有可能的异常,并妥善清理已分配的资源。虽然当前实现已经通过RAII模式管理了大部分资源,但在构造函数中增加详细的错误处理逻辑会更安全。

提供更高级的数据访问接口

例如,添加按行读写、二进制数据读写等功能,使得用户无需直接操作字节指针,提高代码的可读性和易用性。

示例代码改进

考虑到上述建议,这里展示一个简化的追加模式构造函数重载的示例,以及如何处理文件大小变化的思路:

// 追加模式构造函数重载
MmapFile(const std::string& filePath, bool append = false)
    : filePath_(filePath), readOnly_(false) {
    int flags = O_RDWR | O_CREAT;
    if (append) flags |= O_APPEND;
    fd_ = open(filePath.c_str(), flags, 0644);
    if (fd_ == -1) {
        throw std::runtime_error("Failed to open file");
    }

    // 获取并设置文件大小,对于追加模式,这一步可能不是必须的
    // 实际应用中可能需要在写入后调整文件大小
    struct stat sb;
    if (fstat(fd_, &sb) == -1) {
        close(fd_);
        throw std::runtime_error("Failed to get file size");
    }
    fileSize_ = sb.st_size;

    // 注意:对于追加写,直接映射可能不适用,因为映射区域可能不会自动扩展
    // 实际应用中可能需要在每次写入前手动调整映射区域或使用其他策略
    data_ = mmap(nullptr, fileSize_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0);
    if (data_ == MAP_FAILED) {
        close(fd_);
        throw std::runtime_error("Failed to map file");
    }
}

// 假设我们想实现一个简单的按行读取方法
std::vector<std::string> readLines() const {
    std::vector<std::string> lines;
    const char* ptr = data();
    const char* end = ptr + size();
    while (ptr < end) {
        while (ptr < end && *ptr != '\n') ++ptr;
        lines.emplace_back(data_, ptr - data_);
        if (ptr < end) ++ptr; // Skip newline character
    }
    return lines;
}

// 其他可能的改进,如智能指针包装、更复杂的错误处理等

请注意,对于追加写入,直接使用mmap可能不够直观,因为映射区域通常不会自动增长。在实际应用中,你可能需要在每次写入之前检查和调整映射区域的大小,或者考虑使用其他策略来处理文件增长,比如分块映射或传统的文件I/O结合内存映射。

这些改进点都是为了提升封装类的灵活性、安全性和易用性,具体实现可以根据实际应用场景的需求进行调整。

需要特别注意以下几个核心要点以确保高效、稳定且安全的应用:

  1. 内存页对齐:mmap映射区域的大小必须是物理页大小的整数倍。大多数系统中,页大小通常是4KB。如果不按页对齐,映射可能会失败或导致未预期的行为。

  2. 权限控制:在调用mmap时,需正确设置内存保护标志(如PROT_READ, PROT_WRITE),确保对映射区域的访问权限与预期相符。同时,确保文件描述符在打开时具有相应的读/写权限。

  3. 错误处理:充分考虑并处理所有可能的错误情况,如文件打开失败、内存映射失败、文件大小查询失败等。使用RAII(Resource Acquisition Is Initialization)模式确保资源(如文件描述符)即使在异常情况下也能被正确释放。

  4. 内存同步:当映射区域用于写操作时,需注意内存与磁盘之间的同步问题。可以使用msync()确保修改及时写回磁盘,尤其是在程序退出前或映射解除前,以防止数据丢失。

  5. 文件大小管理:如果映射的文件可能在程序运行期间改变大小,需要实现机制来动态调整映射区域。这通常涉及解映射旧区域、调整文件大小(如使用ftruncate())、然后重新映射。

  6. 并发访问:在多线程或多进程环境中,当多个进程/线程映射同一文件时,需要注意同步和互斥问题,以避免数据竞争和不一致。

  7. 性能考量:虽然mmap可以提高I/O效率,但在小文件或非连续访问模式下可能不如传统I/O高效。评估应用的具体场景,选择合适的读写策略。

  8. 资源限制:考虑系统的资源限制,如地址空间大小、最大文件句柄数等,避免因资源耗尽导致的失败。

  9. 平台兼容性:虽然示例基于POSIX标准,但不同操作系统和内核版本之间可能存在差异。确保代码在目标平台上经过充分测试。

  10. 安全性:确保在处理用户提供的文件路径或数据时进行适当的验证和清理,防止潜在的安全漏洞,如路径遍历攻击。

综合考虑以上要点,可以构建出既高效又可靠的基于mmap的文件读写工具。

😍😍 海量H5小游戏、微信小游戏、Web casualgame源码😍😍
😍😍试玩地址: https://www.bojiogame.sg😍😍
😍看上哪一款,需要源码的csdn私信我😍

————————————————

​最后我们放松一下眼睛
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1906426.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】stack和queue的模拟实现 双端队列deque的介绍

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; STL || C 目录 &#x1f308;前言&#x1f525;stack的模拟实现&#x1f525;queue的模拟实现&#x1f525;deque&#xff08;双端队列&#xff09;deque的缺陷 &#x1f308;为什么选择…

从无计划到项目管理高手,只需避开这两大误区!

在项目管理的过程中&#xff0c;制定计划是不可或缺的一环。然而&#xff0c;在实践中&#xff0c;我们往往会遇到两种常见的误区&#xff0c;这些误区不仅阻碍了计划的有效实施&#xff0c;还可能让我们在追求目标的道路上迷失方向。 误区一&#xff1a;认为没有什么可计划的…

充电桩开源平台,开发流程有图有工具

慧哥充电桩开源平台产品研发流程是确保产品从概念阶段到市场推广阶段的有序进行的关键。以下是对您给出的步骤的详细解释和建议&#xff1a; 设计业务流程: 在这一步&#xff0c;团队需要确定产品的核心功能、目标用户以及如何满足用户需求。进行市场调研&#xff0c;了解竞争…

无线网卡怎么连接台式电脑?让上网更便捷!

随着无线网络的普及&#xff0c;越来越多的台式电脑用户希望通过无线网卡连接到互联网。无线网卡为台式电脑提供了无线连接的便利性&#xff0c;避免了有线网络的束缚。本文将详细介绍无线网卡怎么连接台式电脑的四种方法&#xff0c;包括使用USB无线网卡、内置无线网卡以及使用…

探索企业信用巅峰:3A企业认证的魅力与价值

在现代商业环境中&#xff0c;企业的信用和信誉是其发展的核心要素之一。3A企业认证作为信用评级的最高等级&#xff0c;正在吸引越来越多企业的关注。究竟什么是3A企业认证&#xff1f;它为什么对企业如此重要&#xff1f;本文将深入探讨3A企业认证的独特魅力和巨大价值。 3A企…

使用vue3-treeselect问题

1.当vue3-treeselect是单选时&#xff0c;使用watch监听绑定value&#xff0c;无法监听到值清空 对照后将:value改为v-model&#xff0c;如图 2.使用vue3-treeselect全部清空按钮如何置空select的值&#xff0c;使用watch监听 多选&#xff1a;pageInfo.officeName(val) {// …

mybatis中的标签

在MyBatis中&#xff0c;除了基本的SQL映射功能外&#xff0c;还有许多用于动态SQL构建的标签。这些标签允许我们根据不同的条件和需求构建复杂的SQL语句。主要的动态SQL标签包括<if>, <choose>, <when>, <otherwise>, <trim>, <set>, <…

加密与安全_ 解读非对称密钥解决密钥配送问题的四个方案

文章目录 Pre对称密钥的死穴 - 经典的密钥配送问题什么是非对称密钥非对称密钥解决密钥配送问题的四个方案共享密钥密钥分配中心&#xff08;KDC&#xff09;Diffie-Hellman 密钥交换体系公钥密码体系RSA算法 Pre 对称密钥的死穴 - 经典的密钥配送问题 假设 Alice 和 Bob 两个人…

什么是海外仓管理自动化?策略及落地实施步骤指南

作为海外仓的管理者&#xff0c;你每天都面临提高海外仓运营效率、降低成本和满足客户需求的问题。海外仓自动化管理技术为这些问题提供了不错的解决思路&#xff0c;不过和任何新技术一样&#xff0c;从策略到落地实施&#xff0c;都有一个对基础逻辑的认识过程。 今天我们整…

安装 node.js 完整教程

安装 官方下载地址: https://nodejs.org/en/ 下载LTS版本&#xff08;长期稳定版本&#xff09; 安装可以更改安装路径 其余的都是选择 下一步, 安装 安装完成查看 node -v 查看node的版本 npm -v 查看npm的版本(新版的node安装自带安装npm) 配置环境变量 在nodejs文件夹…

关于SQL NOT IN判断失效的情况记录

1.准备测试数据 CREATE TABLE tmp_1 (val integer);CREATE TABLE tmp_2 (val integer, val2 integer);INSERT INTO tmp_1 (val) VALUES (1); INSERT INTO tmp_1 (val) VALUES (2); INSERT INTO tmp_2 (val) VALUES (1); INSERT INTO tmp_2 (val, val2) VALUES (NULL,0);2.测…

基于Address Sanitizer实现Android NDK的内存错误检测DEMO

1.简介 基于Address Sanitizer实现Android NDK的内存错误检测Demo。 ps:适用于Android 13&#xff08;API 级别 33&#xff09;以下的设备&#xff0c;Android 14&#xff08;API 级别 34&#xff09;或更高版本的 ARM64设备推荐使用HWAddress Sanitizer配置更简单。 GitHub源…

算法题:用JS实现删除链表的倒数第N个节点

学习目标&#xff1a; 删除链表的倒数第N个节点 leetcode原题链接 学习内容&#xff1a; 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点 示例 1: 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例 2: 输入&a…

Python酷库之旅-第三方库Pandas(010)

目录 一、用法精讲 22、pandas.read_hdf函数 22-1、语法 22-2、参数 22-3、功能 22-4、返回值 22-5、说明 22-6、用法 22-6-1、数据准备 22-6-2、代码示例 22-6-3、结果输出 23、pandas.HDFStore.put方法 23-1、语法 23-2、参数 23-3、功能 23-4、返回值 23-5…

【APK】Unity出android包,报错 Gradle build failed.See the Console for details

参考大佬的博客&#xff1a;报错&#xff1a;Gradle build failed.See the Console for details.&#xff08;已解决&#xff09;_starting a gradle daemon, 1 incompatible daemon co-CSDN博客 本地出Android包&#xff0c;Build失败 解决办法&#xff1a; 1.下载一个低版本…

防爆手机终端安全管理平台

防爆手机终端安全管理平台能够满足国家能源、化工企业对安全生产信息化运行需求&#xff0c;能够快速搭建起高效、快捷的移动终端管理平台&#xff0c;提高企业安全生产管理水平&#xff0c;保证企业的安全运行和可持续发展。#防爆手机 #终端安全 #移动安全 能源、化工等生产单…

Thingsboard 系列之通过 ESP8266+MQTT 模拟设备上报数据到平台

前置工作 Thingsboard平台ESP 8266 NodeMCU 开发板IDE&#xff1a; Arduino 或 VScode 均可 服务端具体对接流程 系统管理员账号通过 Thingsboard 控制面板创建租户等信息并以租户账号登录 实体 —> 设备维护具体设备信息 创建完成后通过管理凭据修改或直接复制访问令牌…

docker安装oracle 11g

最近把一些常用数据库都移到docker了&#xff0c;而且是windows下&#xff0c;很是方便。偶尔还是要用一下Oracle&#xff0c;今天就试一下安装oracle 11g 在docker上。 一、搜索并拉取镜像 docker search oracle_11gdocker pull ![在这里插入图片描述](https://i-blog.csdni…

刷题之合并两个有序数组(leetcode)

因为换了手机号码&#xff0c;之前leetcode的账号登不上去了&#xff0c;正好太久不刷题&#xff0c;很多思路都没了&#xff0c;所以重新开始刷leetcode&#xff01; 这道题很简单&#xff0c;指针模拟一下&#xff0c;从后往前考虑&#xff0c;先看最大值。 class Solution…

STM32蓝牙HID实战:打造低功耗、高性能的客制化键盘

一、项目概述 本项目旨在使用STM32单片机打造一款功能强大的蓝牙客制化键盘&#xff0c;它拥有以下特点&#xff1a; 九键布局&#xff0c;小巧便携: 满足日常使用需求&#xff0c;方便携带。全键可编程: 所有按键和旋钮均可通过电脑软件自定义快捷键&#xff0c;实现个性化功…