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

我试图向学生们说明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 <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。不过,这并没有增加多少时间成本(但确实表明基准测试的作者要么误解了什么,要么不够小心)
这就解释了该特定基准的高昂成本

你问:

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