|
这段时间,我正在负责升级公司中一个产品的几个版本。这几个系统的基本框架都相同,架构都是以第一个系统为模型,只是后续的产品扩展了许多功能。总的说来这几个产品逻辑也不是很复杂,但因几易开发员,而且没有标准和缺乏设计,所以系统的可维护性较低。
上个星期,我重构了产品中的文件操作方案。原有的操作方案是先以读的方式打开原文件,而后从该文件中不断地读取指定缓冲大小的内容,并对读取的字串进行处理,把处理过后的内容写入到一个临时文件中,最后把原文件删除而把临时文件更名为原文件名。个人基于如下的考虑认为这种方案不适于当前场景:(1)需修改一条内容时,得从文件头一条一条地读取出来,然后判断是修改或者是删除,或者是没有找到则是增加。(2)这种方案对于删除、修改操作都要先逐条地移动记录到临时文件,而后再重命名,所以这种方案对于大文件或者是经常修改的文件是不合适的。(3)由于这块文件操作是多线程的,那么可以假设一个场景 ,TA线程以共享读的方式打开了文件,然后转化到了临时文件,关闭两个文件句柄,TA停止。这时TB也打开原有文件并读取了一段内容,并打开重写了临时文件,接着TB又停止并关闭了文件句柄。TA继续工作,删除原文件重命名临时文件,这时TB线程读了脏数据而TA就有可能提交了错误的数据。当然这种情况是我推理出来的,实际是很少出现的,问题肯定是存在的。有关的这种文件操作方式的其它缺点还可以查阅windows核心编程中的相关章节。其中也有相关的论述。基于上述的分析,我决定重构其实现。我提出了两种备选方案,(1)采用XML的方式,对于文件锁定的方案是以流的方式从独占的文件中读取,完成操作写入时关闭。采用这种方式有利于查找、修改、删除和添加。因为XML文件要先载入内存并解析成DOM树,所以XML文件较大时就不适合。(2)以独占方式打开文件,采用内存映射的方式操作,完成操作后写回并关闭。在创建映射内核对象时以原文件大小再加一个适当的增量,最后根据实际的大小截断文件。通过比较这两种方案在执行50000条记录时,都是1500ms这内。最后,基于与原有格式的兼容性和可能会出现大文件的考虑,我选择了内存映射方式。通过此次重构,大大提高了系统的运行效率和稳定性。
代码如下,供大家点评。
1//头文件 2#pragma once 3 4class ShadowDirText 5{ 6public: 7 ShadowDirText(LPCTSTR szId); 8 virtual ~ShadowDirText(void); 9 10private: 11 ShadowDirText(const ShadowDirText &); 12 ShadowDirText& operator=(const ShadowDirText &); 13 14public: 15 static bool GetDirShadowListFilePath(LPCTSTR szDirPath, CString &strPath); 16 static bool Exists(LPCTSTR szDirPath); 17 static bool Create(LPCTSTR szDirPath); 18 static bool Delete(LPCTSTR szDirPath); 19 static void SetFileName(LPCTSTR szName); 20 21 bool Open(LPCTSTR szDirPath); 22 void Close(); 23 bool RemoveRecord(LPCTSTR szPath, bool bDir); 24 bool AddRecord(LPCTSTR szPath, bool bDir); 25 bool ExistRecord(LPCTSTR szPath, bool bDir); 26 27 bool RemoveAll(); 28 29private: 30 DWORD Find(LPCTSTR szPath, bool bDir); 31 bool FindLineById(DWORD dwSearchPosition, DWORD &dwBegin, DWORD &dwEnd); 32 void FillEnd(); 33 34private: 35 CString m_strId; 36 CString m_strDir; 37 HANDLE m_hFile; 38 HANDLE m_hFileMapping; 39 PVOID m_pvMapView; 40 DWORD m_dwFileSize; 41 DWORD m_dwOriginalSize; 42 43 static const int m_snExtened; 44 static TCHAR m_sszFileName[_MAX_FNAME]; 45 static const CString m_sstrFormatting; 46 static const TCHAR m_sszLineEnd[2]; 47}; 48
1#include "StdAfx.h" 2#include "ShadowDirText.h" 3 4/**/////////////////////////////////////////////////////////////////////////// 5// Constraint and static variable 6// 7// 8/**/////////////////////////////////////////////////////////////////////////// 9const int ShadowDirText::m_snExtened =102400; //100KB 10TCHAR ShadowDirText::m_sszFileName[_MAX_FNAME] = {_T('\0')}; 11 12// attention: 13// m_sstrFormatting and m_sszLineEnd must have the same end symbols. 14// 15const CString ShadowDirText::m_sstrFormatting = _T("id=\"%s\",name=\"%s\",isdir=\"%d\"\r\n"); 16const TCHAR ShadowDirText::m_sszLineEnd[2] = {_T('\r'), _T('\n')}; 17 18/**/////////////////////////////////////////////////////////////////////////// 19// Constructor and Destructor 20// 21// 22/**/////////////////////////////////////////////////////////////////////////// 23ShadowDirText::ShadowDirText(LPCTSTR szId) 24: m_strId(szId) 25{ 26 m_hFile = NULL; 27 m_hFileMapping = NULL; 28 m_pvMapView = NULL; 29 m_dwFileSize = 0; 30} 31 32ShadowDirText::~ShadowDirText(void) 33{ 34 Close(); 35} 36 37/**/////////////////////////////////////////////////////////////////////////// 38// public static methods 39// 40// 41/**/////////////////////////////////////////////////////////////////////////// 42 43bool ShadowDirText::GetDirShadowListFilePath(LPCTSTR szDirPath, CString &strPath) 44{ 45 ATLASSERT(szDirPath && 46 _T("argument 'szDirPath' of ShadowDirListHelper::GetDirShadowListFilePath is null.")); 47 48 strPath = szDirPath; 49 if(strPath.GetLength() == 0) 50 { 51 return false; 52 } 53 if(strPath.GetAt(strPath.GetLength() -1) != _T('\\')) 54 { 55 strPath.AppendChar(_T('\\')); 56 } 57 if(!PathFileExists(strPath)) 58 { 59 return false; 60 } 61 strPath.Append(m_sszFileName); 62 return true; 63} 64 65bool ShadowDirText::Exists( LPCTSTR szDirPath ) 66{ 67 CString strPath; 68 if(!GetDirShadowListFilePath(szDirPath, strPath)) 69 { 70 return false; 71 } 72 if(!PathFileExists(strPath)) 73 { 74 return false; 75 } 76 return true; 77} 78 79bool ShadowDirText::Create(LPCTSTR szDirPath) 80{ 81 CString strPath; 82 if(!GetDirShadowListFilePath(szDirPath, strPath)) 83 { 84 return false; 85 } 86 if(!PathFileExists(strPath)) 87 { 88 HANDLE handle = CreateFile(strPath, 89 GENERIC_READ | GENERIC_WRITE, 90 0, 91 NULL, 92 OPEN_ALWAYS, 93 FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_ARCHIVE, 94 NULL); 95 if(INVALID_HANDLE_VALUE == handle) 96 { 97 return false; 98 } 99 static BYTE unicodeHeader[]= {0xff, 0xfe}; 100 static TCHAR szEnd[] = _T("\r\n"); 101 DWORD dwWrited = 0; 102 WriteFile(handle, unicodeHeader, sizeof(unicodeHeader), &dwWrited,0); 103 WriteFile(handle,szDirPath, (DWORD)(_tcslen(szDirPath) * sizeof(TCHAR)), &dwWrited, 0); 104 WriteFile(handle, szEnd, (DWORD)(_tcslen(szEnd) * sizeof(TCHAR)), &dwWrited, 0); 105 CloseHandle(handle); 106 } 107 return true; 108} 109 110bool ShadowDirText::Delete( LPCTSTR szDirPath ) 111{ 112 CString strPath; 113 if(!GetDirShadowListFilePath(szDirPath, strPath)) 114 { 115 return true; 116 } 117 if(PathFileExists(strPath)) 118 { 119 if(!DeleteFile(strPath)) 120 { 121 return false; 122 } 123 } 124 return true; 125} 126 127void ShadowDirText::SetFileName( LPCTSTR szName ) 128{ 129 ATLASSERT(szName && _T("argument of ShadowDirListHelper::SetFileName is NULL")); 130 _tcscpy_s(m_sszFileName,_MAX_FNAME, szName); 131} 132/**///////////////////////////////////////////////////////////////////////////133// public methods 134// 135// 136/**///////////////////////////////////////////////////////////////////////////137bool ShadowDirText::Open(LPCTSTR szDirPath ) 138{ 139 CString strPath; 140 if(!GetDirShadowListFilePath(szDirPath, strPath)) 141 { 142 return false; 143 } 144 if(!PathFileExists(strPath)) 145 { 146 if(!Create(szDirPath)) 147 { 148 return false; 149 } 150 } 151 Close(); 152 m_strDir = szDirPath; 153 m_hFile = CreateFile(strPath, 154 GENERIC_READ | GENERIC_WRITE, 155 0, 156 NULL, 157 OPEN_EXISTING, 158 FILE_ATTRIBUTE_HIDDEN, 159 0); 160 if(INVALID_HANDLE_VALUE == m_hFile) 161 { 162 return false; 163 } 164 m_dwOriginalSize = m_dwFileSize = GetFileSize(m_hFile, NULL); 165 m_hFileMapping = CreateFileMapping(m_hFile, 166 NULL, 167 PAGE_READWRITE, 168 0, 169 m_dwFileSize + m_snExtened, 170 0); 171 if(NULL == m_hFileMapping) 172 { 173 Close(); 174 return false; 175 } 176 m_pvMapView = MapViewOfFile(m_hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0); 177 if(NULL == m_pvMapView) 178 { 179 Close(); 180 return false; 181 } 182 m_pvMapView = (char*)m_pvMapView + 2; 183 m_dwFileSize -= 2; 184 FillEnd(); 185 return true; 186} 187 188void ShadowDirText::Close() 189{ 190 if(m_pvMapView) 191 { 192 m_pvMapView = (char*)m_pvMapView - 2; 193 ::UnmapViewOfFile(m_pvMapView); 194 m_pvMapView = NULL; 195 } 196 if(m_hFileMapping) 197 { 198 CloseHandle(m_hFileMapping); 199 m_hFileMapping = NULL; 200 } 201 if(m_hFile) 202 { 203 SetFilePointer(m_hFile, m_dwFileSize + 2, NULL, FILE_BEGIN); 204 SetEndOfFile(m_hFile); 205 CloseHandle(m_hFile); 206 m_hFile = NULL; 207 } 208} 209 210bool ShadowDirText::RemoveRecord( LPCTSTR szPath, bool bDir) 211{ 212 DWORD dwPosition = Find(szPath, bDir); 213 if(0 == dwPosition) 214 { 215 return true; 216 } 217 CString strTemp; 218 strTemp.Format(m_sstrFormatting, m_strId, szPath, (bDir? 1: 0)); 219 int nLength = strTemp.GetLength() * sizeof(TCHAR); 220 char *pBegin = (char*)m_pvMapView; 221 memmove_s(pBegin + dwPosition, m_dwFileSize - dwPosition,pBegin + dwPosition + nLength, 222 m_dwFileSize - dwPosition); 223 m_dwFileSize -= nLength; 224 FillEnd(); 225 return true; 226} 227 228bool ShadowDirText::AddRecord(LPCTSTR szPath, bool bDir) 229{ 230 DWORD dwPosition = Find(szPath, bDir); 231 if(0 != dwPosition) 232 { 233 return true; 234 } 235 CString strTemp; 236 strTemp.Format(m_sstrFormatting, m_strId, szPath, (bDir? 1: 0)); 237 int nLength = strTemp.GetLength() * sizeof(TCHAR); 238 if(m_dwFileSize + nLength + 2 > m_dwOriginalSize + m_snExtened) 239 { 240 Close(); 241 Open(m_strDir); 242 return AddRecord(szPath, bDir); 243 } 244 char *pBegin = (char*)m_pvMapView; 245 TCHAR *pWriten=(TCHAR*)(pBegin + m_dwFileSize); 246 memcpy_s(pWriten, nLength + sizeof(TCHAR), strTemp, nLength); 247 m_dwFileSize += nLength; 248 FillEnd(); 249 return true; 250} 251 252bool ShadowDirText::ExistRecord( LPCTSTR szPath, bool bDir) 253{ 254 return (Find(szPath, bDir)? true: false); 255} 256 257bool ShadowDirText::RemoveAll() 258{ 259 DWORD dwSearchPosition = 0; 260 DWORD dwBegin = 0; 261 DWORD dwEnd = 0; 262 bool bRet = true; 263 while (FindLineById(dwSearchPosition, dwBegin, dwEnd)) 264 { 265 if(dwEnd > dwBegin) 266 { 267 char *pBegin = (char*)m_pvMapView; 268 memmove_s(pBegin + dwBegin, m_dwFileSize - dwBegin,pBegin + dwEnd, 269 m_dwFileSize - dwBegin); 270 m_dwFileSize -= (dwEnd - dwBegin); 271 FillEnd(); 272 } 273 } 274 if (dwBegin >= m_dwFileSize || dwEnd > m_dwFileSize) 275 { 276 bRet = false; 277 } 278 return bRet; 279} 280/**///////////////////////////////////////////////////////////////////////////281// private methods 282// 283// 284/**///////////////////////////////////////////////////////////////////////////285DWORD ShadowDirText::Find( LPCTSTR szPath, bool bDir ) 286{ 287 ATLASSERT(m_pvMapView != NULL && _T("Map file base address is invalid.")); 288 if(NULL == m_pvMapView) 289 { 290 return 0; 291 } 292 CString strTemp; 293 strTemp.Format(m_sstrFormatting, m_strId, szPath, (bDir? 1: 0)); 294 TCHAR *pBegin =(TCHAR*)m_pvMapView; 295 TCHAR *p = _tcsstr(pBegin, strTemp); 296 if(!p) 297 { 298 return 0; 299 } 300 DWORD dwOffset = (DWORD)(p - pBegin) * sizeof(TCHAR); 301 if(dwOffset >= m_dwFileSize) 302 { 303 return 0; 304 } 305 return dwOffset; 306} 307 308void ShadowDirText::FillEnd() 309{ 310 char *pBegin = (char*)m_pvMapView; 311 TCHAR *pWriten=(TCHAR*)(pBegin + m_dwFileSize); 312 *pWriten = 0; 313} 314 315bool ShadowDirText::FindLineById( DWORD dwSearchPosition, DWORD &dwBegin, DWORD &dwEnd ) 316{ 317 ATLASSERT(dwSearchPosition % sizeof(TCHAR) == 0 && 318 _T("argument \"dwSearchPosition\" of ShadowDirText::FindLineById must be even")); 319 dwBegin = 0; 320 dwEnd = 0; 321 if(dwSearchPosition % sizeof(TCHAR)) 322 { 323 return true; 324 } 325 CString str; 326 str.Format(_T("id=\"%s\""), m_strId); 327 TCHAR *pBegin = (TCHAR *)((char*)m_pvMapView + dwSearchPosition); 328 TCHAR *p1 = _tcsstr(pBegin, str); 329 if(!p1) 330 { 331 return false; 332 } 333 dwBegin = dwSearchPosition + (p1 - pBegin) * sizeof(TCHAR); 334 TCHAR *p2 = _tcsstr(p1, m_sszLineEnd); 335 if(!p2) 336 { 337 dwEnd = m_dwFileSize; 338 } 339 else 340 { 341 dwEnd = dwBegin + (p2 - p1) * sizeof(TCHAR) + sizeof(m_sszLineEnd); 342 } 343 if(dwBegin >= m_dwFileSize || dwEnd > m_dwFileSize) 344 { 345 ATLASSERT(_T("the end of memory was bad.")); 346 return false; 347 } 348 return true; 349}
由于篇幅太长,我就不贴上XML文件操作的封装类。在上面的代码中str.Format(_T("id=\"%s\""), m_strId);这语句应提取出来以提高可维护性。这里贴出的是评价方案时的类,并不是实际项目中的类。但总体上是一样的,差别是完成了id, name,isdir等的常量及相关格式的标准化。
|