65.9K
CodeProject 正在变化。 阅读更多。
Home

计算文件夹集合大小的方法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.09/5 (6投票s)

2010年2月25日

CPOL

2分钟阅读

viewsIcon

26220

downloadIcon

526

使用 VC++ 计算文件夹集合大小的方法。

引言

我从 CodeProject 得到了很多帮助,现在我想发布一个小程序来帮助他人,同时也能提升自己。希望对大家有所帮助。

有时,我们希望向用户动态显示他们选择的文件夹的大小,就像点击文件夹属性时,文件夹大小会不断增加一样。我们该如何获取文件夹/文件/驱动器的大小呢?当然,我们可以在 CodeProject 上找到一些类似的的文章,例如 Hans Dietrich 的项目。他的应用程序可以获取任何文件夹的容量。但不幸的是,它一次只能获取一个文件夹的大小。在我的演示项目中,我实现了一种方法来获取多个文件夹的大小,而不仅仅是一个,并在新线程中运行。这样就不会影响主线程的职责。

顺便说一下,我的项目提供的是获取文件夹大小的向量,甚至可以获取驱动器的大小。但是你知道,这个过程是耗时的。如果你只想获取驱动器的容量,我建议你使用 Windows API 函数,例如 GetDiskFreeSpace

在我的项目中,我借用了演示对话框 CFolderTreeCtrl 来自 Adrien Pinet。他的文件夹树控件非常有用。为了演示我的项目,我可以在树控件复选框中选择或取消选择任何文件夹。

实现细节

CFolder 类是一个非常简单的文件夹类,用于保存计算文件夹大小所需的必要信息,如下所示

//////////////////////////////////////////////////////////////////////////
// CFolder class
// A very basic folder class with only a few 
//  properties to simulating a real folder
//////////////////////////////////////////////////////////////////////////
class CFolder
{
    // make all class members public for direct access, for it's a basic and small class
  public:
      CString m_szPath;             	// full path of the folder 
      DWORD64 m_dw64Size;           	//  DWORD64 value of the file size, in bytes
      bool m_bSelected;              	// To mark if the folder is selected or not
      bool m_bCalculated;            	//  To mark if the folder size is calculated or not

  public: 
      // Constructor function, all other members can access directly.
      CFolder(CString szPath=_T(""), DWORD64 dwSize=0, bool bSel=true,bool bCal=false):\
          m_szPath(szPath), m_dw64Size(dwSize), m_bSelected(bSel), m_bCalculated(bCal)
      {
         // constructor function
      }

      // overload this function to determine if two folder are the same folder
      // if they path are the same, they are the same folder
      bool operator==(const CFolder& folder) const
      {
          return m_szPath==folder.m_szPath;
      }
      bool operator==(const CString& str) const
      {
          return m_szPath==str;
      }
};  

CFolderSize 类是实现文件夹大小计算的关键

//////////////////////////////////////////////////////////////////////////
// CFolderSize class
// a class integrated the functions to calculate
// the folders size.
//////////////////////////////////////////////////////////////////////////
class CFolderSize
{
    public:
//////////////////////////////////////////////////////////////////////////
//      constructor and destructor
        CFolderSize(HANDLE handle=NULL, bool bCalCompleted=false, 
			bool bToExit=false, DWORD64 dwSize=0) ;
        ~CFolderSize();

//////////////////////////////////////////////////////////////////////////
// Basic get functions of the members
       bool IsCompleted() const
       {
           return m_bCalCompleted;
       }

       bool IsCalledToExit() const
       {
           return m_bToExit;
       }

       DWORD64 GetTotalSize() const
       {
           return m_dw64TotalSize;
       }

       //caution, it's pointer
       vector<CFolder>* GetAllFolder()
       {
           return &m_vFolder;
       }

//////////////////////////////////////////////////////////////////////////
//folders operations

       // set a collection of folders to this class
       // and then start calculation automatically
       void SetPath(const vector<CString>& pathV);

//////////////////////////////////////////////////////////////////////////
// Calculation and its thread operations

       // Thread functions
       static DWORD64 CalcThreadFunc(LPVOID pParam); 

       //function to calculate a folder's capacity, return value in bytes
       DWORD64 CalcFolderSize(CString szPath);

//////////////////////////////////////////////////////////////////////////
//other operations, for convenience use. 

       // Get a proper unit for the folder capacity, return a string
       //  contain capacity and its unit.  for user's convenience
       //  for example,1024 bytes should be 1K
       static CString TrimSizeToUnit(DWORD64 capacity);

    private:
        vector<CFolder> m_vFolder; 	// keep all the folders
        DWORD64 m_dw64TotalSize;  	// value to keep all the folders' size, 
				// the same in bytes
        HANDLE m_hCalcThread;        	// the calculation's thread' handle
        bool m_bToExit;           	// set true to force the calculated thread to exit
        bool m_bCalCompleted;   	//  if the calculation is completed?
}; 

我的应用程序实现计算的关键是以下三个函数。

  • DWORD64 CFolderSize::CalcFolderSize(CString szPath)

    第一个可以获取一个文件夹(及其子文件夹)的大小。

    ///////////////////////////////////////////////////////////////
    //
    // CFolderSize::CalcFolderSize
    //
    // Purpose:     recursive calculate the folder's size 
    //
    // Parameters:  CString szPath   - fully qualified path to a folder
    //             
    //
    // Returns:     DWORD64 - Returns 64-bit folder's size.
    //
    DWORD64 CFolderSize::CalcFolderSize(CString szPath)
    {
    	if(szPath.Right(1) != _T("\\"))
    		szPath += _T("\\");
    	szPath += _T("*");
    
    	WIN32_FIND_DATA fileinfo;
    	HANDLE hFind = FindFirstFile(szPath, &fileinfo);
    	if(hFind == INVALID_HANDLE_VALUE)
    		return 0;
    
        //recursive calculate the folder's size
    	do
    	{
    		if(m_bToExit)        //force to exit?
    		{
    			return 0;           //exit the thread
    		}
    
    		// if the folders are the current or the upper level folder
    		if( (0 == _tcscmp(fileinfo.cFileName, _T("."))) || 
    		(0 == _tcscmp(fileinfo.cFileName, _T(".."))) || 
    		fileinfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT )
    			continue;   // thanks to lusores' advice about 
    				// FILE_ATTRIBUTE_REPARSE_POINT
    		else if(fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)    
    		{    // directory, not a file, recursive calculate the folder size
    			CString szSubFolder = szPath;
    			szSubFolder.Delete(szSubFolder.GetLength()-1, 1);//delete  
    							 // the asterisk
    			szSubFolder += fileinfo.cFileName;
    			szSubFolder += _T("\\");
    			CalcFolderSize(szSubFolder);
    		}
    		else    	// It's a file, add the file size
    		{         // here add all the size to the total size, 
    			// that will the risk to calculate the 
    			// folder size repeatedly
    			// to avoid this situation, when we perform a calculation,
    			// we must start a new calculation and set
    			// total size to 0, the folder which is set 
    			// completed cache the size will 
    			// accelerate the calculation
    			m_dw64TotalSize += (((DWORD64)fileinfo.nFileSizeHigh)
    				<<(sizeof(DWORD)*8)) + fileinfo.nFileSizeLow; 
    		}
    	}while(FindNextFile(hFind, &fileinfo));
    
    	FindClose(hFind);
    
    	return m_dw64TotalSize;
    }
  •  DWORD64 CFolderSize::CalcThreadFunc(LPVOID pParam)   

    是线程回调函数。在该函数中,它将调用 DWORD64 CFolderSize::CalcFolderSize(CString szPath) 来获取文件夹的容量。

    ///////////////////////////////////////////////////////////////////
    //
    // CFolderSize::CalcThreadFunc
    //
    // Purpose:     provide this thread callback function for the thread to calculate 
    // 		folder's size.
    //
    // Parameters:  LPVOID pParam   - provide a CFolderSize object, 
    // and this object keep almost all the necessary information.
    //
    // Returns:    DWORD64 -the total folder collection's size.
    //
    DWORD64 CFolderSize::CalcThreadFunc(LPVOID pParam)
    {
    	CFolderSize* pClass=(CFolderSize*)pParam;
    	if(!pParam)
    		return 0;
    
    	vector<CFolder>* pVector=pClass->GetAllFolder();
    
    	// Every Calculation thread start, reset the total size
    	pClass->m_dw64TotalSize = 0;
        pClass->m_bCalCompleted=FALSE;
    	for(vector<CFolder>::iterator it=pClass->
    		m_vFolder.begin();it!=pClass->m_vFolder.end();++it)
    	{
    		if(pClass->m_bToExit)  // force to exit?
    		{
    			return 0;  
    		}
    		else if((it->m_bCalculated ==false) && it->m_bSelected)  
    		{		
    			DWORD64 dwOriginalSize = pClass->GetTotalSize();
    			CString str=it->m_szPath;
    
    			// In fact, if the path is a full drive, we can 
    			//call GetDiskFreeSpaceEx, it's must more fast.
    			pClass->CalcFolderSize(str);
    			DWORD64 dwNewSize = pClass->GetTotalSize();
    			it->m_bCalculated=true;
    			it->m_dw64Size=dwNewSize-dwOriginalSize; //cache 
    							// folder size
    		}
    		else if(it->m_bCalculated && it->m_bSelected) // use the cached 
    							// folder size
    			pClass->m_dw64TotalSize+=it->m_dw64Size;
    	}
        pClass->m_bCalCompleted=TRUE;
    	return pClass->m_dw64TotalSize;   
    }
  •  void CFolderSize::SetPath(const vector<CString>& pathV) 

    此函数是用户的主要接口。只需将路径向量传递给成员函数,该函数就会启动一个新线程来为您工作。

    /////////////////////////////////////////////////////////////////////
    //
    // CFolderSize::SetPath
    //
    // Purpose:  set a collection of folders' paths for the thread to start calculation 
    //
    // Parameters:  const vector<CString>& pathV   - 
    // a vector of fully qualified folders' paths
    //             
    //
    // Returns:    
    //
    void CFolderSize::SetPath(const vector<CString>& pathV)
    {
    	vector<CString> vszPaths = pathV;
    
        // New calculation will be started, force the old thread (if any) to exit
    	m_bToExit=true;                                 
    	DWORD dwExitCode = 0;
    	GetExitCodeThread(m_hCalcThread, &dwExitCode);
    	if(dwExitCode == STILL_ACTIVE)
    		WaitForSingleObject(m_hCalcThread, INFINITE);
    	if(m_hCalcThread) 
    		CloseHandle(m_hCalcThread);
    
    	std::vector<CFolder>::iterator iter = m_vFolder.begin();
    	for(; iter != m_vFolder.end();)
    	{
    		// if there are any duplicated paths, we should remove them
    		// find if any current class paths in the new set path vector
    		// In this situation, all the path must be in the same format, 
    		// for example, all with or without backslash.
    		std::vector<CString>::iterator iter_find = 
    		std::find(vszPaths.begin(), vszPaths.end(), iter->m_szPath);
    		if(vszPaths.end() == iter_find)
    		{                                                       
    			// if not find, remove the current class path
    			iter = m_vFolder.erase(iter);
    			if(iter == m_vFolder.end())
    				break;
    		}
    		else
    		{               
    		    // If we find it, that is not need to add one more time, 
    		    // just remove it in the new vector
    		    vszPaths.erase(iter_find);
    		    iter ++;
    		}
    	}
    
    	// add new path that is not in the current class paths
    	std::vector<CString>::iterator iter_path = vszPaths.begin();
    	for(; iter_path != vszPaths.end(); iter_path ++)
    	{
    		m_vFolder.push_back(CFolder(*iter_path, 0, true, false));
    	}
    
    	DWORD dwThreadID = 0;
    	m_bToExit = false;
    	//m_dw64TotalSize = 0;   //because inside the thread function call one.
    
    	// start the thread to perform calculation.
    	m_hCalcThread = CreateThread(0, 0, 
    	(LPTHREAD_START_ROUTINE)CalcThreadFunc, (LPVOID)this, 0, &dwThreadID);    
    }

如何使用

事实上,使用这两个类非常简单。您可以按照以下三个步骤操作

  1. 您必须将源文件添加到您的项目中;然后包含头文件。
  2. 然后定义一个 CFolderSize 成员变量,并将其传递一个文件夹路径的向量。就像这样
      CFolderSize folder;
      std::vector<CString> FolderVec;
      FolderVec.push_back(_T("C:\\Windows"));
      // ... other paths
      folder.SetPath(FolderVec); 
  3. 当您想显示总大小时,只需调用
  4. folder.GetTotalSize();  

gradual.png

顺便说一下,文件夹容量的单位是字节。例如,如果总大小是 1000000 字节。向用户显示如此大的数字和如此小的单位并不友好。幸运的是,我编写了一个函数来完成这项繁琐的工作。只需调用...

static CString TrimSizeToUnit(DWORD64 capacity);

...函数,它将返回一个合适的数字及其单位。

历史

  • 2010-02-22 首次发布
  • 2010-02-26 考虑了 FILE_ATTRIBUTE_REPARSE_POINT ,感谢 lusores 的建议
© . All rights reserved.