在 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.transferFrom
或 FileChannel.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
可用于截断文件的最后一兆字节。
即我有一个 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.transferFrom
或 FileChannel.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
可用于截断文件的最后一兆字节。