我想要一个对象来模拟(在纯 Java 中)大于整个虚拟内存(以 Tera 字节测量)的文件的内存映射。
/** interface to memory-mapped file emulator that can read/write to mapped file
* and return multiple views on it. */
public interface FileView {
/** returns single byte of mapped file at position (long index) */
byte getByte (long pos);
/** sets single byte of mapped file at position (long index) to a value */
void setByte (long pos, byte value);
/** returns ByteBuffer-like class exposing bytes of mapped file
* in specified range (from - to, long indices).
* Length of that buffer can be arbitrary limited (perhaps to 50MB) */
XxxByteBuffer getBuffer (long from, long to);
/** register new range of bytes that needs to be listened
* and in case of change, listener should get informed.
* RangeChangedListener takes two arguments in constructor: long from, long to */
void addRangeChangedListener(RangeChangedListener l);
}
我的问题(6)在大标题下方,这里是上下文信息。
每个返回的类似 ByteBufer 的类的实例应该彼此保持一致(在重叠的区域中具有相同的内容)。因此,如果其中一个缓冲区发生更改,其他缓冲区如果重叠,则应适当更改。此外,如果更改发生在侦听区域,则应通知侦听器(对于缓冲区,同步和侦听应该是单一机制。对于代理视图 - 同步没有问题,但侦听器问题仍然存在)。这种同步不必是“超级高性能”,但它应该是可靠的。我认为会有很多重叠的缓冲区,但不会有很多修改。我假设大多数时候我会从“FileView”中获得很少的缓冲区(比如说 2kb)。
每个单独的写入都应该以某种方式存储在内存中(除了写入缓冲区)。在用户单击“保存”之前,不允许直接写入文件。当然它不能持续很长时间——我假设没有必要在内存中缓存超过 200MB 的内存(可以在磁盘上的临时文件中缓存大量的更改)。这些单独的更改可以分布在整个映射文件中。如果文件中的相邻位置被修改,则可以聚合更改。
所以 FileView 必须:
- 缓冲内存中当前使用的数据。
- 将所有单独的更改(写入)存储在缓存中。
- 按需公开类似 ByteBuffer 的对象(通过从文件中读取新数据或从内存中获取现有数据)并对其应用适当的缓存更改(如果存在任何更改)。
- 当它们重叠时,将类似 ByteBuffer 的对象相互同步。
- 从内存中丢弃干净的数据(当前未使用且未更改)(以保留内存)
- 在当前不使用时安全地从内存中丢弃“脏数据”:要做到这一点,必须确保存储更改,然后像干净一样丢弃数据
- 监控当前使用的内存量与最大值相比,在内存不足的情况下 - 尝试尽可能多地丢弃。
有两个大的性能问题:
1. 获取新数据作为视图,而不是作为独立缓冲区
在以下调用之后:
fileView.getBuffer(0,200000000);
fileView.getBuffer(0,200000789);
我们有两个 200MB 数据块的独立副本。连续调用将产生相同数据的下一个副本。所以也许 fileView 应该返回现有内部缓冲区而不是新缓冲区的视图?但是然后 - 如何产生内部缓冲区?这将是“拼凑”。fileView 将根据需要在内部创建缓冲区,并返回以某种方式粘合和修补在一起的这些内部缓冲区的视图。看下面 - 现有的内部缓冲区在第四次调用中被重用(修补):
fileView.getBuffer(0,2); // {0,1,2}
fileView.getBuffer(4,5); // {4,5}
fileView.getBuffer(8,12); // {8,9,10,11,12}
fileView.getBuffer(0,16); // {0,1,2} {3} {4,5} {6,7} {8,9,10,11,12} {13,14,15,16}
2. 在不破坏视图的情况下摆脱内部数据(GC 它们)
当内存不足时,我们应该:
- GC 未更改的缓冲区(因为我们稍后可以再次从 HD 中读取它们);
- 将我们的更改(写入)存储在缓存和 GC 修改的缓冲区中。
这样我们就可以完全摆脱任何读取数据(但将我们的更改(写入)保存在单独的缓存中)。但...
有缓冲区视图返回给外部组件 - 这些视图引用了我们的内部缓冲区,因此我们不能 GC 内部缓冲区。而且我们不想破坏这些外部视图——它们应该保持原样——我们只想摆脱内部数据,然后按需填充它们(如代理)。
.
我正在寻找一种架构上良好的解决方案来解决上述情况。这正是我需要知道的:
.
如何获取新数据作为视图而不是独立缓冲区?我想要:
- 避免多个缓冲区中相同数据的多个独立副本,所以我想要视图
- 在 fileView.getBuffer (long from, long to) 之后;返回功能类似于 ByteBuffer 的东西
- 与裸 ByteBuffers 相比,不要过多地破坏性能也许我应该返回在 fileView 的内部缓冲区上实现代理视图并具有类似于 bytebuffer 的接口的全新类的实例?
什么数据结构适合跟踪已加载和未加载的内容?也许 TreeMap 加上叶子的 LinkedList 并在叶子内保留缓冲区?
如何在不破坏外部视图的情况下摆脱内部数据(GC)?
如何同步(=保证相等)重叠区域中返回的缓冲区/视图的内容?这个问题在视图的情况下消失了,因为每个视图都引用相同的底层结构。
如何有效地存储单个更改(写入),然后在从文件重新加载数据时将它们重新应用到新返回的缓冲区/视图?每一次更改都可以是一个字节长,但也可以是几兆字节长。
如何有效地将选定的字节范围映射到适当的侦听器(单个侦听器侦听定义的字节范围的变化——而不是整个缓冲区)?
.
对于那些仍在阅读的人,我可以提供一些关于我在做什么的额外细节:)
我对缓存几乎一无所知,对缓冲区知之甚少,但是......
我尝试创建一个内部带有脚本引擎 (DSL) 和 GUI-to-data binding 的十六进制编辑器。目的是数据分析和恢复。我选择的语言是 Java + Groovy。项目的某些部分现在处于后期阶段,它们基于 ByteBuffers、CharBuffers 等。
我的应用程序有以下业务限制:
- 该应用程序可以打开超大文件(高清备份文件) - 比如说 1-2TB(Tera Bytes);
- 用户可以看到打开的文件,滚动并修改(就像在任何十六进制编辑器中一样);
- 脚本引擎可以随机访问文件并对其进行处理(读/写/绑定);
- 数据可以通过脚本绑定到 GUI,即字节 100000045 到 100000048 可以被视为 INTEGER 并显示在 JTextField 中。当这些字节发生变化时 - JTextField 也会发生变化。当有人更改文本字段时 - 基础数据(在文件中)会更改。
- 文件应该保持不变,直到我们点击保存。
脚本将严格在打开文件的上下文中运行,其主要任务是 (a) 读取和写入该文件 (b) 打印有关该文件的信息 (c) 将文件区域绑定到 GUI 中的字段。
我怀疑会有两种需求(脚本类型但有些模糊):
绝对脚本。可能需要整个文件和绝对寻址的脚本——它们可以寻找一些东西,即查找分区表并检查分区。我认为他们不会按顺序处理文件(如流)。相反,他们应该需要访问分散在整个文件中的几个地方,但这些地方不会很大——我怀疑大约 50MB。
相对脚本。通常在本地工作的脚本 - 它们可以将某些字节((由用户直接用鼠标或其他脚本指向))解释为具有已知布局的数据记录,并在 GUI 中动态创建字段并将数据绑定到该字段。
有过多的文件类型具有部分静态布局和一些磁盘数据结构。例如 NTFS 引导扇区在其开始附近具有静态布局:
char[8] - "File system ID"
uint16 - "Bytes per sector"
uint8 - "Sectors per cluster"
uint16 - "Reserved sectors"
...
“静态”是指您可以从文件读取字节到字节缓冲区,然后盲目地将前 8 个字节解释为描述文件系统 ID 的字符,然后将接下来的两个字节解释为无符号整数,指示每个扇区值的字节数,依此类推。所以你可以创建文本字段,用标签描述它们并在里面显示数据。