用Java比较文件

我正在为PACKT创建一系列有关Java网络编程的视频教程。 有整节关于Java NIO。 一个示例程序是通过原始套接字连接将文件从客户端复制到服务器。 客户端从磁盘读取文件,服务器将到达的字节保存到磁盘。 因为这是一个演示,所以服务器和客户端在同一台计算机上运行,​​并且文件从一个目录复制到完全相同的目录,但名称不同。 布丁的证明正在吃掉:文件必须进行比较。

我要复制的文件已创建为包含随机字节。 仅传输文本信息有时会在代码中留下一些棘手的错误。 随机文件是使用简单的Java类创建的:

package packt.java9.network.niodemo;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;

public class SampleMaker {
    public static void main(String[] args) throws IOException {
        byte[] buffer = new byte[1024 * 1024 * 10];
        try (FileOutputStream fos = new FileOutputStream("sample.txt")) {
            Random random = new Random();
            for (int i = 0; i < 16; i++) {
                random.nextBytes(buffer);
                fos.write(buffer);
            }
        }
    }
}

使用IntelliJ比较文件相当容易,但是由于文件是二进制文件且很大,因此这种方法并不是真正的最佳选择。 我决定编写一个简短的程序,该程序不仅可以表明文件不同,还可以表明不同之处。 代码非常简单:

package packt.java9.network.niodemo;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class SampleCompare {
    public static void main(String[] args) throws IOException {
        long start = System.nanoTime();
        BufferedInputStream fis1 = new BufferedInputStream(new FileInputStream("sample.txt"));
        BufferedInputStream fis2 = new BufferedInputStream(new FileInputStream("sample-copy.txt"));
        int b1 = 0, b2 = 0, pos = 1;
        while (b1 != -1 && b2 != -1) {
            if (b1 != b2) {
                System.out.println("Files differ at position " + pos);
            }
            pos++;
            b1 = fis1.read();
            b2 = fis2.read();
        }
        if (b1 != b2) {
            System.out.println("Files have different length");
        } else {
            System.out.println("Files are identical, you can delete one of them.");
        }
        fis1.close();
        fis2.close();
        long end = System.nanoTime();
        System.out.print("Execution time: " + (end - start)/1000000 + "ms");
    }
}

在装有SSD的Mac Book上,比较这两个160MB文件的运行时间约为6秒,如果我指定一个较大的缓冲区(例如10MB)作为BufferedInputStream构造函数的第二个参数,则运行时间不会显着改善。 (另一方面,如果我们不使用BufferedInputStream那么时间大约是十倍。)这是可以接受的,但是如果我只是diff sample.txt sample-copy.txt发出diff sample.txt sample-copy.txt ,那么响应明显更快,而不是6秒。 可能有很多事情,例如Java启动时间, while循环开始时的代码解释,直到JIT编译器认为是开始工作的时候。 我的直觉是,代码会花费大部分时间将文件读入内存。 将字节读取到缓冲区是一个复杂的过程。 它涉及操作系统,设备驱动程序,JVM实现,它们将字节从一个位置移到另一位置,最后我们只比较字节,没有别的。 可以用更简单的方式完成。 我们可以要求操作系统为我们做这件事,并跳过大多数Java运行时活动,文件缓冲区和其他闪烁。

我们可以要求操作系统将文件读取到内存中,然后从它们所在的位置逐个读取字节。 我们不需要一个缓冲区,该缓冲区属于Java对象,并且会占用堆空间。 我们可以使用内存映射文件。 毕竟,内存映射文件使用Java NIO,而这正是当前正在制作的教程视频部分的主题。

操作系统将内存映射文件读入内存,并且字节可供Java程序使用。 内存是由操作系统分配的,它不会消耗堆内存。 如果Java代码修改了映射内存的内容,则操作系统会在认为适当时以优化方式将更改写入磁盘。 但是,这并不意味着如果JVM崩溃,数据就会丢失。 当Java代码修改内存映射文件的内存时,它将修改属于操作系统的内存,该内存在JVM停止运行后可用并且有效。 无法保证并100%防止电源中断和硬件崩溃,但这是非常低的水平。 如果有人对此感到恐惧,那么保护应该在Java无关的硬件级别上进行。 使用内存映射文件,我们可以确保将数据以一定的非常高的概率保存到磁盘中,只有通过容错硬件,群集,不间断电源等才能增加数据。 这些不是Java。 如果确实需要使用Java进行某些操作才能将数据写入磁盘,则可以调用MappedByteBuffer.force()方法,该方法要求操作系统将更改写入磁盘。 但是,过于频繁和不必要地调用它可能会影响性能。 (很简单,因为它会将数据写入磁盘,并且仅在操作系统提示已写入数据时才返回。)

对于大文件,使用内存映射文件读取和写入数据通常要快得多。 为了获得适当的性能,计算机应该有大量的内存,否则,只有部分文件保留在内存中,然后页面错误会增加。 一件好事是,如果同一文件通过两个或多个不同的进程映射到内存中,则将使用同一内存区域。 这样,进程甚至可以相互通信。

使用内存映射文件的比较应用程序如下:

package packt.java9.network.niodemo;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class MapCompare {
    public static void main(String[] args) throws IOException {
        long start = System.nanoTime();
        FileChannel ch1 = new RandomAccessFile("sample.txt", "r").getChannel();
        FileChannel ch2 = new RandomAccessFile("sample-copy.txt", "r").getChannel();
        if (ch1.size() != ch2.size()) {
            System.out.println("Files have different length");
            return;
        }
        long size = ch1.size();
        ByteBuffer m1 = ch1.map(FileChannel.MapMode.READ_ONLY, 0L, size);
        ByteBuffer m2 = ch2.map(FileChannel.MapMode.READ_ONLY, 0L, size);
        for (int pos = 0; pos < size; pos++) {
            if (m1.get(pos) != m2.get(pos)) {
                System.out.println("Files differ at position " + pos);
                return;
            }
        }
        System.out.println("Files are identical, you can delete one of them.");
        long end = System.nanoTime();
        System.out.print("Execution time: " + (end - start) / 1000000 + "ms");
    }
}

为了对文件进行内存映射,我们必须首先使用RandomAccessFile类打开它们,然后从该对象中请求通道。 该通道可用于创建MappedByteBuffer ,它表示文件内容加载到的存储区。 该方法map中的示例映射在只读模式的文件,从该文件的文件的末尾的开始。 我们尝试映射整个文件。 仅当文件大小不超过2GB时,此方法才有效。 起始位置很long但是要映射的区域的大小受Integer大小的限制。

通常来说...哦,是的,比较160MB随机内容文件的运行时间约为1秒。

翻译自: https://www.javacodegeeks.com/2018/02/comparing-files-java.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值