编辑/警告:有这个解决方案潜在的陷阱,因为它大量使用了MappedByteBuffer
,以及如何/时,相应的资源被释放,目前尚不清楚。见this Q&A & JDK-4724038 : (fs) Add unmap method to MappedByteBuffer。
话虽这么说,也请看到这个帖子的末尾
我会做什么Nim suggested:
包裹这其中映射在“块”和一类然后按照你写的方向移动块。这种算法相当简单..只需选择一个块大小,这对你正在编写的数据有意义。
事实上,我确实做到了年前刚刚挖出来的代码,它是这样(脱光了最低限度的演示,有一个方法来写数据):
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
public class SlidingFileWriterThingy {
private static final long WINDOW_SIZE = 8*1024*1024L;
private final RandomAccessFile file;
private final FileChannel channel;
private MappedByteBuffer buffer;
private long ioOffset;
private long mapOffset;
public SlidingFileWriterThingy(Path path) throws IOException {
file = new RandomAccessFile(path.toFile(), "rw");
channel = file.getChannel();
remap(0);
}
public void close() throws IOException {
file.close();
}
public void seek(long offset) {
ioOffset = offset;
}
public void writeBytes(byte[] data) throws IOException {
if (data.length > WINDOW_SIZE) {
throw new IOException("Data chunk too big, length=" + data.length + ", max=" + WINDOW_SIZE);
}
boolean dataChunkWontFit = ioOffset < mapOffset || ioOffset + data.length > mapOffset + WINDOW_SIZE;
if (dataChunkWontFit) {
remap(ioOffset);
}
int offsetWithinBuffer = (int)(ioOffset - mapOffset);
buffer.position(offsetWithinBuffer);
buffer.put(data, 0, data.length);
}
private void remap(long offset) throws IOException {
mapOffset = offset;
buffer = channel.map(FileChannel.MapMode.READ_WRITE, mapOffset, WINDOW_SIZE);
}
}
这里是测试片段:
SlidingFileWriterThingy t = new SlidingFileWriterThingy(Paths.get("/tmp/hey.txt"));
t.writeBytes("Hello world\n".getBytes(StandardCharsets.UTF_8));
t.seek(1000);
t.writeBytes("Are we there yet?\n".getBytes(StandardCharsets.UTF_8));
t.seek(50_000_000);
t.writeBytes("No but seriously?\n".getBytes(StandardCharsets.UTF_8));
什么输出文件的样子:
$ hexdump -C /tmp/hey.txt
00000000 48 65 6c 6c 6f 20 77 6f 72 6c 64 0a 00 00 00 00 |Hello world.....|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000003e0 00 00 00 00 00 00 00 00 41 72 65 20 77 65 20 74 |........Are we t|
000003f0 68 65 72 65 20 79 65 74 3f 0a 00 00 00 00 00 00 |here yet?.......|
00000400 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
02faf080 4e 6f 20 62 75 74 20 73 65 72 69 6f 75 73 6c 79 |No but seriously|
02faf090 3f 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |?...............|
02faf0a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
037af080
我希望我没有锐n通过删除不必要的位和重命名...至少偏移量计算看起来正确(0x3e0 + 8 = 1000,0x02faf080 = 50000000)。
数由文件占用的数据块(左列),而同样大小的另一种非稀疏文件:
$ head -c 58388608 /dev/zero > /tmp/not_sparse.txt
$ ls -ls /tmp/*.txt
8 -rw-r--r-- 1 nug nug 58388608 Jul 19 00:50 /tmp/hey.txt
57024 -rw-r--r-- 1 nug nug 58388608 Jul 19 00:58 /tmp/not_sparse.txt
块(和实际的“稀疏”)的数量将取决于OS &文件系统,上面是Debian Buster,ext4 - HFS + for macOS不支持稀疏文件,而在Windows上,它们需要程序去做一些我不太了解的东西,但这似乎并不容易,甚至不可行来自Java,不确定。
我没有新的数字,但当时这个“滑动-MappedByteBuffer
技术”非常快,正如你在上面看到的,它确实在文件中留下了漏洞。
你需要适应WINDOW_SIZE
的东西,有意义的你,也许通过包装writeBytes
,只要适合你添加的所有writeThingy
方法需要。此外,在这种状态下,它会根据需要增大文件大小,但也可能由WINDOW_SIZE
组成,您可能还需要进行修改。
除非有很好的理由,否则它可能是最好的保持它的简单与此单一的机制,而不是保持一个复杂的双模系统。
关于脆弱性和内存消耗,我和内存800GB以下运行在Linux上的压力测试没有一个小时的任何问题,一台机器上,并在另一个非常谦虚VM配备1G的RAM 。系统看起来非常健康,java进程不会使用任何大量的堆内存。
String path = "/tmp/data.txt";
SlidingFileWriterThingy w = new SlidingFileWriterThingy(Paths.get(path));
final long MAX = 5_000_000_000L;
while (true) {
long offset = 0;
while (offset < MAX) {
offset += Math.pow(Math.random(), 4) * 100_000_000;
if (offset > MAX/5 && offset < 2*MAX/5 || offset > 3*MAX/5 && offset < 4*MAX/5) {
// Keep 2 big "empty" bands in the sparse file
continue;
}
w.seek(offset);
w.writeBytes(("---" + new Date() + "---").getBytes(StandardCharsets.UTF_8));
}
w.seek(0);
System.out.println("---");
Scanner output = new Scanner(new ProcessBuilder("sh", "-c", "ls -ls " + path + "; free")
.redirectErrorStream(true).start().getInputStream());
while (output.hasNextLine()) {
System.out.println(output.nextLine());
}
Runtime r = Runtime.getRuntime();
long memoryUsage = (100 * (r.totalMemory() - r.freeMemory()))/r.totalMemory();
System.out.println("Mem usage: " + memoryUsage + "%");
Thread.sleep(1000);
}
所以,是的这就是经验,也许它只是正常工作在最近的Linux系统,也许它只是与特定的工作量运气......但我开始认为这是对一些系统和有效的解决方案工作量,它可以是有用的。
文件大小是否固定?还是需要根据密钥增长?我会简单地使用'MappedByteBuffer'来进行写入操作。如果文件太大或需要增长,我会将其封装在一个映射为“块”的类中,然后在您写入时将块移动。此算法相当简单..只需选择一个对您正在编写的数据有意义的块大小即可。 – Nim
该文件的大小并未提前知道。该文件可能位于网络驱动器上 - 我不确定这是否会影响您的解决方案 – rghome
查看'java.nio.channels'。你可以用'FileChannel'进行随机访问,并写入缓冲数据。 – teppic