代码改变世界

缩略图设计初探

2010-03-24 20:37  curer  阅读(649)  评论(0编辑  收藏  举报

第一部分简介:

        最近有幸参加了一个网上的开源项目,需要我设计一个缩略图存储的方法。整个思路主要是模仿XP的实现方式,而对于Win7(或是Vista)里面将所有缩略图统一管理的模式没有采用。主要想法还是一切从简,这也就是初探的来由了。

  整个缩略图的核心算法来自《Inside MoPaQ》http://shadowflare.samods.org/inside_mopaq/

核心是将文件名通过Hash散列到表中,从而达到快速查找的目的。

  MPQ文件,对于大部分大学时间沉浸的游戏——War3是非常重要的,每次War3版本的更替都对这个文件做了修改。的确,对于一个计算机系的非常爱玩的我,现在才分析MPQ实在是惭愧。

  对于我这个文件名,现在姑且命名为MP(MiniPic)。

  这是我第一次用c++来写程序,整个程序很简单,花了差不多1个半月(我这个效率实在不行),和第一篇一样,希望各位大哥能帮忙看看。

第二部分实现:

1. 需求

1.1          这里实现读取,修改固定大小的文件,即每个缩略图的存储空间大小一致。

1.2          根据文件名添加,查找,删除功能。

1.3          文件存储数目固定,不能动态增长。

1.4          储存文件名,使用ID形式访问。

2.存储方案

 

2.1          表结构设计

整个文件包括FileHeader,HashTable,BlockTable,DataHeader,Data构成。通过计算文件名的hash值,将文件名散列到HashTable中,然后根据HashTable中查找BlockTable(文件块表),在BlockTable中查找到实际文件data的位置。读入DataHeader和Data。

  

FileHeader

HashTableItem

HashTableItem

......

HashTableItem

HashTableItem

BlockTableItem

BlockTableItem

BlockTableItem

......

BlockTableItem

BlockTableItem

DataHeader

Data

DataHeader

Data

......

......

2.2          内存中存储。

内存中存储FileHeader、HashTable、BlockTable、DebrisBlock,dataBuffer。

DebrisBlock是队列结构。存储因删除操作而产生的碎片。

FileHeader,HashTable,BlockTable是数组。dataBuffer数据缓存

 

3.实现方案

             实际还需要增加功能,以下是核心部分。对于每个功能的算法也只写核心部分。

3.1          增加

3.1.1     将文件名转换为数字。这里使用的是MPQ文件算法。

 

代码
DWORD HashManager::HashString( TCHAR *lpszFileName,DWORD dwHashType )
{
BYTE
*key = (BYTE *)lpszFileName;
DWORD seed1
= 0x7FED7FED, seed2 = 0xEEEEEEEE;
int ch;

while(*key != 0)
{
ch
= toupper(*key++);
//原来是char,这里处理TCHAR中的0,这里也有小问题,如果要更多的地方使用,还需要做大头小头机数据转换
if(!*key)
{
++key;
}
seed1
= cryptTable[(dwHashType << 8) + ch] ^ (seed1 + seed2);
seed2
= ch + seed1 + seed2 + (seed2 << 5) + 3;
}
return seed1;
}

 

 

3.1.2     插入HashTable。

在处理冲突的时候,是采用最简单的线性探查法,在判断位置相同的时候,采用增加2个Hash值的方法。虽然不能保证肯定不会出问题。但是3个相同的概率很低,大概是10的22.3次方分之一,这个是在参考《Inside MoPaQ》,中的数据,是否可靠。我无法确定。

 

代码
BOOL HashManager::InsertHashTable( TCHAR *lpszString, FILETIME FileTime, LONG &HashPos, DWORD dwFlags,
BOOL
&isNew//设置,是否是新文件
)
{
ASSERT( m_pHashIndexTable
!= NULL );
isNew
= TRUE;
const DWORD HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2;
DWORD nHash
= HashString(lpszString, HASH_OFFSET);
DWORD nHashA
= HashString(lpszString, HASH_A);
DWORD nHashB
= HashString(lpszString, HASH_B);
DWORD nHashStart
= nHash %m_hashTableLength;
DWORD nHashPos
= nHashStart;
DWORD x
=m_pHashIndexTable[nHashPos].dwFlag & FILE_EXISTS;
DWORD y
= m_pHashIndexTable[nHashPos].dwFlag & FILE_DELETED;
while( x!=0 )
{
if( y==FILE_DELETED )
{
isNew
= FALSE;
break;
}
//如果有同名文件
if (m_pHashIndexTable[nHashPos].dwHashValueA == nHashA  
&& m_pHashIndexTable[nHashPos].dwHashValueB == nHashB)
{
if( dwFlags & INSERT_CHECK_FILETIME )
{
if(m_pHashIndexTable[nHashPos].dwFileLastModiTime.dwHighDateTime == FileTime.dwHighDateTime
&& m_pHashIndexTable[nHashPos].dwFileLastModiTime.dwLowDateTime == FileTime.dwLowDateTime)
{
return FALSE;//说明文件真实存在,插入失败
}
else
{
isNew
= FALSE;
break;
}
}
if( dwFlags & INSERT_REPLACE_EXISTING )
{
isNew
= FALSE;
break;
}
else
{
return FALSE;
}
}
nHashPos
= (nHashPos + 1) % m_hashTableLength;
if(nHashPos == nHashStart)
{
return FALSE;
}
x
=m_pHashIndexTable[nHashPos].dwFlag & FILE_EXISTS;
y
= m_pHashIndexTable[nHashPos].dwFlag & FILE_DELETED;
}
m_pHashIndexTable[nHashPos].dwFlag
= FILE_EXISTS;
m_pHashIndexTable[nHashPos].dwHashValueA
= nHashA;
m_pHashIndexTable[nHashPos].dwHashValueB
= nHashB;
m_pHashIndexTable[nHashPos].iBlockIndex
=nHashPos;
if( dwFlags & INSERT_CHECK_FILETIME )
{
m_pHashIndexTable[nHashPos].dwFileLastModiTime.dwHighDateTime
= FileTime.dwHighDateTime;
m_pHashIndexTable[nHashPos].dwFileLastModiTime.dwLowDateTime
= FileTime.dwLowDateTime;
}
HashPos
= nHashPos;
return TRUE;
}

 

3.1.3     插入BlockTable

如果DebrisBlock(空闲表)为空,则将BlockTable中的文件偏移量指向文件最后。

如果DebrisBlock不为空,则将BlockTable中的文件偏移量指向BlockTable头结点,DebrisBlock头结点出列。

3.2          查找

3.2.1     查找HashTable 

 

 

代码
LONG HashManager::GetHashTablePos( TCHAR *lpszString )
{
const DWORD HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2;
DWORD nHash
= HashString(lpszString, HASH_OFFSET);
DWORD nHashA
= HashString(lpszString, HASH_A);
DWORD nHashB
= HashString(lpszString, HASH_B);
DWORD nHashStart
= nHash % m_hashTableLength;
DWORD nHashPos
= nHashStart;

DWORD x
=m_pHashIndexTable[nHashPos].dwFlag & FILE_EXISTS;
while( x != 0 )
{
if(m_pHashIndexTable[nHashPos].dwHashValueA == nHashA
&& m_pHashIndexTable[nHashPos].dwHashValueB == nHashB)
{
return nHashPos;
}
else
{
nHashPos
= (nHashPos + 1) % m_hashTableLength;
}
if(nHashPos == nHashStart)
break;
x
=m_pHashIndexTable[nHashPos].dwFlag & FILE_EXISTS;
}
return -1; //没有找到
}

 

3.3          删除

3.3.1    查找HashTable :GetHashTablePos;

3.3.2    查找到则DebrisBlock入列文件偏移量,

3.3.3    在HashTable中Flag标记位FILE_DELETED

3.3.4    备注:这里不是真正删除。真正删除在MPFileClose()中的ManagerFileDebris进行。

 

代码
BOOL MiniPic::DeleteData(TCHAR *lpszString)
{
ASSERT(m_isInit);
ASSERT( m_pHashManager
!= NULL );
ASSERT( lpszString
!= NULL );
LONG hashTablePos;
hashTablePos
= m_pHashManager->GetHashTablePos(lpszString);
if( hashTablePos == -1 )
{
return FALSE;
}
MINIPICDEBRIS debris;
debris.dwBlockTableIndex
= hashTablePos;
debris.dwDebrisStartAt
= m_pBlockTableManager->GetBlockTable()[hashTablePos].FileStartAt;
m_pHashManager
->DeleteHashTable(hashTablePos);
m_debrisBlock.push_back(debris);
DebugPrintString(L
"Free data the pos is %d\n",hashTablePos);
#ifdef TEST
if(count1>=testNum)
{
count1
=0;
}
testIns[count1
++]=hashTablePos;
#endif
m_fileHeader.childFileCount
--;
return TRUE;
}

 

 

3.4           回收资源

因为在删除的时候只是在hashTable中做了一个Deleted的标记,并没有真正删除文件。若是长时间使用,则会浪费很多空间。此时需要整理。

收集空闲区域。排序,将文件尾部数据移动至最早空闲处,删除空闲处,更新hashTable。

 

代码
BOOL MiniPic::ManagerFileDebris( LPVOID lpBuffer, const UINT count )
{
ASSERT( lpBuffer
!= NULL );
ASSERT( m_MPFile
!= NULL );
if( m_debrisBlock.empty() )
{
return TRUE;
}
MINIPICDATAITEMHEADER dataItemHeader;
m_debrisBlock.sort();

/*
1首先将比较Debris block末尾,若是处在文件的最后,则将Debris block末尾数据出列。并向前移动fileEndPointer
保证Debris block要小于文件末尾。
2确认Debris block是否为空。
3将末尾文件数据移动到Debris block的首部位置,fileEndPointer--,并将Debris block首部数据出列。
在判断条件1,2
*/
//查找Debris。
while( !m_debrisBlock.empty() )
{
while( m_debrisBlock.back().dwDebrisStartAt + count
+ sizeof(MINIPICDATAITEMHEADER) == m_fileDataEndPointer )//处理文件末尾的空闲区域
{
m_fileDataEndPointer
-= count + sizeof(MINIPICDATAITEMHEADER);
m_pHashManager
->ClearHashTable( m_debrisBlock.back().dwBlockTableIndex );//New add
m_debrisBlock.pop_back();
if( m_debrisBlock.empty() )
{
break;
}
}
if( !m_debrisBlock.empty() )
{
if( 0xFFFFFFFF == m_MPFile->Seek( m_fileDataEndPointer - count - sizeof(MINIPICDATAITEMHEADER),
FILE_BEGIN ))
//指向最后文件块开始处
{
return FALSE;
}
m_MPFile
->Read( &dataItemHeader, sizeof(MINIPICDATAITEMHEADER) );
m_MPFile
->Read( lpBuffer, count );
if( 0xFFFFFFFF == m_MPFile->Seek( m_debrisBlock.front().dwDebrisStartAt, FILE_BEGIN ) )
{
return FALSE;
}
m_MPFile
->Write( &dataItemHeader, sizeof(MINIPICDATAITEMHEADER) );
m_MPFile
->Write( lpBuffer, count );
m_fileDataEndPointer
-= count+sizeof(MINIPICDATAITEMHEADER);
m_pBlockTableManager
->SetBlockTableFileStartAt( dataItemHeader.dwBlockTableIndex,
m_debrisBlock.front().dwDebrisStartAt );
//将原来指向空此间的HashTable标记位空。
m_pHashManager->ClearHashTable( m_debrisBlock.front().dwBlockTableIndex );
m_debrisBlock.pop_front();
DebugPrintString(L
"find freedata set in it ,and the pos is %d\n",dataItemHeader.dwBlockTableIndex);
}
}
m_fileHeader.bfSize
= m_fileDataEndPointer;
return TRUE;
}

 

 

第三部分 使用

BOOL      MPFileOpen( LPCTSTR lpFileName );

void          MPFileClose();                            

BOOL      MPFileAddFile( LPCVOID lpBuffer, TCHAR *lpszString, const DWORD fileSize,

                 const FILETIME LastModiTime, LONG &lIndex, DWORD dwFlags

);

BOOL       MPFileReadFile( LPVOID lpBuffer, TCHAR *lpszString );

BOOL       MPFileDeleteFile(TCHAR *lpszString);

void           MPFileFlushData();

BOOL       MPFileReadFileByIndex( LPVOID lpBuffer, const DWORD index );

BOOL       MPFileGetFileName( TCHAR *FileNameBuffer, const  size_t count,

                                                        const DWORD index

);

BOOL        MPFileReName( LPCTSTR lpNewFileName, LPCTSTR lpOldFileName);

第四部分 未解决问题

  • 没有实现动态增长,处理文件的最大值有限。
  • 没有对文件正确性做足够保证。没有加入CRC or MD5校验。
  • 没有设定返回的错误码只有TRUE,FALSE,不利于识别错误问题。
  • 算法的有些地方效率不高。还需改进。
  • 数据的大小没有做限制,插入过多数据会溢出。

第五部分 疑问

  1. 在处理非常多的数据时,是否该将文件压缩?
  2. 许多管理文件系统都加入了最近访问文件列表,这些在有大量文件读入时效率高,是否可以设计类似结构?
  3. 如何处理异常,特别是在写入时产生的异常,导致文件写入错误。如何能够更好的解决类似问题?在发生异常的时候,如何能够做到高效的完全释放资源?
  4. 把HashTable,BlockTable放置到文件末尾,方便文件以后动态扩展。但是具体的如何扩容hashTable,可能不好办。
  5. 在文件头部分增加一个Flag,标示文件的状态。(在读入的时候标记位0,最后如果没有异常则标记位1,那么如果在这个文件出现了问题,那么就可以通过检测这个Flag来判断文件异常),若是设计的更好,可以通过这个检测那部分有问题,而对问题修正。
  6. 我之前谈到的异常情况,导致有的文件没有即时更新。我的想法是来自IE浏览器,他会记录上次出错的位置,那么这时候就可以启动恢复程序。要是MP文件的Flag是正常,那么就说明问题不在MP文件处理,可能是别的地方异常导致结束,那么就可以重新查找,根据文件创建的时间来判断是否文件变化过,那么这样就比重新生成要快多了。我这里应该提供用来恢复的接口,这个由外部程序调用它。

第六部分 写给自己

如果仔细看的话,这个并不是很复杂的东西,只是实现了最基本最简单的内容,但是自己做的依然很不完整。问题依然很多。这也映射出自己学习不仔细,编程依然马虎。很多问题都想的不周全。这也进一步说明自己基础不扎实。

我相信我这里肯定还有不少问题,希望大家有时间给小弟看看,与此同时我也会不断改进,把这个做到自己的极限。