在32位機(jī)上(64位也是一樣的,但是空間大很多),一個進(jìn)程可以分配到4GB的虛擬內(nèi)存,當(dāng)然,其中2G給了內(nèi)核,剩下2GB有一些分給了代碼段、數(shù)據(jù)段,最后剩下的就是給我們程序員用的了,這樣看來,一個應(yīng)用程序若硬生生的讀取2GB左右的數(shù)據(jù)是一個極限了。
不過事實并非如此,只要利用虛擬內(nèi)存技術(shù)有時還是可以讀取的。
先簡單說說windows虛擬內(nèi)存的思路:
所謂虛擬內(nèi)存,就是事實上可能還沒有得到實際內(nèi)存的內(nèi)存,如果CPU要訪問一個虛擬內(nèi)存,哪么操作系統(tǒng)得首先判斷這個虛擬內(nèi)存有沒有獲得實際內(nèi)存。如果有,那大可以大大方方的讀取,甚至寫數(shù)據(jù)。
但若如果沒有,操作系統(tǒng)就必須在實際內(nèi)存上找到一塊地方,建立起與要讀取虛擬內(nèi)存的映射關(guān)系,這樣就可以讀取了。當(dāng)然若是找不到這么多實際內(nèi)存,那么可以一些暫時還沒用到的實際內(nèi)存放到更低一級的硬盤臨時空間上,騰出空間來,再建立與虛擬內(nèi)存的映射關(guān)系。
當(dāng)然了,比如要讀取2GB的數(shù)據(jù),在1G內(nèi)存上是無論如何也沒法找到足夠空間建立映射關(guān)系的。我們當(dāng)然不可能一次性讀取2GB數(shù)據(jù),所以引入“頁”這個概念,即每次虛擬內(nèi)存射到實際內(nèi)存上的時候,都是按頁大小映射的,這個頁大小,與CPU有關(guān),在X86上一般是4KB。
說到這,讀取大型數(shù)據(jù)的思路就出來了,我們可以先分配虛擬內(nèi)存,等到我們讀或者寫當(dāng)中的一些數(shù)據(jù)的時候,再分配實際內(nèi)存,由于這些都是在內(nèi)存這一級操作,效率遠(yuǎn)比讀寫數(shù)據(jù)的時候再從硬盤取出來的高。
windows API讓我們輕松做到這一點。
VirtualAlloc,可以申請到一個虛擬內(nèi)存,或者實際映射到實際內(nèi)存的內(nèi)存。我們可以先申請?zhí)摂M內(nèi)存,等到真的要讀取某處數(shù)據(jù)的時候,再申請實際內(nèi)存(建立映射關(guān)系)。
詳細(xì)可以看MSDN,下面給出一個示例代碼:
#include
#include
#include
struct Sheet //我們要讀取的數(shù)據(jù)結(jié)構(gòu)
{
int nSize;
BOOL bVisible;
char big[1024 * 15];
std::string strText;
};
int main()
{
SYSTEM_INFO si;
GetSystemInfo(&si);
DWORD dwPageSize = si.dwPageSize; //獲得CPU讀取的頁大小
//把頁的單位轉(zhuǎn)為字節(jié)(頁的單位本來是KB,而我們的數(shù)據(jù)結(jié)構(gòu)是按字節(jié)算的)
DWORD dwPageAsByte = dwPageSize * 1024;
//以下dwOccupy獲得一個數(shù)據(jù)結(jié)構(gòu)所占的空間,由于是按頁分配實際內(nèi)存,這樣所占空間并定是頁的整數(shù)倍
DWORD dwMod = sizeof(Sheet) % dwPageAsByte;
DWORD dwOccupy = 0;
if (dwMod != 0)
{
dwOccupy = (sizeof(Sheet) / dwPageAsByte) * dwPageAsByte + dwPageAsByte;
}
else
{
dwOccupy = sizeof(Sheet);
}
//申請?zhí)摂M內(nèi)存,MEM_RESERVE表示不會和實際內(nèi)存建立映射關(guān)系
LPVOID lpBaseAddr = VirtualAlloc(NULL, dwOccupy * 50, MEM_RESERVE, PAGE_READWRITE);
if (lpBaseAddr == NULL)
{
return 1;
}
__try
{
DWORD dwIndex = 0;
//以下不斷的輸入要讀取哪個數(shù)據(jù),若此數(shù)據(jù)還有沒有建立實際內(nèi)存映射關(guān)系,那么建立
while(true)
{
std::cout << "input the sheet number to write: ";
std::cin >> dwIndex;
//根據(jù)輸入的索引,計算出所要讀取的數(shù)據(jù)結(jié)構(gòu)所在地址,注意同樣是按頁分配的
LPVOID lpSheetAddr = (LPVOID)((DWORD)lpBaseAddr + (dwIndex - 1) * dwOccupy);
MEMORY_BASIC_INFORMATION mbi;
memset(&mbi, 0, sizeof(mbi));
//查詢內(nèi)存狀態(tài)
if (VirtualQuery(lpSheetAddr, &mbi, sizeof(mbi)) == sizeof(mbi))
{
if (mbi.State == MEM_RESERVE)//屬于reserve狀態(tài)
{
//建立與實際內(nèi)存的映射關(guān)系
LPVOID lpAlloc = VirtualAlloc(lpSheetAddr, dwOccupy, MEM_COMMIT, PAGE_READWRITE);
if (lpAlloc == NULL)
{
std::cout << "commit fails in reserve state\n";
}
else
{
std::cout << "commit successes in reserve state\n";
}
}
}
//建立成功,考試@大提示讀/寫數(shù)據(jù)
Sheet * pSheet = (Sheet*)lpSheetAddr;
pSheet->nSize = 9;
pSheet->strText = "hello world";
pSheet->bVisible = TRUE;
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
}
//釋放內(nèi)存
VirtualFree(lpBaseAddr, 0, MEM_RELEASE);
}
當(dāng)然,這其中用到了一個影響效率的VirtualQuery函數(shù),其實可以用異常處理手段提高效率的,留待以后再說。
還可以添加刪除代碼,一些數(shù)據(jù)不再用到的時候,可以把虛擬內(nèi)存狀態(tài)重新置為MEM_RESERVE,這樣可以節(jié)省實際內(nèi)存。
不過事實并非如此,只要利用虛擬內(nèi)存技術(shù)有時還是可以讀取的。
先簡單說說windows虛擬內(nèi)存的思路:
所謂虛擬內(nèi)存,就是事實上可能還沒有得到實際內(nèi)存的內(nèi)存,如果CPU要訪問一個虛擬內(nèi)存,哪么操作系統(tǒng)得首先判斷這個虛擬內(nèi)存有沒有獲得實際內(nèi)存。如果有,那大可以大大方方的讀取,甚至寫數(shù)據(jù)。
但若如果沒有,操作系統(tǒng)就必須在實際內(nèi)存上找到一塊地方,建立起與要讀取虛擬內(nèi)存的映射關(guān)系,這樣就可以讀取了。當(dāng)然若是找不到這么多實際內(nèi)存,那么可以一些暫時還沒用到的實際內(nèi)存放到更低一級的硬盤臨時空間上,騰出空間來,再建立與虛擬內(nèi)存的映射關(guān)系。
當(dāng)然了,比如要讀取2GB的數(shù)據(jù),在1G內(nèi)存上是無論如何也沒法找到足夠空間建立映射關(guān)系的。我們當(dāng)然不可能一次性讀取2GB數(shù)據(jù),所以引入“頁”這個概念,即每次虛擬內(nèi)存射到實際內(nèi)存上的時候,都是按頁大小映射的,這個頁大小,與CPU有關(guān),在X86上一般是4KB。
說到這,讀取大型數(shù)據(jù)的思路就出來了,我們可以先分配虛擬內(nèi)存,等到我們讀或者寫當(dāng)中的一些數(shù)據(jù)的時候,再分配實際內(nèi)存,由于這些都是在內(nèi)存這一級操作,效率遠(yuǎn)比讀寫數(shù)據(jù)的時候再從硬盤取出來的高。
windows API讓我們輕松做到這一點。
VirtualAlloc,可以申請到一個虛擬內(nèi)存,或者實際映射到實際內(nèi)存的內(nèi)存。我們可以先申請?zhí)摂M內(nèi)存,等到真的要讀取某處數(shù)據(jù)的時候,再申請實際內(nèi)存(建立映射關(guān)系)。
詳細(xì)可以看MSDN,下面給出一個示例代碼:
#include
#include
#include
struct Sheet //我們要讀取的數(shù)據(jù)結(jié)構(gòu)
{
int nSize;
BOOL bVisible;
char big[1024 * 15];
std::string strText;
};
int main()
{
SYSTEM_INFO si;
GetSystemInfo(&si);
DWORD dwPageSize = si.dwPageSize; //獲得CPU讀取的頁大小
//把頁的單位轉(zhuǎn)為字節(jié)(頁的單位本來是KB,而我們的數(shù)據(jù)結(jié)構(gòu)是按字節(jié)算的)
DWORD dwPageAsByte = dwPageSize * 1024;
//以下dwOccupy獲得一個數(shù)據(jù)結(jié)構(gòu)所占的空間,由于是按頁分配實際內(nèi)存,這樣所占空間并定是頁的整數(shù)倍
DWORD dwMod = sizeof(Sheet) % dwPageAsByte;
DWORD dwOccupy = 0;
if (dwMod != 0)
{
dwOccupy = (sizeof(Sheet) / dwPageAsByte) * dwPageAsByte + dwPageAsByte;
}
else
{
dwOccupy = sizeof(Sheet);
}
//申請?zhí)摂M內(nèi)存,MEM_RESERVE表示不會和實際內(nèi)存建立映射關(guān)系
LPVOID lpBaseAddr = VirtualAlloc(NULL, dwOccupy * 50, MEM_RESERVE, PAGE_READWRITE);
if (lpBaseAddr == NULL)
{
return 1;
}
__try
{
DWORD dwIndex = 0;
//以下不斷的輸入要讀取哪個數(shù)據(jù),若此數(shù)據(jù)還有沒有建立實際內(nèi)存映射關(guān)系,那么建立
while(true)
{
std::cout << "input the sheet number to write: ";
std::cin >> dwIndex;
//根據(jù)輸入的索引,計算出所要讀取的數(shù)據(jù)結(jié)構(gòu)所在地址,注意同樣是按頁分配的
LPVOID lpSheetAddr = (LPVOID)((DWORD)lpBaseAddr + (dwIndex - 1) * dwOccupy);
MEMORY_BASIC_INFORMATION mbi;
memset(&mbi, 0, sizeof(mbi));
//查詢內(nèi)存狀態(tài)
if (VirtualQuery(lpSheetAddr, &mbi, sizeof(mbi)) == sizeof(mbi))
{
if (mbi.State == MEM_RESERVE)//屬于reserve狀態(tài)
{
//建立與實際內(nèi)存的映射關(guān)系
LPVOID lpAlloc = VirtualAlloc(lpSheetAddr, dwOccupy, MEM_COMMIT, PAGE_READWRITE);
if (lpAlloc == NULL)
{
std::cout << "commit fails in reserve state\n";
}
else
{
std::cout << "commit successes in reserve state\n";
}
}
}
//建立成功,考試@大提示讀/寫數(shù)據(jù)
Sheet * pSheet = (Sheet*)lpSheetAddr;
pSheet->nSize = 9;
pSheet->strText = "hello world";
pSheet->bVisible = TRUE;
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
}
//釋放內(nèi)存
VirtualFree(lpBaseAddr, 0, MEM_RELEASE);
}
當(dāng)然,這其中用到了一個影響效率的VirtualQuery函數(shù),其實可以用異常處理手段提高效率的,留待以后再說。
還可以添加刪除代碼,一些數(shù)據(jù)不再用到的時候,可以把虛擬內(nèi)存狀態(tài)重新置為MEM_RESERVE,這樣可以節(jié)省實際內(nèi)存。