在 Java 7 中删除二进制文件的一部分的最佳方法是什么

What is the best way of deleting a section of a binary file in Java 7

即我有一个 10 mb 的文件,我想删除 1M 到 2M 的字节,所以生成的文件是 9mb,文件中的数据从 2m 字节开始,现在从 1M 开始

我正在使用 Java 7,因此可以使用 NIO,文件通常大小为 10MB,并且经常通过网络访问,因此我正在寻找一个性能良好的优雅解决方案。

我知道 BteBuffer.allocateDirect() 和 File.getChannel() 但我正在努力解决是否有办法做我想做的事情,而不涉及必须从文件通道写入 8MB 到临时缓冲区只是为了将其写回不同位置的文件,或者如果使用 allocateDirect()

这实际上没问题

将结果写入临时文件,然后将旧文件替换为您的临时文件(充当磁盘缓冲区)。

代码示例:

public static void main(String[] args) {
    // -- prepare files
    File inFile = new File("D:/file.dat");
    File outFile;
    try {
        outFile = File.createTempFile("swap", "buffer");
    } catch (IOException ex) {
        throw new IOError(ex);
    }

    // -- process file
    try (
            InputStream inStream = new FileInputStream(inFile);
            OutputStream outStream = new FileOutputStream(outFile)
    ) {
        //drop some bytes      (will be removed)
        inStream.skip(4);
        //modify some bytes    (will be changed)
        for (int i = 0; i < 4; i++) {
            byte b = (byte) inStream.read();
            outStream.write(b >> 4);
        }
        //copy bytes in chunks (will be kept)
        final int CHUNK_SIZE = 1024;
        byte[] chunkBuffer = new byte[CHUNK_SIZE];
        while (true) {
            int chunkSize = inStream.read(chunkBuffer, 0, CHUNK_SIZE);
            if (chunkSize < 0) {
                break;
            }
            outStream.write(chunkBuffer, 0, chunkSize);
        }
    } catch (FileNotFoundException ex) {
        throw new RuntimeException("input file not found!", ex);
    } catch (IOException ex) {
        throw new RuntimeException("failed to trim data!", ex);
    }

    // -- release changes
    //replace inFile with outFile
    inFile.delete();
    outFile.renameTo(inFile);
}

我不认为你可以将一个巨大的文件读入内存并只切掉它的一部分。 @Binkan 的方法更聪明,因为这个方法已经针对高达 15MB

的文件进行了测试
    try {
        // read the file 
        byte[] results = Files.readAllBytes(Paths.get("xfile.apk"));
        // strip off say 1MB
        byte[] trimmed = Arrays.copyOfRange(results, 0,
                (results.length / (1024 * 1024)) - 1);
        Files.write(Paths.get("xfile.apk"), trimmed,
                StandardOpenOption.TRUNCATE_EXISTING);
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

也许使用 MappedByteBuffer 会带来性能开销

我认为在磁盘上使用临时文件的方法是个好方法。如果您处于可以在磁盘上创建新临时文件的情况,那么 nio 确实有一些选项可以帮助您。我刚刚离开这里的 API 和 nio 教程,但似乎 FileChannel.transferFromFileChannel.transferTo 可能是您需要的工具。

我没有测试过以下代码,但它应该能为您指明正确的方向。

public static void main(String[] args) {
    int megaByte = 1024 * 1024;
    // prepare the paths
    Path inPath = Paths.get("D:/file.dat"); // java.nio.file.Paths
    Path outPath; // java.nio.file.Path
    try {
        outPath = Files.createTempFile(null, "swp"); // java.nio.file.Files
    } catch (IOException ex) {
        throw new IOError(ex);
    }

    // process the file
    try (
            FileChannel readChannel = new FileChannel.open(inPath, READ);
            FileChannel writeChannel = new FileChannel.open(outPath, WRITE, TRUNCATE_EXISTING)
    ) {
        long readFileSize = readChannel.size();
        long expectedWriteSize = readFileSize;
        if (readFileSize > 2 * megabyte)
            expectedWriteSize = readFileSize - megabyte;
        else if (readFileSize > megabyte)
            expectedWriteSize = megabyte;
        // copy first megabyte (or entire file if less than a megabyte)
        long bytesTrans = readChannel.transferTo(0, megabyte, writeChannel);
        // copy everything after the second megabyte
        if (readFileSize > 2 * megabyte)
            bytesTrans += readChannel.transferTo(2 * megabyte, readFileSize - 2 * megabyte, writeChannel);
        if (bytesTrans != expectedWriteSize)
            System.out.println("WARNING: Transferred " + bytesTrans + " bytes instead of " + expectedWriteSize);
    } catch (FileNotFoundException ex) {
        throw new RuntimeException("File not found!", ex);
    } catch (IOException ex) {
        throw new RuntimeException("Caught IOException", ex);
    }

    // replace the original file with the temporary file
    try {
        // ATOMIC_MOVE may cause IOException here . . .
        Files.move(outPath, inPath, REPLACE_EXISTING, ATOMIC_MOVE);
    } catch (IOException e1) {
        try {
            // . . . so it's probably worth trying again without that option
            Files.move(outPath, inPath, REPLACE_EXISTING);
        } catch (IOException e2) {
            throw new IOError(e2);
        }
    }
}

即使您无法打开新文件,nio 也可能会有所帮助。如果您打开文件的 read/write 通道或在同一文件上打开两个不同的通道,则可以使用 transferTo 方法将文件的一部分传输到文件的另一部分。我没有足够的经验知道。 API 声明采用显式位置参数(如 transferTo 的第一个参数)的方法可以与写入文件的操作同时进行,因此我不会排除它。如果您尝试这样做,您可能希望以兆字节大小的块重写文件。如果确实有效,那么当您完成将文件的部分写入文件中较早 1 兆字节的位置时,FileChannel.truncate 可用于截断文件的最后一兆字节。