如何使用滚动缓冲区附加到 dexie 条目(在不分配 GB 内存的情况下存储大条目)
How to append to dexie entry using a rolling buffer (to store large entries without allocating GBs of memory)
我在给 Dexie (David Fahlander) 的作者发邮件后被重定向到这里。这是我的问题:
有没有办法附加到现有的 Dexie 条目?我需要在 dexie 中存储大的东西,但我希望能够用滚动缓冲区填充大条目,而不是分配一个巨大的缓冲区然后进行存储。
例如,我有一个2gb的文件要存储在dexie中。我想通过一次将 32kb 存储到同一个存储中来存储该文件,而不必在浏览器中分配 2gb 的内存。有没有办法做到这一点? put 方法似乎只覆盖条目。
感谢您在 Whosebug 上提出问题 :) 这有助于我建立一个开放的知识库供所有人访问。
在不实例化整个条目的情况下,IndexedDB 无法更新条目。 Dexie 添加了 update() 和 modify() 方法,但它们只是模拟了一种改变某些属性的方法。在后台,整个文档总是会暂时加载到内存中。
IndexedDB 也有 Blob 支持,但是当一个 Blob 存储到 IndexedDB 时,它的全部内容是 cloned/copied 到数据库中的规范。
因此,处理此问题的最佳方法是为动态大型内容专用 table 并向其中添加新条目。
例如,假设您有 table、"files" 和 "fileChunks"。您需要逐渐增加 "file",并且每次这样做时,您都不想在内存中实例化整个文件。然后,您可以将文件块作为单独的条目添加到 fileChunks table.
let db = new Dexie('filedb');
db.version(1).stores({
files: '++id, name',
fileChunks: '++id, fileId'
});
/** Returns a Promise with ID of the created file */
function createFile (name) {
return db.files.add({name});
}
/** Appends contents to the file */
function appendFileContent (fileId, contentToAppend) {
return db.fileChunks.add ({fileId, chunk: contentToAppend});
}
/** Read entire file */
function readEntireFile (fileId) {
return db.fileChunks.where('fileId').equals(fileId).toArray()
.then(entries => {
return entries.map(entry=>entry.chunk)
.join(''); // join = Assume chunks are strings
});
}
很简单。如果您希望 appendFileContent 成为滚动缓冲区(具有最大大小并删除旧内容),您可以添加截断方法:
function deleteOldChunks (fileId, maxAllowedChunks) {
return db.fileChunks.where('fileId').equals(fileId);
.reverse() // Important, so that we delete old chunks
.offset(maxAllowedChunks) // offset = skip
.delete(); // Deletes all records older before N last records
}
您还会获得其他好处,例如无需将其全部内容加载到内存中即可跟踪存储文件的能力:
/** Tail a file. This function only shows an example on how
* dynamic the data is stored and that file tailing would be
* simple to do. */
function tailFile (fileId, maxLines) {
let result = [], numNewlines = 0;
return db.fileChunks.where('fileId').equals(fileId)
.reverse()
.until(() => numNewLines >= maxLines)
.each(entry => {
result.unshift(entry.chunk);
numNewlines += (entry.chunk.match(/\n/g) || []).length;
})
.then (()=> {
let lines = result.join('').split('\n')
.slice(1); // First line may be cut off
let overflowLines = lines.length - maxLines;
return (overflowLines > 0 ?
lines.slice(overflowLines) :
lines).join('\n');
});
}
我知道块将以正确的顺序出现在 readEntireFile() 和 tailFile() 中的原因是 indexedDB 查询将始终按照查询列的主要顺序检索,但次要顺序检索主键,它们是自动递增的数字。
此模式可用于其他情况,例如日志记录等。如果文件不是基于字符串的,您将不得不稍微更改此示例。具体来说,不要使用 string.join() 或 array.split()。
我在给 Dexie (David Fahlander) 的作者发邮件后被重定向到这里。这是我的问题:
有没有办法附加到现有的 Dexie 条目?我需要在 dexie 中存储大的东西,但我希望能够用滚动缓冲区填充大条目,而不是分配一个巨大的缓冲区然后进行存储。
例如,我有一个2gb的文件要存储在dexie中。我想通过一次将 32kb 存储到同一个存储中来存储该文件,而不必在浏览器中分配 2gb 的内存。有没有办法做到这一点? put 方法似乎只覆盖条目。
感谢您在 Whosebug 上提出问题 :) 这有助于我建立一个开放的知识库供所有人访问。
在不实例化整个条目的情况下,IndexedDB 无法更新条目。 Dexie 添加了 update() 和 modify() 方法,但它们只是模拟了一种改变某些属性的方法。在后台,整个文档总是会暂时加载到内存中。
IndexedDB 也有 Blob 支持,但是当一个 Blob 存储到 IndexedDB 时,它的全部内容是 cloned/copied 到数据库中的规范。
因此,处理此问题的最佳方法是为动态大型内容专用 table 并向其中添加新条目。
例如,假设您有 table、"files" 和 "fileChunks"。您需要逐渐增加 "file",并且每次这样做时,您都不想在内存中实例化整个文件。然后,您可以将文件块作为单独的条目添加到 fileChunks table.
let db = new Dexie('filedb');
db.version(1).stores({
files: '++id, name',
fileChunks: '++id, fileId'
});
/** Returns a Promise with ID of the created file */
function createFile (name) {
return db.files.add({name});
}
/** Appends contents to the file */
function appendFileContent (fileId, contentToAppend) {
return db.fileChunks.add ({fileId, chunk: contentToAppend});
}
/** Read entire file */
function readEntireFile (fileId) {
return db.fileChunks.where('fileId').equals(fileId).toArray()
.then(entries => {
return entries.map(entry=>entry.chunk)
.join(''); // join = Assume chunks are strings
});
}
很简单。如果您希望 appendFileContent 成为滚动缓冲区(具有最大大小并删除旧内容),您可以添加截断方法:
function deleteOldChunks (fileId, maxAllowedChunks) {
return db.fileChunks.where('fileId').equals(fileId);
.reverse() // Important, so that we delete old chunks
.offset(maxAllowedChunks) // offset = skip
.delete(); // Deletes all records older before N last records
}
您还会获得其他好处,例如无需将其全部内容加载到内存中即可跟踪存储文件的能力:
/** Tail a file. This function only shows an example on how
* dynamic the data is stored and that file tailing would be
* simple to do. */
function tailFile (fileId, maxLines) {
let result = [], numNewlines = 0;
return db.fileChunks.where('fileId').equals(fileId)
.reverse()
.until(() => numNewLines >= maxLines)
.each(entry => {
result.unshift(entry.chunk);
numNewlines += (entry.chunk.match(/\n/g) || []).length;
})
.then (()=> {
let lines = result.join('').split('\n')
.slice(1); // First line may be cut off
let overflowLines = lines.length - maxLines;
return (overflowLines > 0 ?
lines.slice(overflowLines) :
lines).join('\n');
});
}
我知道块将以正确的顺序出现在 readEntireFile() 和 tailFile() 中的原因是 indexedDB 查询将始终按照查询列的主要顺序检索,但次要顺序检索主键,它们是自动递增的数字。
此模式可用于其他情况,例如日志记录等。如果文件不是基于字符串的,您将不得不稍微更改此示例。具体来说,不要使用 string.join() 或 array.split()。