我试图用
Java来向学生说明传统IO和内存映射文件之间的性能差异.
我在互联网上的某个地方找到了一个例子,但并非一切都很清楚,我甚至认为所有步骤都不是.我在这里和那里读了很多关于它的内容,但我不相信它们都没有正确实现.
我试着理解的代码是:
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 ");
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();
}
}
究竟是什么时候使用RandomAccessFile呢?在这里它用于读取和写入memoryMappedCopy,它实际上只是复制一个文件nececary?或者它是记忆映射的一部分?
在customBufferedCopy中,为什么在这里使用同步?
我还发现了一个不同的例子 – 应该测试2之间的性能:
public class MappedIO {
private static int numOfInts = 4000000;
private static int numOfUbuffInts = 200000;
private abstract static class Tester {
private String name;
public Tester(String name) { this.name = name; }
public long runTest() {
System.out.print(name + ": ");
try {
long startTime = System.currentTimeMillis();
test();
long endTime = System.currentTimeMillis();
return (endTime - startTime);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public abstract void test() throws IOException;
}
private static Tester[] tests = {
new Tester("Stream Write") {
public void test() throws IOException {
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(new File("temp.tmp"))));
for(int i = 0; i < numOfInts; i++)
dos.writeInt(i);
dos.close();
}
},
new Tester("Mapped Write") {
public void test() throws IOException {
FileChannel fc =
new RandomAccessFile("temp.tmp", "rw")
.getChannel();
IntBuffer ib = fc.map(
FileChannel.MapMode.READ_WRITE, 0, fc.size())
.asIntBuffer();
for(int i = 0; i < numOfInts; i++)
ib.put(i);
fc.close();
}
},
new Tester("Stream Read") {
public void test() throws IOException {
DataInputStream dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream("temp.tmp")));
for(int i = 0; i < numOfInts; i++)
dis.readInt();
dis.close();
}
},
new Tester("Mapped Read") {
public void test() throws IOException {
FileChannel fc = new FileInputStream(
new File("temp.tmp")).getChannel();
IntBuffer ib = fc.map(
FileChannel.MapMode.READ_ONLY, 0, fc.size())
.asIntBuffer();
while(ib.hasRemaining())
ib.get();
fc.close();
}
},
new Tester("Stream Read/Write") {
public void test() throws IOException {
RandomAccessFile raf = new RandomAccessFile(
new File("temp.tmp"), "rw");
raf.writeInt(1);
for(int i = 0; i < numOfUbuffInts; i++) {
raf.seek(raf.length() - 4);
raf.writeInt(raf.readInt());
}
raf.close();
}
},
new Tester("Mapped Read/Write") {
public void test() throws IOException {
FileChannel fc = new RandomAccessFile(
new File("temp.tmp"), "rw").getChannel();
IntBuffer ib = fc.map(
FileChannel.MapMode.READ_WRITE, 0, fc.size())
.asIntBuffer();
ib.put(0);
for(int i = 1; i < numOfUbuffInts; i++)
ib.put(ib.get(i - 1));
fc.close();
}
}
};
public static void main(String[] args) {
for(int i = 0; i < tests.length; i++)
System.out.println(tests[i].runTest());
}
}
我或多或少看到了什么,我的输出看起来像这样:
Stream Write: 653
Mapped Write: 51
Stream Read: 651
Mapped Read: 40
Stream Read/Write: 14481
Mapped Read/Write: 6
什么使流读/写如此令人难以置信?作为读/写测试,对我来说,反复读取相同的整数看起来有点无意义(如果我很清楚在流读/写中发生了什么)从以前读取int是不是更好写入文件,只是在同一个地方读写内容?有没有更好的方式来说明它?
我一直在关注很多这些事情,但我无法全面了解……
最佳答案 我看到的一个基准“流读/写”是:
>它实际上并不是流I / O,而是寻找文件中的特定位置.这是非缓冲的,因此所有I / O必须从磁盘完成(其他流使用缓冲I / O,因此在大块中实际读/写,然后从存储区读取或写入整数).
>它正在寻找结尾 – 4个字节,因此读取最后一个int并写入一个新的int.每次迭代时,文件的长度都会延长一个int.这确实不会增加时间成本(但确实表明该基准的作者误解了某些东西或者不小心).
这解释了该特定基准的非常高的成本.
您询问:
Wouldn’t it be better to read int’s
from the previously written file and
just read and write ints on the same
place?
这是作者我认为试图用最后两个基准测试,但这不是他们得到的.使用RandomAccessFile读取和写入文件中的相同位置,您需要在读取和写入之前进行搜索:
raf.seek(raf.length() - 4);
int val = raf.readInt();
raf.seek(raf.length() - 4);
raf.writeInt(val);
这确实展示了内存映射I / O的一个优点,因为您可以使用相同的内存地址来访问文件的相同位,而不必在每次调用之前进行额外的搜索.
顺便说一句,你的第一个基准示例类也可能有问题,因为CHUNK_SIZE不是文件系统块大小的偶数倍.对于大多数应用程序而言,使用1024和8192的倍数通常被认为是一个很好的最佳点(并且Java的BufferedInputStream和BufferedOutputStream使用该值作为默认缓冲区大小的原因).操作系统需要读取一个额外的块以满足不在块边界上的读取请求.后续读取(流)将重新读取相同的块,可能是一些完整的块,然后再次读取.内存映射I / O始终以块的形式进行物理读写,因为实际的I / O由OS内存管理器处理,后者将使用其页面大小.页面大小始终经过优化,可以很好地映射到文件块.
在该示例中,内存映射测试确实将所有内容读入内存缓冲区,然后将其全部写回.这两个测试真的写不出来比较这两个案例. memmoryMappedCopy应该以与customBufferedCopy相同的块大小进行读写.
编辑:这些测试类甚至可能有更多错误.由于你对另一个答案的评论,我再次仔细看了第一堂课.
方法customBufferedCopy是静态的并使用静态缓冲区.对于这种测试,缓冲区应该在方法中定义.然后它不需要使用synchronized(虽然它在这个上下文中并不需要它,无论如何也是这些测试).这种静态方法被称为普通方法,这是一种糟糕的编程习惯(即使用FileCopy.customBufferedCopy(…)而不是新的FileCopy().customBufferedCopy(…)).
如果您确实从多个线程运行此测试,则该缓冲区的使用将是有争议的,并且基准测试不仅仅是文件I / O,因此比较两种测试方法的结果是不公平的.