gpt4 book ai didi

java - 传统 IO 与内存映射

转载 作者:搜寻专家 更新时间:2023-11-01 03:30:08 27 4
gpt4 key购买 nike

我试图向学生说明传统 IO 和 java 中的内存映射文件之间的性能差异。我在互联网上的某个地方找到了一个例子,但我并不是很清楚所有的事情,我什至不认为所有的步骤都是必要的。我在这里和那里阅读了很多关于它的内容,但我不相信它们都可以正确实现。

我尝试理解的代码是:

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

什么时候需要使用 RandomAccessFile?这里是用来在memoryMappedCopy中读写的,真的只需要拷贝一个文件吗?或者它是内存映射的一部分?

customBufferedCopy中,这里为什么要用synchronized

我还找到了一个不同的例子,它应该测试 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

是什么让 Stream Read/Write 如此之长?作为读/写测试,对我来说,一遍又一遍地读取相同的整数看起来有点毫无意义(如果我理解 Stream Read/Write 中发生的事情)不是吗最好从以前写入的文件中读取 int 并在同一个地方读取和写入 int?有没有更好的方式来说明它?

一段时间以来,我一直在为很多这些事情伤脑筋,但我就是无法了解全貌..

最佳答案

我在一个基准“流读/写”中看到的是:

  • 它并不真正执行流 I/O,而是查找文件中的特定位置。这是非缓冲的,所以所有的 I/O 都必须从磁盘完成(其他流正在使用缓冲的 I/O,所以真正在大块中读/写,然后从内存区域读取或写入 int)。<
  • 它正在寻找结束 - 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 不是文件系统 block 大小的偶数倍。通常最好使用 1024 的倍数,而 8192 已被证明是大多数应用程序的最佳选择(也是 Java 的 BufferedInputStreamBufferedOutputStream 使用该值作为默认值的原因缓冲区大小)。操作系统将需要读取额外的 block 以满足不在 block 边界上的读取请求。 (流的)后续读取将重新读取同一个 block ,可能是一些完整的 block ,然后再读取一个额外的 block 。内存映射 I/O 总是以 block 为单位进行物理读写,因为实际的 I/O 由操作系统内存管理器处理,该管理器将使用其页面大小。页面大小始终经过优化以很好地映射到文件 block 。

在该示例中,内存映射测试确实将所有内容读入内存缓冲区,然后将其全部写回。这两个测试真的没有写好来比较这两种情况。 memmoryMappedCopy 应该以与 customBufferedCopy 相同的 block 大小读取和写入。

编辑:这些测试类可能还有更多问题。由于您对其他答案的评论,我再次更仔细地看了第一个类。

方法 customBufferedCopy 是静态的,使用静态缓冲区。对于这种测试,缓冲区应该在方法中定义。那么它就不需要使用 synchronized(尽管它在这种情况下和这些测试中都不需要它)。此静态方法被称为普通方法,这是糟糕的编程习惯(即使用 FileCopy.customBufferedCopy(...) 而不是 new FileCopy().customBufferedCopy(...))。

如果您确实从多个线程运行此测试,那么该缓冲区的使用将引起争议,并且基准测试将不仅仅是关于文件 I/O,因此比较两种测试方法的结果是不公平的。

关于java - 传统 IO 与内存映射,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2593531/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com