【java】内存映射文件、虚拟内存、RandomAccessFile类

来源:互联网 发布:gogs windows安装 编辑:程序博客网 时间:2024/06/05 11:27

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);

 

例子:(vs2008代码下载)

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

// 文件名   : 17_FileMapping2.cpp

// 创建者    方煜宽

// 邮箱      fangyukuan@gmail.com

// 创建时间  :  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();      }   }}



0 0