1. 程式人生 > >【java】記憶體對映檔案、虛擬記憶體、RandomAccessFile類

【java】記憶體對映檔案、虛擬記憶體、RandomAccessFile類

Windows提供了3種進行記憶體管理的方法: 

• 虛擬記憶體,最適合用來管理大型物件結構陣列。 

• 記憶體對映檔案,最適合用來管理大型資料流(通常來自檔案)以及在單個計算機上執行的多個程序之間共享資料。 

• 記憶體堆疊,最適合用來管理大量的小物件


1.什麼是記憶體對映檔案,有啥作用

http://bbs.csdn.net/topics/340238673

通俗點,就是ReadFile和WriteFile這樣的I/O系統函式, 在檔案裡來回地讀、寫、移動檔案指標效率低 速度慢;CreateFileMapping函式允許應用程式把檔案對映到一個程序,這樣檔案內的資料就可以用記憶體讀/寫指令來訪問,提高效率。

再通俗點,就是比如 要讀取一個檔案裡的東西 這時候你就得去硬碟讀,但是對映到記憶體後 就可以直接對這塊記憶體操作了;寫操作也一個意思。。。。。就是把要在硬碟上搞的東西 弄到記憶體搞 搞起來方便

JDK1.4版本引入了java.nio包,對檔案流進行讀寫操作,提供無阻塞模式,同時也提供了一種高效率的檔案讀寫模式,記憶體對映檔案,把檔案某個區域塊對映到記憶體,進行高效率的讀寫,主要用到下面類
java.nio.MappedByteBuffer;
java.nio.channels.FileChannel

記憶體對映檔案(memory-mappedfile)能讓你建立和修改那些大到無法讀入記憶體的檔案。有了記憶體對映檔案,你就可以認為檔案已經全部讀進了記憶體,然後把它當成一個非常大的陣列來訪問了。將檔案的一段區域對映到記憶體中,比傳統的檔案處理速度要快很多

2.記憶體對映原理

http://blog.csdn.net/mg0832058/article/details/5890688

首先,“對映”這個詞,就和數學課上說的“一一對映”是一個意思,就是建立一種一一對應關係,在這裡主要是指硬碟上檔案的位置與程序邏輯地址空間中一塊大小相同的區域之間的一一對應,如圖1中過程1所示。這種對應關係純屬是邏輯上的概念,物理上是不存在的,原因是程序的邏輯地址空間本身就是不存在的。在記憶體對映的過程中,並沒有實際的資料拷貝,檔案沒有被載入記憶體,只是邏輯上被放入了記憶體,具體到程式碼,就是建立並初始化了相關的資料結構(struct address_space),這個過程有系統呼叫mmap()實現,所以建立記憶體對映的效率很高。

既然建立記憶體對映沒有進行實際的資料拷貝,那麼程序又怎麼能最終直接通過記憶體操作訪問到硬碟上的檔案呢?那就要看記憶體對映之後的幾個相關的過程了。

mmap()會返回一個指標ptr,它指向程序邏輯地址空間中的一個地址,這樣以後,程序無需再呼叫read或write對檔案進行讀寫,而只需要通過ptr就能夠操作檔案。但是ptr所指向的是一個邏輯地址,要操作其中的資料,必須通過MMU將邏輯地址轉換成實體地址,如圖1中過程2所示。這個過程與記憶體對映無關。

前面講過,建立記憶體對映並沒有實際拷貝資料,這時,MMU在地址對映表中是無法找到與ptr相對應的實體地址的,也就是MMU失敗,將產生一個缺頁中斷,缺頁中斷的中斷響應函式會在swap中尋找相對應的頁面,如果找不到(也就是該檔案從來沒有被讀入記憶體的情況),則會通過mmap()建立的對映關係,從硬碟上將檔案讀取到實體記憶體中,如圖1中過程3所示。這個過程與記憶體對映無關。

如果在拷貝資料時,發現物理記憶體不夠用,則會通過虛擬記憶體機制(swap)將暫時不用的物理頁面交換到硬碟上,如圖1中過程4所示。這個過程也與記憶體對映無關。

效率比較:

從程式碼層面上看,從硬碟上將檔案讀入記憶體,都要經過檔案系統進行資料拷貝,並且資料拷貝操作是由檔案系統和硬體驅動實現的,理論上來說,拷貝資料的效率是一樣的。但是通過記憶體對映的方法訪問硬碟上的檔案,效率要比read和write系統呼叫高,這是為什麼呢?原因是read()是系統呼叫,其中進行了資料拷貝,它首先將檔案內容從硬碟拷貝到核心空間的一個緩衝區,如圖2中過程1,然後再將這些資料拷貝到使用者空間,如圖2中過程2,在這個過程中,實際上完成了兩次資料拷貝;而mmap()也是系統呼叫,如前所述,mmap()中沒有進行資料拷貝,真正的資料拷貝是在缺頁中斷處理時進行的,由於mmap()將檔案直接對映到使用者空間,所以中斷處理函式根據這個對映關係,直接將檔案從硬碟拷貝到使用者空間,只進行了一次資料拷貝。因此,記憶體對映的效率要比read/write效率高。

例項:未進行實地驗證,結果未知

#include<unistd.h>

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<sys/time.h>

#include<fcntl.h>

#include<sys/mman.h>

 

#define MAX 10000

 

int main()

{

int i=0;

int count=0, fd=0;

struct timeval tv1, tv2;

int *array = (int *)malloc( sizeof(int)*MAX );

 

/*read*/

 

gettimeofday( &tv1, NULL );

fd = open( "mmap_test", O_RDWR );

if( sizeof(int)*MAX != read( fd, (void *)array, sizeof(int)*MAX ) )

{

printf( "Reading data failed.../n" );

return -1;

}

for( i=0; i<MAX; ++i )

 

++array[ i ];

if( sizeof(int)*MAX != write( fd, (void *)array, sizeof(int)*MAX ) )

{

printf( "Writing data failed.../n" );

return -1;

}

free( array );

close( fd );

gettimeofday( &tv2, NULL );

printf( "Time of read/write: %dms/n", tv2.tv_usec-tv1.tv_usec );

 

/*mmap*/

 

gettimeofday( &tv1, NULL );

fd = open( "mmap_test", O_RDWR );

array = mmap( NULL, sizeof(int)*MAX, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 );

for( i=0; i<MAX; ++i )

 

++array[ i ];

munmap( array, sizeof(int)*MAX );

msync( array, sizeof(int)*MAX, MS_SYNC );

free( array );

close( fd );

gettimeofday( &tv2, NULL );

printf( "Time of mmap: %dms/n", tv2.tv_usec-tv1.tv_usec );

 

return 0;

} 

記憶體對映檔案

記憶體對映檔案可以用於3個不同的目的

• 系統使用記憶體對映檔案,以便載入和執行. exe和DLL檔案。這可以大大節省頁檔案空間和應用程式啟動執行所需的時間。

• 可以使用記憶體對映檔案來訪問磁碟上的資料檔案。這使你可以不必對檔案執行I/O操作,並且可以不必對檔案內容進行快取。

• 可以使用記憶體對映檔案,使同一臺計算機上執行的多個程序能夠相互之間共享資料。Windows確實提供了其他一些方法,以便在程序之間進行資料通訊,但是這些方法都是使用記憶體對映檔案來實現的,這使得記憶體對映檔案成為單個計算機上的多個程序互相進行通訊的最有效的方法。

使用記憶體對映資料檔案

若要使用記憶體對映檔案,必須執行下列操作步驟:

1) 建立或開啟一個檔案核心物件,該物件用於標識磁碟上你想用作記憶體對映檔案的檔案。

2) 建立一個檔案對映核心物件,告訴系統該檔案的大小和你打算如何訪問該檔案。

3) 讓系統將檔案對映物件的全部或一部分對映到你的程序地址空間中。

當完成對記憶體對映檔案的使用時,必須執行下面這些步驟將它清除:

1) 告訴系統從你的程序的地址空間中撤消檔案對映核心物件的映像。

2) 關閉檔案對映核心物件。

3) 關閉檔案核心物件。

下面將詳細介紹這些操作步驟。

步驟1:建立或開啟檔案核心物件

HANDLE CreateFile(

   PCSTR pszFileName,

   DWORD dwDesiredAccess,

   DWORD dwShareMode,

   PSECURITY_ATTRIBUTES psa,

   DWORD dwCreationDisposition,

   DWORD dwFlagsAndAttributes,

   HANDLE hTemplateFile);

dwDesiredAccess的值

值 

含義 

0

不能讀取或寫入檔案的內容。當只想獲得檔案的屬性時,請設定0

GENERIC_READ

可以從檔案中讀取資料 

GENERIC_WRITE

可以將資料寫入檔案 

GENERIC_READ |GENERIC_WRITE

可以從檔案中讀取資料,也可以將資料寫入檔案 

dwShareMode 的值

值 

含義

0

開啟檔案的任何嘗試均將失敗

FILE_SHARE_READ

使用GENERIC_WRITE開啟檔案的其他嘗試將會失敗

FILE_SHARE_WRITE

使用GENERIC_READ開啟檔案的其他嘗試將會失敗

FILE_SHARE_READ FILE_SHARE_WRITE|

開啟檔案的其他嘗試將會取得成功

步驟2:建立一個檔案對映核心物件

呼叫CreateFileMapping函式告訴系統,檔案對映物件需要多少物理儲存器。

HANDLE CreateFileMapping(

   HANDLE hFile,

   PSECURITY_ATTRIBUTES psa,

   DWORD fdwProtect,

   DWORD dwMaximumSizeHigh,

   DWORD dwMaximumSizeLow,

   PCTSTR pszName);

第一個引數hFile用於標識你想要對映到程序地址空間中的檔案控制代碼。該控制代碼由前面呼叫的CreateFile函式返回。

第二個引數psa引數是指向檔案對映核心物件的SECURITY_ATTRIBUTES結構的指標,通常傳遞的值是NULL(它提供預設的安全特性,返回的控制代碼是不能繼承的)。

第三個引數fdwProtect引數使你能夠設定這些保護屬性。大多數情況下,可以設定下表列出的3個保護屬性之一。

使用fdwProtect 引數設定的部分保護屬性

保護屬性

含義

PAGE_READONLY

當檔案對映物件被對映時,可以讀取檔案的資料。必須已經將GENERIC_READ傳遞給CreateFile函式

PAGE_READWRITE

當檔案對映物件被對映時,可以讀取和寫入檔案的資料。必須已經將GENERIC_READ | GENERIC_WRITE傳遞給Creat eFile

PAGE_WRITECOPY

當檔案對映物件被對映時,可以讀取和寫入檔案的資料。如果寫入資料,會導致頁面的私有拷貝得以建立。必須已經將GENERIC_READ或GENERIC_WRITE傳遞給CreateFile

除了上面的頁面保護屬性外,還有4個節保護屬性

節的第一個保護屬性SEC_NOCACHE,它告訴系統,沒有將檔案的任何記憶體對映頁面放入快取記憶體。因此,當將資料寫入該檔案時,系統將更加經常地更新磁碟上的檔案資料。供裝置驅動程式開發人員使用的,應用程式通常不使用。

節的第二個保護屬性SEC_IMAGE,它告訴系統,你對映的檔案是個可移植的可執行PE)檔案映像。當系統將該檔案對映到你的程序的地址空間中時,系統要檢視檔案的內容,以確定將哪些保護屬性賦予檔案映像的各個頁面。例如, PE檔案的程式碼節( . text)通常用PAGE_ EXECUTE_READ屬性進行對映,PE檔案的資料節( .data) 則通常用PAGE_READW RITE屬性進行對映。如果設定的屬性是S E C _ I M A G E,則告訴系統進行檔案映像的對映,並設定相應的頁面保護屬性。

最後兩個保護屬性是SEC_RESERVESEC_COMMIT,它們是兩個互斥屬性。只有當建立由系統的頁檔案支援的檔案對映物件時,這兩個標誌才有意義。SEC_COMMIT標誌能使CreateFileMapping從系統的頁檔案中提交儲存器。如果兩個標誌都不設定,其結果也一樣。

第四和五個引數:dwMaximumSizeHighdwMaximumSizeLow這兩個引數將告訴系統該檔案的最大位元組數

最後一個引數是pszName它是個以0結尾的字串,用於給該檔案對映物件賦予一個名字。該名字用於與其他程序共享檔案對映物件。

步驟3:將檔案資料對映到程序的地址空間

將檔案的資料作為對映到該區域的物理儲存器進行提交。

PVOID MapViewOfFile(

   HANDLE hFileMappingObject,

   DWORD dwDesiredAccess,

   DWORD dwFileOffsetHigh,

   DWORD dwFileOffsetLow,

   SIZE_T dwNumberOfBytesToMap);

第一個引數:hFileMappingObject用於標識檔案對映物件的控制代碼,該控制代碼是前面呼叫CreateFileMappingOpenFileMapping函式返回的。

第二個引數:dwDesiredAccess用於標識如何訪問該資料。可以設定下表所列的4個值中的一個。

含義

FILE_MAP_WRITE

可以讀取和寫入檔案資料。CreateFileMapping函式必須通過傳遞PAGE_READWRITE標誌來呼叫

FILE_MAP_READ

可以讀取檔案資料。CreateFileMapping函式可以通過傳遞下列任何一個保護屬性來呼叫:PAGE_READONLY、PAGE_ READWRITE或PAGE_WRITECOPY

FILE_MAP_ALL_ACCES S

與FILE_MAP_WRITE相同

FILE_MAP_COPY

可以讀取和寫入檔案資料。如果寫入檔案資料,可以建立一個頁面的私有拷貝。在Windows 2000中,CreateileMapping函式可以用PAGE_READONLY、PAGE_READWRITE或PAGE_WRITECOPY等保護屬性中的任何一個來呼叫。在Windows 98中,CreateFileMapping必須用PAGE_WRITECOPY來呼叫

(一個檔案對映到你的程序的地址空間中時,你不必一次性地對映整個檔案。相反,可以只將檔案的一小部分對映到地址空間。被對映到程序的地址空間的這部分檔案稱為一個檢視。)

第三四個引數:dwFileOfsetHighdwFileOfsetLow引數。指定哪個位元組應該作為檢視中的第一個位元組來對映。

第五個引數dwNumberOfBytesToMap有多少位元組要對映到地址空間。如果設定的值是0,那麼系統將設法把從檔案中的指定位移開始到整個檔案的結尾的檢視對映到地址空間。

步驟4:從程序的地址空間中撤消檔案資料的映像

當不再需要保留對映到程序地址空間區域中的檔案資料時,可以通過呼叫下面的函式將它釋放:

BOOL UnmapViewOfFile(PVOID pvBaseAddress);

引數:pvBaseAddressMapViewOfFile函式返回。

注意:如果沒有呼叫這個函式,那麼在程序終止執行前,保留的區域就不會被釋放。每當呼叫MapViewOfFile時,系統總是在你的程序地址空間中保留一個新區域,而以前保留的所有區域將不被釋放。

為了提高速度,系統將檔案的資料頁面進行快取記憶體,並且在對檔案的對映檢視進行操作時不立即更新檔案的磁碟映像。如果需要確保你的更新被寫入磁碟,可以強制系統將修改過的資料的一部分或全部重新寫入磁碟映像中,方法是呼叫FlushViewOfFile函式:

BOOL FlushViewOfFile(

   PVOID pvAddress,

   SIZE_T dwNumberOfBytesToFlush);

第一個引數是包含在記憶體對映檔案中的檢視的一個位元組的地址。該函式將你在這裡傳遞的地址圓整為一個頁面邊界值。

第二個引數用於指明你想要重新整理的位元組數。系統將把這個數字向上圓整,使得位元組總數是頁面的整數。如果你呼叫FlushViewOfFile函式並且不修改任何資料,那麼該函式只是返回,而不將任何資訊寫入磁碟。

步驟5和步驟6:關閉檔案對映物件和檔案物件

CloseHandle函式關閉相應的物件。

在程式碼開始執行時關閉這些物件:

HANDLEhFile = CreateFile(...);

HANDLEhFileMapping = CreateFileMapping(hFile, ...);

CloseHandle(hFile);

PVOIDpvFile = MapViewOfFile(hFileMapping, ...);

CloseHandle(hFileMapping);

// Use thememory-mapped file.

UnmapViewOfFile(pvFile);

// ------------------------------------------------------------

// 檔名: 17_FileMapping2.cpp

// 建立者方煜寬

// 郵箱 [email protected]

// 建立時間:  2010-7-12 23:50

// 功能描述記憶體對映資料檔案

//

// ------------------------------------------------------------

#include "stdafx.h"

#include "windows.h"

#include <iostream>

using namespace std;

int _tmain(int argc,_TCHAR*argv[])

{

   // Open the file that we want to map.

   // 注意請在c盤,自己建立一個kuan.txt檔案,並寫入內容

   HANDLE hFile =::CreateFile(L"C:\\kuan.txt",

      GENERIC_READ |GENERIC_WRITE,

      0,

      NULL,

      OPEN_ALWAYS,

      FILE_ATTRIBUTE_NORMAL,

      NULL);

   // Create a file-mapping object for the file.

   HANDLE hFileMapping= ::CreateFileMapping(hFile,

      NULL,

      PAGE_WRITECOPY,

      0,0,

      NULL);

   PBYTE pbFile =(PBYTE)::MapViewOfFile(hFileMapping,FILE_MAP_COPY,0, 0, 0);

   cout << pbFile<< endl;

   ::UnmapViewOfFile(pbFile);

   ::CloseHandle(hFileMapping);

   ::CloseHandle(hFile);

   return 0;

}



3.虛擬記憶體

http://www.cnblogs.com/fangyukuan/archive/2010/09/06/1818724.html

虛擬記憶體

在地址空間中保留一個區域

通過呼叫VirtualAlloc函式,可以在程序的地址空間中保留一個區域:

PVOID VirtualAlloc(

   PVOID pvAddress,

   SIZE_T dwSize,

   DWORD fdwAllocationType,

   DWORD fdwProtect);

第一個引數pvAddress包含一個記憶體地址,用於設定想讓系統將地址空間保留在什麼地方。 

如果在特定的地址上不存在空閒區域,或者如果空閒區域不夠大,那麼系統就不能滿足你的要求,VirtualAlloc函式返回NULL。注意,為pvAddress引數傳遞的任何地址必須始終位於程序的使用者方式分割槽中,否則對VirtualAlloc函式的呼叫就會失敗。 

地址空間區域總是按照分配粒度的邊界來保留的(迄今為止在所有的Windows環境下均是64KB)。 

第二個引數是dwSize,用於設定想保留的區域的大小(以位元組為計量單位)。由於系統保留的區域始終必須是CPU頁面大小的倍數。 

第三個引數是fdwAllocationType,它能夠告訴系統你想保留一個區域還是提交物理儲存器(這樣的區分是必要的,因為VirtualAlloc函式也可以用來提交物理儲存器)。若要保留一個地址空間區域,必須傳遞MEM_RESERVE識別符號作為FdwAllocationType引數的值。

如果保留的區域預計在很長時間內不會被釋放,那麼可以在儘可能高的記憶體地址上保留該區域。這樣,該區域就不會從程序地址空間的中間位置上進行保留。因為在這個位置上它可能導致區域分成碎片。如果想讓系統在最高記憶體地址上保留一個區域,必須為pvAddress引數fdwAllocationType 引數傳遞NULL,還必須逐位使用OR 將MEM_TOP_DOWN標誌和MEM_RESERVE標誌連線起來

最後一個引數是fdwProtect,用於指明應該賦予該地址空間區域的保護屬性。與該區域相關聯的保護屬性對對映到該區域的已提交記憶體沒有影響。

當保留一個區域時,應該為該區域賦予一個已提交記憶體最常用的保護屬性。例如,如果打算提交的物理儲存器的保護屬性是PAGE_READWRITE(這是最常用的保護屬性),那麼應該用PAGE_READWRITE保護屬性來保留該區域。當區域的保護屬性與已提交記憶體的保護屬性相匹配時,系統儲存的內部記錄的執行效率最高。

可以使用下列保護屬性中的任何一個: PAGE_NOACCESS、PAGE_READWRITE、PAGE_READONLY、PAGE_EXECUTE、PAGE_EXECUTE_READ或PAGE_EXECUTE _READWRITE。但是,既不能設定PAGE_WRITECOPY屬性,也不能設定PAGE_EXECUTE_WRITECOPY屬性。如果設定了這些屬性,VirtualAlloc函式將不保留該區域,並且返回NULL。另外,當保留地址空間區域時,不能使用保護屬性標誌PAGE_GUARD,PAGE_NOCACHE或PAGE_WRITECOMBINE,這些標誌只能用於已提交的記憶體。

在保留區域中的提交儲存器

當保留一個區域後,必須將物理儲存器提交給該區域,然後才能訪問該區域中包含的記憶體地址。物理儲存器總是按頁面邊界頁面大小的塊來提交的。

若要提交物理儲存器,必須再次呼叫VirtualAlloc函式,fdwAllocationType引數傳遞的是MEM_COMMIT,不必立即將物理儲存器提交給整個區域(可以先提供一部分)。

同一個記憶體頁面的不同部分不能使用不同的保護屬性。然而,區域中的一個頁面可以使用一種保護屬性(比如PAGE_READWRITE),而同一個區域中的另一個頁面可以使用不同的保護屬性(比如PAGE_READONLY)。

同時進行區域的保留和記憶體的提交

PVOIDpvMem = VirtualAlloc(NULL,99 * 1024,

   MEM_RESERVE | MEM_COMMIT,PAGE_READWRITE);

回收虛擬記憶體和釋放地址空間區域

BOOL VirtualFree(

   LPVOID pvAddress,

   SIZE_T dwSize,

   DWORD fdwFreeType);

引數pvAddress:必須是該區域的基地址。此地址與該區域被保留時VirtualAlloc函式返回的地址相同。

引數dwSize:系統知道在特定記憶體地址上的該區域的大小,因此可以為dwSize引數傳遞0。實際上,必須為dwSize引數傳遞0,否則對VirtualFree的呼叫就會失敗。

引數fdwFreeType:必須傳遞MEM_RELEASE,以告訴系統將所有對映的物理儲存器提交給該區域並釋放該區域。

釋放一個區域時,必須釋放該區域保留的所有地址空間。例如不能保留一個128 KB的區域,然後決定只釋放它的64 KB。必須釋放所有的128 KB。

當想要從一個區域回收某些物理儲存器,但是卻不釋放該區域時,也可以呼叫VirtualFree函式。

若要回收某些物理儲存器,必須在VirtualFree函式的pvAddress引數中傳遞用於標識要回收的第一個頁面的記憶體地址,還必須在dwSize引數中設定要釋放的位元組數,並在fdwFreeType引數中傳遞MEM_DECOMMIT標誌。

與提交物理儲存器的情況一樣,回收時也必須按照頁面的分配粒度來進行。這就是說,設定頁面中間的一個記憶體地址就可以回收整個頁面。當然,如果pvAddress + dwSize的值位於一個頁面的中間,那麼包含該地址的整個頁面將被回收。因此位於pvAddress 至pvAddress +dwSize範圍內的所有頁面均被回收。

4.RandomAccessFile

http://blog.csdn.net/akon_vm/article/details/7429245#

RandomAccessFile是用來訪問那些儲存資料記錄的檔案的,你就可以用seek( )方法來訪問記錄,並進行讀寫了。這些記錄的大小不必相同;但是其大小和位置必須是可知的。但是該類僅限於操作檔案。

RandomAccessFile不屬於InputStream和OutputStream類系的。實際上,除了實現DataInput和DataOutput介面之外(DataInputStream和DataOutputStream也實現了這兩個介面),它和這兩個類系毫不相干,甚至不使用InputStream和OutputStream類中已經存在的任何功能;它是一個完全獨立的類,所有方法(絕大多數都只屬於它自己)都是從零開始寫的。這可能是因為RandomAccessFile能在檔案裡面前後移動,所以它的行為與其它的I/O類有些根本性的不同。總而言之,它是一個直接繼承Object的,獨立的類。

基本上,RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream結合起來,再加上它自己的一些方法,比如定位用的getFilePointer( ),在檔案裡移動用的seek( ),以及判斷檔案大小的length( )、skipBytes()跳過多少位元組數。此外,它的建構函式還要一個表示以只讀方式("r"),還是以讀寫方式("rw")開啟檔案的引數 (和C的fopen( )一模一樣)。它不支援只寫檔案。

只有RandomAccessFile才有seek搜尋方法,而這個方法也只適用於檔案。BufferedInputStream有一個mark( )方法,你可以用它來設定標記(把結果儲存在一個內部變數裡),然後再呼叫reset( )返回這個位置,但是它的功能太弱了,而且也不怎麼實用。

RandomAccessFile的絕大多數功能,但不是全部,已經被JDK 1.4的nio的"記憶體對映檔案(memory-mapped files)"給取代了,你該考慮一下是不是用"記憶體對映檔案"來代替RandomAccessFile了。


package test;

import java.io.IOException;
import java.io.RandomAccessFile;
//有沒有注意到 每次一個rf都只是做了自己一件事情 要不就是寫資料  要不就是讀資料 沒有說寫入資料之後立馬讀資料的
public class HelloWorld {
	public static void main(String[] args) throws IOException {
		RandomAccessFile rf = new RandomAccessFile("rtest.dat", "rw");
		for (int i = 0; i < 10; i++) {
			//寫入基本型別double資料
			rf.writeDouble(i * 1.414);
		}
		/*for (int j = 0; j < 10; j++) {
			System.out.println("Value " + j + ": " + rf.readDouble());
		}*/
		rf.close();
		rf = new RandomAccessFile("rtest.dat", "rw");
		//直接將檔案指標移到第5個double資料後面
		rf.seek(5 * 8);
		//覆蓋第6個double資料
		rf.writeDouble(47.0001);
		rf.close();
		rf = new RandomAccessFile("rtest.dat", "r");
		for (int i = 0; i < 10; i++) {
			System.out.println("Value " + i + ": " + rf.readDouble());
		}
		rf.close();
	}
}

綜合示例:

package test;

/*
 * 程式功能:演示了RandomAccessFile類的操作,同時實現了一個檔案複製操作。
 */


import java.io.*;

public class HelloWorld {
 public static void main(String[] args) throws Exception {
  RandomAccessFile file = new RandomAccessFile("file", "rw");
  // 以下向file檔案中寫資料
  file.writeInt(20);// 佔4個位元組
  file.writeDouble(8.236598);// 佔8個位元組
  file.writeUTF("這是一個UTF字串");// 這個長度寫在當前檔案指標的前兩個位元組處,可用readShort()讀取
  file.writeBoolean(true);// 佔1個位元組
  file.writeShort(395);// 佔2個位元組
  file.writeLong(2325451l);// 佔8個位元組
  file.writeUTF("又是一個UTF字串");
  file.writeFloat(35.5f);// 佔4個位元組
  file.writeChar('a');// 佔2個位元組

  file.seek(0);// 把檔案指標位置設定到檔案起始處

  // 以下從file檔案中讀資料,要注意檔案指標的位置
  System.out.println("——————從file檔案指定位置讀資料——————");
  System.out.println(file.readInt());
  System.out.println(file.readDouble());
  System.out.println(file.readUTF());

  file.skipBytes(3);// 將檔案指標跳過3個位元組,本例中即跳過了一個boolean值和short值。
  System.out.println(file.readLong());

  file.skipBytes(file.readShort()); // 跳過檔案中“又是一個UTF字串”所佔位元組,注意readShort()方法會移動檔案指標,所以不用加2。
  System.out.println(file.readFloat());
  
  //以下演示檔案複製操作
  System.out.println("——————檔案複製(從file到fileCopy)——————");
  file.seek(0);
  RandomAccessFile fileCopy=new RandomAccessFile("fileCopy","rw");
  int len=(int)file.length();//取得檔案長度(位元組數)
  byte[] b=new byte[len];
//  把file裡面的內容寫入到陣列b中
  file.readFully(b);
//  直接寫入陣列
  fileCopy.write(b);
  System.out.println("複製完成!");
 }
}


readfully函式使用示例:

package test;

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class HelloWorld {
   public static void main(String[] args) throws IOException {
      
      InputStream is = null;
      DataInputStream dis = null;
      
      try{
         // create file input stream
//    	  FileInputStream 類關注的是檔案內容,以位元組方式讀取,而 File 類關注的是檔案在磁碟上的儲存。
    	  
    	/*  一般使用以下構造方法來例項化,首先建立File物件,通過File物件來例項化FileInputStream。
    	  File file = new File(pathname);
    	  FileInputStream inputStream = new FileInputStream(file);*/
    	  
    	  
         is = new FileInputStream("test.txt");
         
         // create new data input stream
         dis = new DataInputStream(is);
         
         // available stream to be read
        /* 返回下一次對此輸入流呼叫的方法可以不受阻塞地從此輸入流讀取(或跳過)的估計剩餘位元組數。
          下一個呼叫者可能是同一個執行緒,也可能是另一個執行緒。一次讀取或跳過此數量個位元組不會發生阻塞,但讀取或跳過的位元組可能小於該數。*/
         int length = dis.available();
         
         // create buffer
         byte[] buf = new byte[length];
         
         // read the full data into the buffer
         dis.readFully(buf);
         
         // for each byte in the buffer
         for (byte b:buf)
         {
            // convert byte to char
            char c = (char)b; 
            
            // prints character
            System.out.print(c);
         }
      }catch(Exception e){
         // if any error occurs
         e.printStackTrace();
      }finally{
         
         // releases all system resources from the streams
         if(is!=null)
            is.close();
         if(dis!=null)
            dis.close();
      }
   }
}