随笔-5  评论-33  文章-0  trackbacks-0

    在实际的开发过程中,我们可能需要在文件生成时就立即将文件更改为指定的大小,以便于后续简化对文件的并发操作,
这一点在各类的下载工具中有很好的体现。
    要满足这一需求,通常可以使用的最直观的方式是:生成文件后,通过循环向文件中写入任意数据,直到文件大小达到预期的大小。
对于较小的文件,这种方法可以接受,但是如果文件大小过大,那么这种方法就显得不够简洁且低效。
    这里介绍一种高效且简单的方法,基于函数"lseek"。
    执行"man lseek"之后,我们可以看到关于lseek特性的关键信息,如下:
    The lseek() function shall allow the file offset to be set beyond the end of the existing data in the file.
    If data is later written at this point, subsequent reads of data in the gap shall return bytes with the
    value 0 until data is actually written into the gap.
    The lseek() function shall not, by itself, extend the size of a file.

简单的总结就是:
1. lseek 函数本身不会扩展文件的大小.
2. lseek 允许文件的偏移值超过文件的末端,如果下一次在这个偏移点写入数据,那么在偏移之前的文件末端与偏移点之间的数据将会自动填充为0。

了解了上面几点,大家应该想到了扩展文件大小将是一件多么简洁高效的事情了:) 



//! @brief 扩展文件大小
/*!
    扩展一个已打开的文件的大小
    @note <b>此函数只能用于扩展一个文件的大小,不能缩小一个文件的大小.如果需要缩小一个文件大小可以使用 Linux API "truncate"</b>。
          此函数不会改变文件指针的位置,即如果文件原大小为N,文件指针位置为K( K >= 0 , K <=N ),调用此函数后,文件大小扩展为 M( M>N ),
          那么在调用函数完成后,文件指针仍然位于位置K。
          <b>此函数能且只能"沿着向文件末端的方向扩展文件大小"</b>。
    @param iFileHandle 扩展大小的文件的句柄
    @param iNewSize 扩展后的文件的大小
    @return 如果调用成功,返回<b>true</b>;如果调用失败,返回<b>false</b>。
 
*/

bool EnlargeFile( int iFileHandle , off_t iNewSize )
{
    
if( ( iFileHandle < 0 ) || ( iNewSize < 0 ) )
    
{
        LOG_DEBUG_INFO( 
"parameter for < EnlargeFile( %d , %ld ) > is not valid" , iFileHandle , iNewSize );
        
return false;
    }

    
    
struct stat fileState;
    
int iStatus = fstat( iFileHandle , &fileState );
    
if0 != iStatus )
    
{
        LOG_DEBUG_INFO( 
"invoke < fstat( %d , %p ) > fail , ErrCode:%d , ErrMsg:%s" , \
                        iFileHandle , 
&fileState , errno , strerror( errno ) );
        
return false;
    }

    
    
const off_t iCurSize = fileState.st_size;
    
if( iNewSize <= iCurSize )
    
{
        LOG_DEBUG_INFO( 
"new file size = %ld , current file size = %ld , can not resize file to smaller size." , \
                        iNewSize , iCurSize );
        
return false;
    }


    
const off_t iCurPos = lseek( iFileHandle , 0 , SEEK_CUR );                //保存文件指针的当前位置以便于在扩展文件大小后恢复到当前位置
    if( iCurPos < 0 )
    
{
        LOG_DEBUG_INFO( 
"invoke < lseek( %d , 0 , SEEK_CUR ) > fail , ErrCode:%d , ErrMsg:%s" , \
                        iFileHandle , errno , strerror( errno ) );
        
return false;
    }

    
    
const off_t iMoveOffset = iNewSize - 1;
    iStatus 
= lseek( iFileHandle , iMoveOffset , SEEK_SET );
    
if( iStatus != iMoveOffset )
    
{
        LOG_DEBUG_INFO( 
"invoke < lseek( %d , %ld , SEEK_SET ) > fail , ErrCode:%d , ErrMsg:%s" , \
                        iFileHandle , iMoveOffset ,  errno , strerror( errno ) );
        
return false;        
    }

    
    
const int WRITE_BYTE_COUNT = 1;
    iStatus 
= write( iFileHandle , " " , WRITE_BYTE_COUNT );                //写入一个字节的数据,完成对文件大小的更改
    if( iStatus != WRITE_BYTE_COUNT )
    
{
        LOG_DEBUG_INFO( 
"invoke < write( %d , \" \" , %d ) > fail , ErrCode:%d , ErrMsg:%s" , \
                        iFileHandle , WRITE_BYTE_COUNT ,  errno , strerror( errno ) );
        
return false;        
    }

    
    iStatus 
= lseek( iFileHandle , iCurPos , SEEK_SET );                        //恢复文件指针到之前保存的文件位置
    if( iStatus != iCurPos )
    
{
        LOG_DEBUG_INFO( 
"invoke < lseek( %d , %ld , SEEK_SET ) > fail , ErrCode:%d , ErrMsg:%s" , \
                        iFileHandle , iCurPos ,  errno , strerror( errno ) );
        
return false;    
    }

    
    
return true;
}




点击下载

Linux manpages CHM文件下载:
Linux_manpages_part_1_of_4
Linux_manpages_part_2_of_4
Linux_manpages_part_3_of_4
Linux_manpages_part_4_of_4
(由于cppblog限制单个文件上传大小,这里将CHM分割成4部分,全部下载后解压即可).
如果下载速度太慢,请前往这里下载.


posted on 2010-03-01 21:48 luckycat 阅读(6272) 评论(5)  编辑 收藏 引用 所属分类: Linux

评论:
# re: Linux下快速扩展文件大小 2010-03-02 09:47 | 宇宙无敌之凤姐
windows只要一个SetFilePointer,SetEndOfFile就搞定了。  回复  更多评论
  
# re: Linux下快速扩展文件大小 2010-03-02 12:53 | luckycat
谢谢指教!
以前在Win32下做过一段时间,深感Microsoft的巨大努力给我们带来的便利:) 海量的MSDN和丰富的Win32 API让我们遇到问题有据可查,
同时也减少了大量"reinvent the wheel"的时间.
但是到了*nix下面,很多东西都不一样了,*nix的哲学是"提供解决问题的机制而不是具体的实现",相反,Win32的哲学是"提供具体的实现但是不告诉你具体的机制"。
所以这篇文章的出发点就是"利用Linux提供的机制来解决一个实际的问题",形式上与Win32的"SetFilePointer & SetEndOfFile"组合不一样,但是仔细分析一下,
它们是如此的相似:按照你的建议"先调用 SetFilePointer(设置文件逻辑指针位置) 然后调用 SetEndOfFile(设置文件物理末端位置)"即可快速扩展文件大小。
在上面的代码中将参数合法性判断以及对应的函数调用状态判断去掉,简化一下就是下面这样了:

bool EnlargeFile( int iFileHandle , off_t iNewSize )
{

1. lseek( iFileHandle , 0 , SEEK_CUR ); //保存文件指针的当前位置以便于在扩展文件大小后恢复到当前位置
2. lseek( iFileHandle , iMoveOffset , SEEK_SET );
3. write( iFileHandle , " " , WRITE_BYTE_COUNT ); //写入一个字节的数据,完成对文件大小的更改
4. lseek( iFileHandle , iCurPos , SEEK_SET ); //恢复文件指针到之前保存的文件位置

return true;
}

其中的第1行和第4行是为了在扩展文件的过程中保存和恢复文件指针位置,如果我们将这一点也简化掉(实际上必须保留),如下:
bool EnlargeFile( int iFileHandle , off_t iNewSize )
{
// 设置文件指针(这里是逻辑指针)位置,相当于Win32下调用 SetFilePointer
2. lseek( iFileHandle , iMoveOffset , SEEK_SET );

// 写入一个字节的数据,完成对文件大小的更改,即是设置了文件的物理末端指针位置,相当于调用了 SetEndOfFile
3. write( iFileHandle , " " , WRITE_BYTE_COUNT );

return true;
}

这一次很清晰了,形式上不一样,但是本质上很相近了。

  回复  更多评论
  
# re: Linux下快速扩展文件大小 2010-03-02 19:40 | 阿福
难道truncate这个函数不能起到同样的效果?  回复  更多评论
  
# re: Linux下快速扩展文件大小 2010-03-02 21:28 | luckycat
@阿福:
一直都把truncate用作截断文件,没有发现truncate还可以用于扩展文件大小,刚才看了一下 man 文档:
int truncate(const char *path, off_t length);
If the file previously was larger than length, the extra data is discarded.
If the file was previously shorter than length, its size is increased, and the extended area appears as if it were zero-filled.
这样一来,截断和扩展文件都可以用"truncate"来完成,这样相对于上面的EnlargeFile就更简洁了而且基于"truncate"的形式只需要一次系统调用即可实现相同的效果,效率上也更有优势.
看来这次真的是"reinvent the wheel"了:(
thank you for reminding me.
最后分享一个 linux 2.6 的 man pages 打包成的CHM文件,在上面的下载文件中.
  回复  更多评论
  
# re: Linux下快速扩展文件大小 2010-03-03 18:30 | Jakcie
An application can truncate or extend a file by calling SetEndOfFile on sets the end-of-file marker to the current position of the file pointer.

Note that when a file is extended, the contents between the old and new end-of-file locations are not defined.
在Windows下, 可以用SetEndOfFile   回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理