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