如何安全的保存数据到文件

如何安全的保存数据到文件

作者:付黎
主页:www.crazy-bit.com
下载源码

  看到这个标题您一定很迷惑:难道保存文件还不安全吗?fwrite,WriteFile这些函数有bug吗?当然不是,全世界几亿台电脑时刻在调用它们,有bug比尔早被口水淹死了。我这里要说的是另外一种情况,请看:
void SaveSetting (const char* strFile, const char* pBuffer, int nLength)
{
    FILE   * pFL = fopen ("c://setting.txt", "w+") ;
    fwrite (pBuffer, 1, nLength, pFL) ;
    fclose (pFL) ;
}

  有问题吗?绝大部分人会认为没错,对,确实没错@_@。。。先别仍鸡蛋。让我们考虑这样一种情况:在写文件的时候遇到断电或死机等异常退出,那目标文件就会不完整,要是配置信息这样的重要文件,损坏后可能就导致程序运行不了。也许有人会说这是杞人忧天,写文件也就几十毫秒,被中断的概率不亚于中彩票。。。最开始我也没怀疑到,但很高兴上帝如此偏爱我,总是让我偶尔(尤其关机时)又不可重现的碰到这种情况。而且,随着配置信息的增多,中奖概率也在上升。很多时候,越是这种看不上眼的小东西就越容易被人忽视。

  究其本质,是写文件操作不能象 InterlockedExchange 之类的函数是原子操作:要么全部完成,要么一点不做。那么我们有没有办法把这一过程用原子操作来实现呢,目前我还没找到,因此提出下面的一种解决方案。

1)在目标路径里创建一个临时文件。
2)把数据写在临时文件里
3)临时文件重命名为目标文件名。

OK,现在我们模拟出了写文件的原子操作,这个文件要么不存在,要么完美无缺,不会出现只有一部分的情况了^_^ 剩下的任务就是根据这个原理封装出一个类,方便重用:

/// 定义
class FCSafeSaveFile
{
public:
    FCSafeSaveFile (LPCTSTR szFilename, bool bSameExt=false) ;
    ~FCSafeSaveFile() ;

    bstr_t GetTempFilename() const ;

    void ReplaceFile() ;
    void DisableReplace() ;
    void EnableReplace() ;	

private:
    bstr_t   m_strDest ;
    bstr_t   m_strTemp ;
    BOOL     m_bHasReplace ;
};

/// 构造函数,在同一目录生成一个临时文件。
FCSafeSaveFile::FCSafeSaveFile (LPCTSTR szFilename, bool bSameExt=false)
{
    m_strDest = szFilename ;
    m_bHasReplace = FALSE ;

    // get temp path
    TCHAR     szPath[MAX_PATH],
              szDriver[_MAX_DRIVE],
              szDir[MAX_PATH] ;
    ::_tsplitpath (szFilename, szDriver, szDir, NULL, NULL) ;
    ::_tmakepath (szPath, szDriver, szDir, NULL, NULL) ;

    // get a temp filename
    TCHAR     szTmp[MAX_PATH] ;
    ::GetTempFileName (szPath, _T("foo"), 0, szTmp) ;
    m_strTemp = szTmp ;

    if (bSameExt)
    {
        TCHAR     szExt[_MAX_EXT] ;
        ::_tsplitpath (szFilename, NULL, NULL, NULL, szExt) ;
        bstr_t    strNew = m_strTemp + szExt ;
        ::_trename (m_strTemp, strNew) ;
        m_strTemp = strNew ;
    }
}

/// 安全替换文件,请确认所有作用于临时文件的句柄都已关闭
void FCSafeSaveFile::ReplaceFile()
{
    // delete exist file
    if (!PathFileExists(m_strDest))
    {
        // rename
        ::_trename (m_strTemp, m_strDest) ;
    }
    else
    {
        ::SetFileAttributes (m_strDest, FILE_ATTRIBUTE_NORMAL) ;
        if (::DeleteFile (m_strDest))
        {
            // rename
            ::_trename (m_strTemp, m_strDest) ;
        }
        else
        {
            assert(false) ;
            ::DeleteFile (m_strTemp) ;
        }
    }
    m_bHasReplace = TRUE ;
}

/// 没有人希望被要求必须调用 ReplaceFile,我们交给编译器和析构来做这事
FCSafeSaveFile::~FCSafeSaveFile()
{
    ReplaceFile() ;
    if (PathFileExists(m_strTemp))
    {
        assert(false) ;
        ::DeleteFile (m_strTemp) ;
        assert (!PathFileExists(m_strTemp)) ;
    }
}
现在我们可以用我们的类修改上面的代码,让它更安全:
void SaveSetting (const char* strFile, const char* pBuffer, int nLength)
{
    FCSafeSaveFile   ssf (TEXT("c://setting.txt")) ;

    FILE   * pFL = fopen (ssf.GetTempFilename(), "w+") ;
    fwrite (pBuffer, 1, nLength, pFL) ;
    fclose (pFL) ;
}
这样,我们就保证了目标文件的安全
似乎很完美了,考虑一个问题,如果你的代码维护者不清楚这段代码作用,在其中加入了一些return语句中途返回,文件替换操作还是会被执行,还是不完整。
void SaveSetting (const char* strFile, const char* pBuffer, int nLength)
{
    FCSafeSaveFile   ssf (TEXT("c://setting.txt")) ;
    ssf.DisableReplace() ;

    if (nLength != 10)
        return ;

    FILE   * pFL = fopen (ssf.GetTempFilename(), "w+") ;
    fwrite (pBuffer, 1, nLength, pFL) ;
    fclose (pFL) ;
    ssf.EnableReplace() ;
}

这样我们就从结构上尽量避免了维护者可能带来的问题。

这里,我们封装了一个非常非常简单的类,但它很有用,作为构建高层软件的基础,它能帮您省却今后的很多麻烦。

 

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值