Java 传统IO与内存映射
我试图向学生们说明java中传统IO和内存映射文件在性能上的差异。 我在网上的某个地方找到了一个例子,但并不是所有的事情我都清楚,我甚至不认为所有的步骤都是必要的。我在这里和那里读了很多关于它的书,但我不相信这两种方法的正确实现 我试图理解的代码是:Java 传统IO与内存映射,java,file-io,memory-mapped-files,Java,File Io,Memory Mapped Files,我试图向学生们说明java中传统IO和内存映射文件在性能上的差异。 我在网上的某个地方找到了一个例子,但并不是所有的事情我都清楚,我甚至不认为所有的步骤都是必要的。我在这里和那里读了很多关于它的书,但我不相信这两种方法的正确实现 我试图理解的代码是: public class FileCopy{ public static void main(String args[]){ if (args.length < 1){ System.out.p
public class FileCopy{
public static void main(String args[]){
if (args.length < 1){
System.out.println(" Wrong usage!");
System.out.println(" Correct usage is : java FileCopy <large file with full path>");
System.exit(0);
}
String inFileName = args[0];
File inFile = new File(inFileName);
if (inFile.exists() != true){
System.out.println(inFileName + " does not exist!");
System.exit(0);
}
try{
new FileCopy().memoryMappedCopy(inFileName, inFileName+".new" );
new FileCopy().customBufferedCopy(inFileName, inFileName+".new1");
}catch(FileNotFoundException fne){
fne.printStackTrace();
}catch(IOException ioe){
ioe.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}
}
public void memoryMappedCopy(String fromFile, String toFile ) throws Exception{
long timeIn = new Date().getTime();
// read input file
RandomAccessFile rafIn = new RandomAccessFile(fromFile, "rw");
FileChannel fcIn = rafIn.getChannel();
ByteBuffer byteBuffIn = fcIn.map(FileChannel.MapMode.READ_WRITE, 0,(int) fcIn.size());
fcIn.read(byteBuffIn);
byteBuffIn.flip();
RandomAccessFile rafOut = new RandomAccessFile(toFile, "rw");
FileChannel fcOut = rafOut.getChannel();
ByteBuffer writeMap = fcOut.map(FileChannel.MapMode.READ_WRITE,0,(int) fcIn.size());
writeMap.put(byteBuffIn);
long timeOut = new Date().getTime();
System.out.println("Memory mapped copy Time for a file of size :" + (int) fcIn.size() +" is "+(timeOut-timeIn));
fcOut.close();
fcIn.close();
}
static final int CHUNK_SIZE = 100000;
static final char[] inChars = new char[CHUNK_SIZE];
public static void customBufferedCopy(String fromFile, String toFile) throws IOException{
long timeIn = new Date().getTime();
Reader in = new FileReader(fromFile);
Writer out = new FileWriter(toFile);
while (true) {
synchronized (inChars) {
int amountRead = in.read(inChars);
if (amountRead == -1) {
break;
}
out.write(inChars, 0, amountRead);
}
}
long timeOut = new Date().getTime();
System.out.println("Custom buffered copy Time for a file of size :" + (int) new File(fromFile).length() +" is "+(timeOut-timeIn));
in.close();
out.close();
}
}
我或多或少看到了发生了什么,我的输出如下:
Stream Write: 653
Mapped Write: 51
Stream Read: 651
Mapped Read: 40
Stream Read/Write: 14481
Mapped Read/Write: 6
是什么让流读/写如此之长?作为一个读/写测试,对我来说,一遍又一遍地读取同一个整数(如果我能很好地理解流读/写
中发生的事情)似乎有点毫无意义,从以前编写的文件中读取int,然后在同一个位置读取和写入int不是更好吗?有没有更好的方法来说明这一点
我为这些事情绞尽脑汁已经有一段时间了,但我无法了解全部情况。1)这些听起来像是你的学生应该问的问题,而不是相反
2) 使用这两种方法的原因是为了演示复制文件的不同方式。我猜第一个方法(RamdomAccessFile)在RAM中创建文件的版本,然后复制到磁盘上的新版本,第二个方法(customBufferedCop)直接从驱动器读取
3) 我不确定,但我认为synchronized用于确保同一类的多个实例不会同时写入
4) 至于最后一个问题,我得走了,所以我希望其他人能帮你
说真的,这些听起来就像是导师应该教给学生的问题。如果你自己没有能力研究像这样简单的事情,你会给你的学生树立什么样的榜样 我在一个基准“流读/写”中看到的是:
- 它实际上并不执行流I/O,而是查找文件中的特定位置。这是非缓冲的,因此所有I/O都必须从磁盘完成(其他流使用缓冲I/O,因此实际上是在大数据块中读取/写入,然后从内存区域读取或写入INT)
- 它正在寻找结束-4字节,因此读取最后一个int,然后写入一个新的int。文件的长度在每次迭代中继续增长一个int。不过,这并没有增加多少时间成本(但确实表明基准测试的作者要么误解了什么,要么不够小心)
RandomAccessFile
读取和写入文件中的同一位置时,需要在读取和写入之前进行搜索:
raf.seek(raf.length() - 4);
int val = raf.readInt();
raf.seek(raf.length() - 4);
raf.writeInt(val);
这确实说明了内存映射I/O的一个优点,因为您可以使用相同的内存地址访问文件的相同位,而不必在每次调用之前进行额外的查找
顺便说一下,您的第一个基准示例类可能也有问题,因为CHUNK\u SIZE
不是文件系统块大小的偶数倍。通常,对于大多数应用程序来说,使用1024和8192的倍数是一个很好的最佳选择(Java的BufferedInputStream
和BufferedOutput Stream
使用该值作为默认缓冲区大小的原因)。操作系统将需要读取额外的块,以满足不在块边界上的读取请求。后续读取(流)将重新读取相同的块,可能是一些完整的块,然后再次读取额外的块。内存映射I/O始终以块的形式进行物理读写,因为实际I/O由操作系统内存管理器处理,该管理器将使用其页面大小。页面大小总是经过优化,以便很好地映射到文件块
在该示例中,内存映射测试将所有内容读取到内存缓冲区中,然后将其全部写回。这两个测试在比较这两种情况时写得并不好memmoryMappedCopy
的读写块大小应与customBufferedCopy
相同
编辑:这些测试类可能有更多的错误。由于你对另一个答案的评论,我再次更仔细地看了第一节课
方法customBufferedCopy
是静态的,并使用静态缓冲区。对于这种测试,应该在方法中定义缓冲区。然后它就不需要使用synchronized
(尽管在这种情况下和这些测试中都不需要它)。此静态方法被称为普通方法,这是一种糟糕的编程实践(即使用FileCopy.customBufferedCopy(…)
而不是newfilecopy().customBufferedCopy(…)
)
如果您确实从多个线程运行此测试,那么该缓冲区的使用将是有争议的,基准测试将不仅仅是关于文件I/O,因此比较这两种测试方法的结果是不公平的。感谢您关注此问题。稍后我将看第一个示例,现在,我的教授要求重写两个测试(流和映射读/写)
它们生成随机整数,首先读取索引(生成的整数)并检查此索引处的整数是否等于生成的整数,如果不等于,则将生成的整数写入其索引。他认为这可能导致更好的测试,更多地使用
随机访问文件
,这有意义吗
然而,我有一些问题,首先我不知道如何在流读/写时使用缓冲区。当我使用RandomAccessFile
时,我发现了很多关于byte[]
缓冲区的信息
raf.seek(raf.length() - 4);
int val = raf.readInt();
raf.seek(raf.length() - 4);
raf.writeInt(val);
new Tester("Stream Read/Write") {
public void test() throws IOException {
RandomAccessFile raf = new RandomAccessFile(new File("temp.tmp"), "rw");
raf.seek(numOfUbuffInts*4);
raf.writeInt(numOfUbuffInts);
for (int i = 0; i < numOfUbuffInts; i++) {
int getal = (int) (1 + Math.random() * numOfUbuffInts);
raf.seek(getal*4);
if (raf.readInt() != getal) {
raf.seek(getal*4);
raf.writeInt(getal);
}
}
raf.close();
}
},
new Tester("Mapped Read/Write") {
public void test() throws IOException {
RandomAccessFile raf = new RandomAccessFile(new File("temp.tmp"), "rw");
raf.seek(numOfUbuffInts*4);
raf.writeInt(numOfUbuffInts);
FileChannel fc = raf.getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size()).asIntBuffer();
for(int i = 1; i < numOfUbuffInts; i++) {
int getal = (int) (1 + Math.random() * numOfUbuffInts);
if (ib.get(getal) != getal) {
ib.put(getal, getal);
}
}
fc.close();
}
}