您的位置:首页 > 编程语言 > Java开发

Java的随机读写文件-RandomAccessFile 与内存映射

2012-10-15 15:44 791 查看


RandomAccessFile

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了。

Java代码

import java.io.IOException;

import java.io.RandomAccessFile;

public class TestRandomAccessFile {

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

}

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

}

}


内存映射文件

内存映射文件能让你创建和修改那些因为太大而无法放入内存的文件。有了内存映射文件,你就可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问。这种解决办法能大大简化修改文件的代码。

fileChannel.map(FileChannel.MapMode mode, long position, long size)将此通道的文件区域直接映射到内存中。注意,你必须指明,它是从文件的哪个位置开始映射的,映射的范围又有多大;也就是说,它还可以映射一个大文件的某个小片断。

MappedByteBuffer是ByteBuffer的子类,因此它具备了ByteBuffer的所有方法,但新添了force()将缓冲区的内容强制刷新到存储设备中去、load()将存储设备中的数据加载到内存中、isLoaded()位置内存中的数据是否与存储设置上同步。这里只简单地演示了一下put()和get()方法,除此之外,你还可以使用asCharBuffer( )之类的方法得到相应基本类型数据的缓冲视图后,可以方便的读写基本类型数据。

Java代码

import java.io.RandomAccessFile;

import java.nio.MappedByteBuffer;

import java.nio.channels.FileChannel;

public class LargeMappedFiles {

static int length = 0x8000000; // 128 Mb

public static void main(String[] args) throws Exception {

// 为了以可读可写的方式打开文件,这里使用RandomAccessFile来创建文件。

FileChannel fc = new RandomAccessFile("test.dat", "rw").getChannel();

//注意,文件通道的可读可写要建立在文件流本身可读写的基础之上

MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, length);

//写128M的内容

for (int i = 0; i < length; i++) {

out.put((byte) 'x');

}

System.out.println("Finished writing");

//读取文件中间6个字节内容

for (int i = length / 2; i < length / 2 + 6; i++) {

System.out.print((char) out.get(i));

}

fc.close();

}

}

尽管映射写似乎要用到FileOutputStream,但是映射文件中的所有输出 必须使用RandomAccessFile,但如果只需要读时可以使用FileInputStream,写映射文件时一定要使用随机访问文件,可能写时要读的原因吧。

该程序创建了一个128Mb的文件,如果一次性读到内存可能导致内存溢出,但这里访问好像只是一瞬间的事,这是因为,真正调入内存的只是其中的一小部分,其余部分则被放在交换文件上。这样你就可以很方便地修改超大型的文件了(最大可以到2 GB)。注意,Java是调用操作系统的"文件映射机制"来提升性能的。

------------------------------------------------------

当然,还可以参考以下这篇文章重写一个类来优化:

http://www.ibm.com/developerworks/cn/java/l-javaio/index.html

它的测试结论参考为:

耗用时间(秒)
RandomAccessFileRandomAccessFile95.848
BufferedInputStream + DataInputStreamBufferedOutputStream + DataOutputStream2.935
BufferedRandomAccessFileBufferedOutputStream + DataOutputStream2.813
BufferedRandomAccessFileBufferedRandomAccessFile2.453
BufferedRandomAccessFile优BufferedRandomAccessFile优2.197
BufferedRandomAccessFile完BufferedRandomAccessFile完0.401
MappedByteBuffer+ RandomAccessFileMappedByteBuffer+ RandomAccessFile1.209
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: