1) .mat(BIG_MAT.mat 1.29GB / 5 信号 x 16,142,471 采样)
旧链路同时驻留三份内存:整文件 fread 进 vector、整个数据集解压成字节数组、再 memcpy 成
double;外加 streams 缓存与每条信号各一份时间列拷贝。实测私有内存 +1233.9MB、117s。
现改为:
- MatHdf5::Open 用 CreateFileW + CreateFileMappingW + MapViewOfFile(MappedBytes 只读视图,
解析代码零改动;虚拟地址不等于物理内存,未访问页不驻留);
- 新增 ReadDatasetInfo(只读元信息)+ StreamDataset(ByteSink)(连续布局 4MB 切片、
分块布局逐 chunk inflate)+ ReadDoubleStream(SDI #data# 字节流按 8 字节一组直接写 double,
7 字节尾巴处理非 8 对齐 chunk);ReadNumbers 改为流式转换;
- MatLoader 先收集信号清单,再按(分组,时间流)合并为一个 TableBlock:时间列按内容去重、
数据列直接 move;进度改成 0-0.02 映射 / 0.02-0.98 逐流解码 / 1.0。
结果:117.3s -> 12.5s,私有内存 +1233.9MB -> +740.4MB(最终列数据 738.9MB)。
关键性能修正:SDI 的 #data# 是 dims=[N]、chunk=[N,1],归一化后每行只有 1 字节;旧的逐行回调
(且每行分配 coordinate 向量)只有 11MB/s。把目标连续的行合并成一次回调后约 110MB/s。
顺带把 inflate 的 Huffman 解码改成 9 位快查表,并抽到新的 Deflate.hpp/.cpp 供 MF4 复用。
2) MDF4 数据块
Problem-MF4.mf4 的第二个数据组把记录写在 ##DL(数据列表,两个 ##DT:131040+504 字节)
里,旧实现只认 ##DT,整组被跳过,于是 8 个信号只读出 4 个。
新增 CollectDataSegments(递归展开 ##DL / ##HL,叶子识别 ##DT / ##DV / ##DZ)与
ReadRecordSegments(跨段拼接、压缩段先 inflate、非压缩段仍按 4MB 分块读),
压缩段的记录长度改用 max(cg_record_id_len*8, 各通道 bitOffset+bitCount) 推算(载荷是压缩长度)。
实测 8 个信号:gVP_WindowLearnSt[0] 94 行、gVR_WinOpenReq[0] 9396 行,时间列单调。
##SD(已排序数据)仍未实现,这类数据组会被跳过。
另按要求只做检查、不改代码:该文件中 gVP_WindowLearnSt[0] 与 gVR_WinOpenReq[0] 都存在
值枚举文本(##CC,cc_type=7;links[4+i] 对应 cc_val[i]),清单已写入 Documents/agent_mem.md。
3) 光标数据点标记
光标的时间读数与差值胶囊绘制在绘图区底部、且在取值标注之后绘制,靠近底部的数据点圆点
会被完全盖住。新增 MarkerCoveredByCursorLabel(圆心落入面板或重叠面积达到半个圆)
与 CoverHiddenMarker(改成向下的三角、锚点放到面板上沿上方),在原始信号与派生曲线胶囊
两处调用,语义与“数据点在视界下方”一致。
验证(全部离屏:SW_HIDE 窗口 + 只注入合成消息 + BELOW_NORMAL,不占用鼠标键盘屏幕):
- 新增 .preflight/round4check 34/34;A/B:临时禁用 ##DL 支持 => 2 项如实失败;
临时禁用 CoverHiddenMarker => 3 项如实失败(含像素断言为 0);
- 回归:.preflight/round3check 31/31、editcheck 20/20、check 137/137;
- make release / make debug 零警告;Build/Release/Target.exe 785,920 字节(< 800KB)。
1192 lines
36 KiB
C++
1192 lines
36 KiB
C++
// HDF5 只读子集:MATLAB 7.3 `.mat` 解析用(见 MatH5.hpp 的说明)。
|
||
#include "MatH5.hpp"
|
||
|
||
#include "Deflate.hpp"
|
||
|
||
#include <windows.h>
|
||
|
||
#include <algorithm>
|
||
#include <cstdio>
|
||
#include <cstring>
|
||
|
||
namespace jdi
|
||
{
|
||
|
||
namespace
|
||
{
|
||
constexpr std::uint16_t kMessageDataspace = 0x0001;
|
||
constexpr std::uint16_t kMessageLink = 0x0006;
|
||
constexpr std::uint16_t kMessageDatatype = 0x0003;
|
||
constexpr std::uint16_t kMessageLayout = 0x0008;
|
||
constexpr std::uint16_t kMessageFilter = 0x000B;
|
||
constexpr std::uint16_t kMessageAttribute = 0x000C;
|
||
constexpr std::uint16_t kMessageContinuation = 0x0010;
|
||
constexpr std::uint16_t kMessageSymbolTable = 0x0011;
|
||
|
||
std::uint64_t ReadLE(const std::uint8_t* data, std::size_t size)
|
||
{
|
||
std::uint64_t value = 0;
|
||
for (std::size_t index = 0; index < size; ++index)
|
||
{
|
||
value |= static_cast<std::uint64_t>(data[index]) << (8 * index);
|
||
}
|
||
return value;
|
||
}
|
||
|
||
/** @brief 连续布局的读取切片大小(映射视图下只是 memcpy,不会产生额外峰值)。 */
|
||
constexpr std::size_t kStreamSliceBytes = 4u << 20;
|
||
|
||
/** @brief 把一段原始字节按元素类型转换成 double(供流式解码使用)。 */
|
||
void ConvertNumbers(const std::uint8_t* source, std::size_t count, std::size_t elementSize,
|
||
bool integer, bool floating, bool isSigned, double* out)
|
||
{
|
||
for (std::size_t index = 0; index < count; ++index)
|
||
{
|
||
const std::uint8_t* bytes = source + index * elementSize;
|
||
if (floating && elementSize == 8)
|
||
{
|
||
double value = 0.0;
|
||
std::memcpy(&value, bytes, sizeof(value));
|
||
out[index] = value;
|
||
}
|
||
else if (floating && elementSize == 4)
|
||
{
|
||
float value = 0.0f;
|
||
std::memcpy(&value, bytes, sizeof(value));
|
||
out[index] = static_cast<double>(value);
|
||
}
|
||
else if (integer && isSigned)
|
||
{
|
||
std::int64_t value = 0;
|
||
for (std::size_t byte = 0; byte < elementSize; ++byte)
|
||
{
|
||
value |= static_cast<std::int64_t>(bytes[byte]) << (8 * byte);
|
||
}
|
||
const int bits = static_cast<int>(elementSize) * 8;
|
||
if (bits < 64 && (value & (1ll << (bits - 1))) != 0)
|
||
{
|
||
value |= ~((1ll << bits) - 1);
|
||
}
|
||
out[index] = static_cast<double>(value);
|
||
}
|
||
else
|
||
{
|
||
std::uint64_t value = 0;
|
||
for (std::size_t byte = 0; byte < elementSize; ++byte)
|
||
{
|
||
value |= static_cast<std::uint64_t>(bytes[byte]) << (8 * byte);
|
||
}
|
||
out[index] = static_cast<double>(value);
|
||
}
|
||
}
|
||
}
|
||
} // namespace
|
||
|
||
std::uint8_t MatHdf5::U8(std::uint64_t offset) const
|
||
{
|
||
return (offset < m_data.size()) ? m_data[static_cast<std::size_t>(offset)] : 0;
|
||
}
|
||
|
||
std::uint16_t MatHdf5::U16(std::uint64_t offset) const
|
||
{
|
||
return static_cast<std::uint16_t>(U8(offset)) | (static_cast<std::uint16_t>(U8(offset + 1)) << 8);
|
||
}
|
||
|
||
std::uint32_t MatHdf5::U32(std::uint64_t offset) const
|
||
{
|
||
return static_cast<std::uint32_t>(U16(offset)) | (static_cast<std::uint32_t>(U16(offset + 2)) << 16);
|
||
}
|
||
|
||
std::uint64_t MatHdf5::U64(std::uint64_t offset) const
|
||
{
|
||
return static_cast<std::uint64_t>(U32(offset)) | (static_cast<std::uint64_t>(U32(offset + 4)) << 32);
|
||
}
|
||
|
||
std::uint64_t MatHdf5::Addr(std::uint64_t offset) const
|
||
{
|
||
return (m_offsetSize == 8) ? U64(offset) : U32(offset);
|
||
}
|
||
|
||
std::uint64_t MatHdf5::Resolve(std::uint64_t address) const
|
||
{
|
||
if (address == 0 || address == 0xFFFFFFFFFFFFFFFFull)
|
||
{
|
||
return 0;
|
||
}
|
||
return address + m_base;
|
||
}
|
||
|
||
bool MatHdf5::Open(const std::wstring& path, std::wstring& error, const OpenProgress& progress)
|
||
{
|
||
error.clear();
|
||
Close();
|
||
|
||
// ⚠️ **内存映射而不是整文件读入**:SDI 导出的 `.mat` 可能是 GB 级,
|
||
// “整文件读入 + 分块解码成字节数组 + 再拷成 double”会同时驻留三份内存
|
||
//(用户 2026-10-19 报告,并在本轮改为流式填充)。
|
||
// 映射只占虚拟地址空间:未被解析到的页不会驻留,系统也可以在内存压力下回收。
|
||
const HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
|
||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||
if (file == INVALID_HANDLE_VALUE)
|
||
{
|
||
error = L"无法打开文件。";
|
||
return false;
|
||
}
|
||
LARGE_INTEGER fileSize = {};
|
||
if (!GetFileSizeEx(file, &fileSize) || fileSize.QuadPart <= 0)
|
||
{
|
||
CloseHandle(file);
|
||
error = L"无法读取文件大小。";
|
||
return false;
|
||
}
|
||
const HANDLE mapping = CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, 0, nullptr);
|
||
if (mapping == nullptr)
|
||
{
|
||
CloseHandle(file);
|
||
error = L"无法映射文件(请确认文件可读且未被独占)。";
|
||
return false;
|
||
}
|
||
const void* view = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
|
||
if (view == nullptr)
|
||
{
|
||
CloseHandle(mapping);
|
||
CloseHandle(file);
|
||
error = L"无法映射文件视图。";
|
||
return false;
|
||
}
|
||
m_fileHandle = file;
|
||
m_mapHandle = mapping;
|
||
m_data.pointer = static_cast<const std::uint8_t*>(view);
|
||
m_data.length = static_cast<std::size_t>(fileSize.QuadPart);
|
||
|
||
// 字节已全部可访问(映射完成):把“已加载 = 总大小”报给遮罩层,
|
||
// 之后的时间主要在“解码样本流”阶段(由 MatLoader 逐流推进)。
|
||
if (progress && !progress(m_data.size(), m_data.size()))
|
||
{
|
||
Close();
|
||
error.clear(); // 用户取消:不显示错误
|
||
return false;
|
||
}
|
||
|
||
// 超级块:签名可能出现在 0、512、1024…(MATLAB 用 512 字节 user block)。
|
||
std::size_t signature = m_data.size();
|
||
for (std::size_t index = 0; index + 8 <= m_data.size() && index <= 4096; index += 512)
|
||
{
|
||
if (m_data[index] == 0x89 && m_data[index + 1] == 'H' && m_data[index + 2] == 'D' &&
|
||
m_data[index + 3] == 'F')
|
||
{
|
||
signature = index;
|
||
break;
|
||
}
|
||
}
|
||
if (signature == m_data.size())
|
||
{
|
||
error = L"不是 HDF5(MATLAB 7.3)格式。";
|
||
return false;
|
||
}
|
||
const std::uint8_t version = U8(signature + 8);
|
||
if (version == 0 || version == 1)
|
||
{
|
||
m_offsetSize = U8(signature + 13);
|
||
m_lengthSize = U8(signature + 14);
|
||
m_base = U64(signature + 24);
|
||
const std::uint64_t rootEntry = signature + 24 + 32;
|
||
m_root = Resolve(Addr(rootEntry + 8));
|
||
}
|
||
else
|
||
{
|
||
m_offsetSize = U8(signature + 9);
|
||
m_lengthSize = U8(signature + 10);
|
||
m_base = U64(signature + 12);
|
||
m_root = Resolve(U64(signature + 36));
|
||
}
|
||
if (m_root == 0 || m_root >= m_data.size())
|
||
{
|
||
error = L"HDF5 超级块损坏。";
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
void MatHdf5::Close()
|
||
{
|
||
// 先解除视图,再关映射与文件句柄(顺序不能反)。
|
||
if (m_data.pointer != nullptr)
|
||
{
|
||
UnmapViewOfFile(m_data.pointer);
|
||
}
|
||
if (m_mapHandle != nullptr)
|
||
{
|
||
CloseHandle(static_cast<HANDLE>(m_mapHandle));
|
||
m_mapHandle = nullptr;
|
||
}
|
||
if (m_fileHandle != nullptr)
|
||
{
|
||
CloseHandle(static_cast<HANDLE>(m_fileHandle));
|
||
m_fileHandle = nullptr;
|
||
}
|
||
m_data.clear();
|
||
m_base = 0;
|
||
m_root = 0;
|
||
m_heaps.clear();
|
||
}
|
||
|
||
void MatHdf5::ReadMessages(std::uint64_t address, std::vector<Message>& messages) const
|
||
{
|
||
messages.clear();
|
||
if (address + 8 > m_data.size())
|
||
{
|
||
return;
|
||
}
|
||
if (U8(address) == 1)
|
||
{
|
||
const std::uint16_t count = U16(address + 2);
|
||
const std::uint32_t headerSize = U32(address + 8);
|
||
// 先收集消息块:主块(长度 = 对象头里的 headerSize)+ 若干续块(续块里没有对象头前缀)。
|
||
// ⚠️ **主块长度必须用 headerSize 限定**:消息数超过主块容量时,其余消息在续块里,
|
||
// 若在主块里继续按"消息数"往下读,就会读到块外的其它数据(字段全部解析失败)。
|
||
std::vector<std::pair<std::uint64_t, std::uint64_t>> blocks;
|
||
blocks.emplace_back(address + 16, headerSize);
|
||
std::size_t read = 0;
|
||
for (std::size_t blockIndex = 0; blockIndex < blocks.size(); ++blockIndex)
|
||
{
|
||
std::uint64_t offset = blocks[blockIndex].first;
|
||
const std::uint64_t end =
|
||
(blocks[blockIndex].second > 0) ? (offset + blocks[blockIndex].second) : 0;
|
||
while (read < count && offset + 8 <= m_data.size() && (end == 0 || offset + 8 <= end))
|
||
{
|
||
Message message;
|
||
message.type = U16(offset);
|
||
const std::uint16_t messageSize = U16(offset + 2);
|
||
offset += 8;
|
||
if (offset + messageSize > m_data.size())
|
||
{
|
||
break;
|
||
}
|
||
message.data.assign(m_data.begin() + static_cast<std::ptrdiff_t>(offset),
|
||
m_data.begin() + static_cast<std::ptrdiff_t>(offset + messageSize));
|
||
offset += (messageSize + 7u) & ~static_cast<std::uint16_t>(7u);
|
||
++read;
|
||
if (message.type == kMessageContinuation && message.data.size() >= 16)
|
||
{
|
||
const std::uint64_t blockAddress = Resolve(ReadLE(message.data.data(), 8));
|
||
const std::uint64_t blockSize = ReadLE(message.data.data() + 8, 8);
|
||
if (blockAddress != 0)
|
||
{
|
||
blocks.emplace_back(blockAddress, blockSize);
|
||
}
|
||
}
|
||
messages.push_back(std::move(message));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void MatHdf5::WalkBtree(std::uint64_t node, std::uint64_t heapData,
|
||
std::vector<std::pair<std::string, std::uint64_t>>& out) const
|
||
{
|
||
int guard = 0;
|
||
while (node != 0 && node + 24 < m_data.size() && guard++ < 100000)
|
||
{
|
||
if (std::memcmp(&m_data[static_cast<std::size_t>(node)], "TREE", 4) != 0)
|
||
{
|
||
return;
|
||
}
|
||
const std::uint8_t level = U8(node + 5);
|
||
const std::uint16_t entries = U16(node + 6);
|
||
std::uint64_t offset = node + 8 + 8 + 8;
|
||
std::vector<std::uint64_t> children;
|
||
for (std::uint16_t index = 0; index < static_cast<std::uint16_t>(entries + 1u); ++index)
|
||
{
|
||
offset += 8; // key(分组 B-tree 的 key 是堆偏移)
|
||
if (index < entries)
|
||
{
|
||
children.push_back(Resolve(U64(offset)));
|
||
offset += 8;
|
||
}
|
||
}
|
||
for (const std::uint64_t child : children)
|
||
{
|
||
if (level > 0)
|
||
{
|
||
WalkBtree(child, heapData, out);
|
||
}
|
||
else
|
||
{
|
||
WalkSymbolNode(child, heapData, out);
|
||
}
|
||
}
|
||
node = Resolve(U64(node + 8)); // 右兄弟
|
||
}
|
||
}
|
||
|
||
void MatHdf5::WalkSymbolNode(std::uint64_t node, std::uint64_t heapData,
|
||
std::vector<std::pair<std::string, std::uint64_t>>& out) const
|
||
{
|
||
if (node == 0 || node + 8 > m_data.size() ||
|
||
std::memcmp(&m_data[static_cast<std::size_t>(node)], "SNOD", 4) != 0 || heapData == 0)
|
||
{
|
||
return;
|
||
}
|
||
const std::uint16_t count = U16(node + 6);
|
||
std::uint64_t offset = node + 8;
|
||
for (std::uint16_t index = 0; index < count; ++index)
|
||
{
|
||
if (offset + 40 > m_data.size())
|
||
{
|
||
return;
|
||
}
|
||
const std::uint64_t nameOffset = U64(offset);
|
||
const std::uint64_t objectAddress = Resolve(U64(offset + 8));
|
||
offset += 40;
|
||
if (objectAddress == 0 || heapData + nameOffset >= m_data.size())
|
||
{
|
||
continue;
|
||
}
|
||
const char* name = reinterpret_cast<const char*>(m_data.data() + heapData + nameOffset);
|
||
out.emplace_back(std::string(name), objectAddress);
|
||
}
|
||
}
|
||
|
||
std::vector<std::pair<std::string, std::uint64_t>> MatHdf5::Members(std::uint64_t address) const
|
||
{
|
||
std::vector<std::pair<std::string, std::uint64_t>> out;
|
||
std::vector<Message> messages;
|
||
ReadMessages(address, messages);
|
||
for (const Message& message : messages)
|
||
{
|
||
if (message.type == kMessageSymbolTable && message.data.size() >= 16)
|
||
{
|
||
const std::uint64_t btree = Resolve(ReadLE(message.data.data(), 8));
|
||
const std::uint64_t heap = Resolve(ReadLE(message.data.data() + 8, 8));
|
||
std::uint64_t heapData = 0;
|
||
if (heap + 32 <= m_data.size() &&
|
||
std::memcmp(&m_data[static_cast<std::size_t>(heap)], "HEAP", 4) == 0)
|
||
{
|
||
heapData = Resolve(U64(heap + 24));
|
||
bool known = false;
|
||
for (const auto& entry : m_heaps)
|
||
{
|
||
if (entry.first == heap)
|
||
{
|
||
known = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!known)
|
||
{
|
||
m_heaps.emplace_back(heap, heapData);
|
||
}
|
||
}
|
||
WalkBtree(btree, heapData, out);
|
||
}
|
||
else if (message.type == kMessageLink && message.data.size() >= 8)
|
||
{
|
||
std::size_t offset = 2;
|
||
const std::uint8_t flags = message.data[1];
|
||
if (flags & 0x08)
|
||
{
|
||
offset += 8;
|
||
}
|
||
if (flags & 0x04)
|
||
{
|
||
offset += 8;
|
||
}
|
||
if (flags & 0x10)
|
||
{
|
||
++offset;
|
||
}
|
||
if (offset >= message.data.size())
|
||
{
|
||
continue;
|
||
}
|
||
const std::uint8_t lengthSize = message.data[offset++];
|
||
std::uint64_t nameLength = 0;
|
||
for (std::uint8_t byte = 0; byte < lengthSize && offset < message.data.size(); ++byte)
|
||
{
|
||
nameLength |= static_cast<std::uint64_t>(message.data[offset + byte]) << (8 * byte);
|
||
}
|
||
offset += lengthSize;
|
||
if (offset + nameLength + 8 > message.data.size())
|
||
{
|
||
continue;
|
||
}
|
||
std::string name(reinterpret_cast<const char*>(message.data.data() + offset),
|
||
static_cast<std::size_t>(nameLength));
|
||
offset += static_cast<std::size_t>(nameLength);
|
||
const std::uint64_t target = Resolve(ReadLE(message.data.data() + offset, 8));
|
||
out.emplace_back(std::move(name), target);
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
std::uint64_t MatHdf5::Member(std::uint64_t address, const std::string& name) const
|
||
{
|
||
for (const auto& entry : Members(address))
|
||
{
|
||
if (entry.first == name)
|
||
{
|
||
return entry.second;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
bool MatHdf5::IsDataset(std::uint64_t address) const
|
||
{
|
||
std::vector<Message> messages;
|
||
ReadMessages(address, messages);
|
||
for (const Message& message : messages)
|
||
{
|
||
if (message.type == kMessageDataspace)
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool MatHdf5::ParseType(const std::uint8_t* data, std::size_t size, std::size_t offset, Type& out,
|
||
std::size_t& consumed) const
|
||
{
|
||
if (offset + 8 > size)
|
||
{
|
||
return false;
|
||
}
|
||
out.version = data[offset] >> 4;
|
||
out.cls = static_cast<TypeClass>(data[offset] & 0x0F);
|
||
const std::uint8_t bits0 = data[offset + 1];
|
||
out.size = ReadLE(data + offset + 4, 4);
|
||
std::size_t cursor = offset + 8;
|
||
if (out.cls == TypeClass::Integer)
|
||
{
|
||
out.bigEndian = (bits0 & 0x01) != 0;
|
||
out.isSigned = (bits0 & 0x08) != 0;
|
||
if (out.version >= 3)
|
||
{
|
||
cursor += 2; // bit offset
|
||
cursor += 2; // precision
|
||
}
|
||
else
|
||
{
|
||
cursor += 4;
|
||
}
|
||
}
|
||
else if (out.cls == TypeClass::Float)
|
||
{
|
||
out.bigEndian = (bits0 & 0x01) != 0;
|
||
cursor += 12;
|
||
}
|
||
else if (out.cls == TypeClass::String)
|
||
{
|
||
// 无需额外属性
|
||
}
|
||
else if (out.cls == TypeClass::Reference)
|
||
{
|
||
// 无需额外属性
|
||
}
|
||
else if (out.cls == TypeClass::VariableLength)
|
||
{
|
||
out.isVariableString = (bits0 & 0x0F) == 1;
|
||
std::size_t childConsumed = 0;
|
||
Type child;
|
||
if (ParseType(data, size, cursor, child, childConsumed))
|
||
{
|
||
cursor += childConsumed;
|
||
}
|
||
}
|
||
else if (out.cls == TypeClass::Compound)
|
||
{
|
||
const unsigned members = static_cast<unsigned>(bits0) | (static_cast<unsigned>(data[offset + 2]) << 8);
|
||
for (unsigned index = 0; index < members; ++index)
|
||
{
|
||
if (cursor >= size)
|
||
{
|
||
break;
|
||
}
|
||
const std::size_t nameStart = cursor;
|
||
while (cursor < size && data[cursor] != 0)
|
||
{
|
||
++cursor;
|
||
}
|
||
std::string name(reinterpret_cast<const char*>(data + nameStart), cursor - nameStart);
|
||
++cursor; // 结束符
|
||
cursor = (cursor + 7) & ~static_cast<std::size_t>(7);
|
||
if (out.version == 3)
|
||
{
|
||
// v3:先维度(1 字节 rank + 4 字节保留),再字节偏移(可变)
|
||
}
|
||
cursor += 4; // 字节偏移(v1/v2)
|
||
std::size_t childConsumed = 0;
|
||
Type child;
|
||
if (ParseType(data, size, cursor, child, childConsumed))
|
||
{
|
||
cursor += childConsumed;
|
||
out.members.emplace_back(std::move(name), std::move(child));
|
||
}
|
||
else
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
else if (out.cls == TypeClass::Array)
|
||
{
|
||
if (cursor + 4 <= size)
|
||
{
|
||
const std::uint8_t rank = data[cursor];
|
||
cursor += 4;
|
||
cursor += static_cast<std::size_t>(rank) * 4; // 维度
|
||
cursor += static_cast<std::size_t>(rank) * 4; // 排列
|
||
std::size_t childConsumed = 0;
|
||
Type child;
|
||
if (ParseType(data, size, cursor, child, childConsumed))
|
||
{
|
||
cursor += childConsumed;
|
||
}
|
||
}
|
||
}
|
||
consumed = cursor - offset;
|
||
return true;
|
||
}
|
||
|
||
bool MatHdf5::ReadDatasetInfo(std::uint64_t address, Dataset& out) const
|
||
{
|
||
out = Dataset();
|
||
std::vector<Message> messages;
|
||
ReadMessages(address, messages);
|
||
bool haveSpace = false;
|
||
bool haveType = false;
|
||
std::size_t compactSize = 0;
|
||
std::uint64_t dataAddress = 0;
|
||
std::uint64_t dataSize = 0;
|
||
for (const Message& message : messages)
|
||
{
|
||
if (message.type == kMessageDataspace && message.data.size() >= 8)
|
||
{
|
||
const std::uint8_t version = message.data[0];
|
||
const std::uint8_t rank = message.data[1];
|
||
std::size_t offset = (version == 1) ? 8 : 4;
|
||
out.dims.assign(rank, 0);
|
||
for (std::uint8_t axis = 0; axis < rank && offset + 8 <= message.data.size(); ++axis)
|
||
{
|
||
out.dims[axis] = static_cast<std::size_t>(ReadLE(&message.data[offset], 8));
|
||
offset += 8;
|
||
}
|
||
haveSpace = true;
|
||
}
|
||
else if (message.type == kMessageDatatype)
|
||
{
|
||
std::size_t consumed = 0;
|
||
haveType = ParseType(message.data.data(), message.data.size(), 0, out.type, consumed);
|
||
}
|
||
else if (message.type == kMessageLayout && message.data.size() >= 8)
|
||
{
|
||
const std::uint8_t version = message.data[0];
|
||
const std::uint8_t klass = message.data[1];
|
||
out.layout = static_cast<LayoutClass>(klass);
|
||
if (klass == 0)
|
||
{
|
||
// 紧凑:数据就放在消息里
|
||
if (version == 3)
|
||
{
|
||
compactSize = static_cast<std::size_t>(message.data[2]) |
|
||
(static_cast<std::size_t>(message.data[3]) << 8);
|
||
}
|
||
else
|
||
{
|
||
compactSize = static_cast<std::size_t>(ReadLE(&message.data[2], 4));
|
||
}
|
||
}
|
||
else if (klass == 1)
|
||
{
|
||
dataAddress = Resolve(ReadLE(&message.data[2], 8));
|
||
dataSize = ReadLE(&message.data[10], 8);
|
||
}
|
||
else if (klass == 2)
|
||
{
|
||
if (version == 3)
|
||
{
|
||
const std::uint8_t rank = message.data[2];
|
||
out.chunkAddress = Resolve(ReadLE(&message.data[3], 8));
|
||
out.chunkDims.assign(rank, 0);
|
||
for (std::uint8_t axis = 0; axis < rank; ++axis)
|
||
{
|
||
out.chunkDims[axis] = ReadLE(&message.data[11 + axis * 4], 4);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
const std::uint8_t rank = message.data[2];
|
||
out.chunkAddress = Resolve(ReadLE(&message.data[3], 8));
|
||
out.chunkDims.assign(rank, 0);
|
||
for (std::uint8_t axis = 0; axis < rank; ++axis)
|
||
{
|
||
out.chunkDims[axis] = ReadLE(&message.data[11 + axis * 4], 4);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else if (message.type == kMessageFilter && message.data.size() >= 2)
|
||
{
|
||
const int count = message.data[1];
|
||
std::size_t offset = 8;
|
||
for (int index = 0; index < count && offset + 8 <= message.data.size(); ++index)
|
||
{
|
||
const std::uint16_t id = static_cast<std::uint16_t>(message.data[offset]) |
|
||
(static_cast<std::uint16_t>(message.data[offset + 1]) << 8);
|
||
const std::uint16_t nameLength = static_cast<std::uint16_t>(message.data[offset + 2]) |
|
||
(static_cast<std::uint16_t>(message.data[offset + 3]) << 8);
|
||
const std::uint16_t values =
|
||
static_cast<std::uint16_t>(message.data[offset + 6]) |
|
||
(static_cast<std::uint16_t>(message.data[offset + 7]) << 8);
|
||
offset += 8;
|
||
offset += (nameLength + 7) & ~static_cast<std::size_t>(7);
|
||
offset += static_cast<std::size_t>(values) * 4;
|
||
if (id == 1)
|
||
{
|
||
out.deflate = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!haveSpace || !haveType || out.type.size == 0)
|
||
{
|
||
return false;
|
||
}
|
||
out.dataAddress = dataAddress;
|
||
out.dataBytes = dataSize;
|
||
if (out.layout == LayoutClass::Compact)
|
||
{
|
||
// 紧凑数据就在布局消息里(极小),保留一份以便流式遍历时吐出。
|
||
for (const Message& message : messages)
|
||
{
|
||
if (message.type == kMessageLayout)
|
||
{
|
||
const std::size_t offset = (message.data[0] == 3) ? 4 : 8;
|
||
if (offset + compactSize <= message.data.size())
|
||
{
|
||
out.compact.assign(message.data.begin() + static_cast<std::ptrdiff_t>(offset),
|
||
message.data.begin() +
|
||
static_cast<std::ptrdiff_t>(offset + compactSize));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
out.ok = true;
|
||
return true;
|
||
}
|
||
|
||
bool MatHdf5::ReadDataset(std::uint64_t address, Dataset& out) const
|
||
{
|
||
if (!ReadDatasetInfo(address, out))
|
||
{
|
||
return false;
|
||
}
|
||
const std::size_t elementSize = out.type.size;
|
||
const std::uint64_t total = out.ElementCount();
|
||
if (total == 0 || total > static_cast<std::uint64_t>(SIZE_MAX) / elementSize)
|
||
{
|
||
return false;
|
||
}
|
||
out.data.assign(static_cast<std::size_t>(total) * elementSize, 0);
|
||
const bool ok = StreamDataset(
|
||
out, [&out, elementSize](const std::uint8_t* bytes, std::size_t byteCount,
|
||
std::uint64_t elementOffset) -> bool
|
||
{
|
||
const std::size_t target = static_cast<std::size_t>(elementOffset) * elementSize;
|
||
if (target + byteCount > out.data.size())
|
||
{
|
||
return false;
|
||
}
|
||
std::memcpy(out.data.data() + target, bytes, byteCount);
|
||
return true;
|
||
});
|
||
return ok && out.ok;
|
||
}
|
||
|
||
bool MatHdf5::StreamDataset(const Dataset& dataset, const ByteSink& sink) const
|
||
{
|
||
if (!dataset.ok || !sink)
|
||
{
|
||
return false;
|
||
}
|
||
const std::size_t elementSize = dataset.type.size;
|
||
if (elementSize == 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// 紧凑布局:数据就在消息里(很小)。
|
||
if (dataset.layout == LayoutClass::Compact)
|
||
{
|
||
return sink(dataset.compact.data(), dataset.compact.size(), 0);
|
||
}
|
||
|
||
// 连续布局:按 4MB 切片直接吐出(文件是映射的,这里只是指针运算,不产生额外缓冲)。
|
||
if (dataset.layout == LayoutClass::Contiguous)
|
||
{
|
||
const std::uint64_t total = dataset.ElementCount();
|
||
const std::uint64_t totalBytes = total * elementSize;
|
||
const std::uint64_t available =
|
||
std::min<std::uint64_t>(dataset.dataBytes,
|
||
(dataset.dataAddress < m_data.size())
|
||
? (m_data.size() - dataset.dataAddress)
|
||
: 0);
|
||
const std::uint64_t bytes = std::min(totalBytes, available);
|
||
std::size_t slice = kStreamSliceBytes - (kStreamSliceBytes % elementSize);
|
||
if (slice == 0)
|
||
{
|
||
slice = elementSize;
|
||
}
|
||
for (std::uint64_t offset = 0; offset < bytes; offset += slice)
|
||
{
|
||
const std::size_t count = static_cast<std::size_t>(std::min<std::uint64_t>(slice, bytes - offset));
|
||
if (!sink(m_data.data() + dataset.dataAddress + offset, count, offset / elementSize))
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// 分块布局:逐 chunk 解压,立刻把每一行交给回调(不保留整块字节数组)。
|
||
if (dataset.chunkAddress == 0 || dataset.chunkDims.empty() || dataset.dims.empty())
|
||
{
|
||
return false;
|
||
}
|
||
// ⚠️ MATLAB 写出的分块维度可能与数据空间维度**维数不一致**(实测:1 维数据集的
|
||
// 分块维度是 `[N, 1]`)。把维度数组在**右侧补 1**(行优先的最快轴)对齐即可。
|
||
std::vector<std::size_t> dims = dataset.dims;
|
||
std::vector<std::size_t> chunkDims = dataset.chunkDims;
|
||
while (dims.size() < chunkDims.size())
|
||
{
|
||
dims.push_back(1);
|
||
}
|
||
while (chunkDims.size() < dims.size())
|
||
{
|
||
chunkDims.push_back(1);
|
||
}
|
||
const std::size_t rank = dims.size();
|
||
std::size_t expected = elementSize;
|
||
for (const std::size_t dim : dataset.chunkDims)
|
||
{
|
||
expected *= dim;
|
||
}
|
||
const std::size_t rowBytes = chunkDims[rank - 1] * elementSize;
|
||
std::size_t rowsPerChunk = 1;
|
||
for (std::size_t axis = 0; axis + 1 < rank; ++axis)
|
||
{
|
||
rowsPerChunk *= chunkDims[axis];
|
||
}
|
||
|
||
std::vector<std::uint8_t> inflated;
|
||
std::vector<std::uint64_t> stack{dataset.chunkAddress};
|
||
int guard = 0;
|
||
while (!stack.empty() && guard++ < 1000000)
|
||
{
|
||
const std::uint64_t node = stack.back();
|
||
stack.pop_back();
|
||
if (node == 0 || node + 24 > m_data.size() ||
|
||
std::memcmp(&m_data[static_cast<std::size_t>(node)], "TREE", 4) != 0)
|
||
{
|
||
continue;
|
||
}
|
||
const std::uint8_t level = U8(node + 5);
|
||
const std::uint16_t entries = U16(node + 6);
|
||
const std::size_t keySize = 8 + 8 * rank;
|
||
std::uint64_t offset = node + 8 + 8 + 8;
|
||
for (std::uint16_t index = 0; index < static_cast<std::uint16_t>(entries + 1u); ++index)
|
||
{
|
||
const std::uint32_t chunkSize = (offset + 4 <= m_data.size()) ? U32(offset) : 0;
|
||
std::vector<std::uint64_t> chunkOffset(rank, 0);
|
||
for (std::size_t axis = 0; axis < rank; ++axis)
|
||
{
|
||
chunkOffset[axis] = U64(offset + 8 + axis * 8);
|
||
}
|
||
offset += keySize;
|
||
if (index >= entries)
|
||
{
|
||
continue;
|
||
}
|
||
const std::uint64_t child = Resolve(U64(offset));
|
||
offset += 8;
|
||
if (level > 0)
|
||
{
|
||
stack.push_back(child);
|
||
continue;
|
||
}
|
||
if (child == 0 || chunkSize == 0 || child + chunkSize > m_data.size())
|
||
{
|
||
continue;
|
||
}
|
||
|
||
const std::uint8_t* source = m_data.data() + child;
|
||
std::size_t sourceSize = chunkSize;
|
||
if (dataset.deflate && chunkSize != expected)
|
||
{
|
||
// 分块未压缩时大小 = 分块元素数 × 元素大小:此时不需要解压。
|
||
if (!InflateZlib(source, sourceSize, inflated, expected))
|
||
{
|
||
continue;
|
||
}
|
||
if (inflated.size() < expected)
|
||
{
|
||
inflated.resize(expected, 0);
|
||
}
|
||
source = inflated.data();
|
||
sourceSize = expected;
|
||
}
|
||
|
||
// 块内按行(最快轴一整行连续)散射到目标位置。
|
||
// ⚠️ 行数可能很大(例如 SDI 的 `#data#` 是 `dims=[N]`、chunk 为 `[N,1]`,
|
||
// 归一化后 rank=2 ⇒「每行只有 1 字节」)。若逐行回调,就是上千万次
|
||
// 小 memcpy + 上千万次 vector 分配,实测把解压吞吐拖到 ~11MB/s。
|
||
// 因此这里把**目标连续的行合并成一次回调**(典型情况整块只需 1 次)。
|
||
std::vector<std::size_t> coordinate(rank > 0 ? rank - 1 : 0, 0);
|
||
std::uint64_t runElement = 0; // 合并段的起始元素下标
|
||
std::size_t runSourceOffset = 0; // 合并段的源偏移
|
||
std::size_t runBytes = 0; // 合并段字节数
|
||
bool runOpen = false;
|
||
for (std::size_t row = 0; row < rowsPerChunk; ++row)
|
||
{
|
||
std::size_t remainder = row;
|
||
for (std::size_t axis = rank - 1; axis-- > 0;)
|
||
{
|
||
coordinate[axis] = remainder % chunkDims[axis];
|
||
remainder /= chunkDims[axis];
|
||
}
|
||
std::uint64_t targetRow = 0;
|
||
bool inside = true;
|
||
std::uint64_t stride = 1;
|
||
for (std::size_t axis = rank - 1; axis-- > 0;)
|
||
{
|
||
const std::uint64_t index = chunkOffset[axis] + coordinate[axis];
|
||
if (index >= dims[axis])
|
||
{
|
||
inside = false;
|
||
break;
|
||
}
|
||
targetRow += index * stride;
|
||
stride *= dims[axis];
|
||
}
|
||
if (!inside)
|
||
{
|
||
continue;
|
||
}
|
||
const std::size_t sourceOffset = row * rowBytes;
|
||
if (sourceOffset + rowBytes > sourceSize)
|
||
{
|
||
continue;
|
||
}
|
||
const std::uint64_t targetElement = targetRow * chunkDims[rank - 1];
|
||
if (runOpen && targetElement == runElement + (runBytes / elementSize))
|
||
{
|
||
runBytes += rowBytes;
|
||
continue;
|
||
}
|
||
if (runOpen)
|
||
{
|
||
if (!sink(source + runSourceOffset, runBytes, runElement))
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
runOpen = true;
|
||
runElement = targetElement;
|
||
runSourceOffset = sourceOffset;
|
||
runBytes = rowBytes;
|
||
}
|
||
if (runOpen)
|
||
{
|
||
if (!sink(source + runSourceOffset, runBytes, runElement))
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
|
||
std::string MatHdf5::AttributeString(std::uint64_t address, const char* name) const
|
||
{
|
||
std::vector<Message> messages;
|
||
ReadMessages(address, messages);
|
||
for (const Message& message : messages)
|
||
{
|
||
if (message.type != kMessageAttribute || message.data.size() < 8)
|
||
{
|
||
continue;
|
||
}
|
||
const std::uint8_t version = message.data[0];
|
||
const std::size_t headerSize = (version == 1) ? 8 : (version == 2 ? 8 : 9);
|
||
const std::uint16_t nameSize = static_cast<std::uint16_t>(message.data[2]) |
|
||
(static_cast<std::uint16_t>(message.data[3]) << 8);
|
||
const std::uint16_t typeSize = static_cast<std::uint16_t>(message.data[4]) |
|
||
(static_cast<std::uint16_t>(message.data[5]) << 8);
|
||
const std::uint16_t spaceSize = static_cast<std::uint16_t>(message.data[6]) |
|
||
(static_cast<std::uint16_t>(message.data[7]) << 8);
|
||
std::size_t offset = headerSize;
|
||
if (offset + nameSize > message.data.size())
|
||
{
|
||
continue;
|
||
}
|
||
std::string attributeName(reinterpret_cast<const char*>(message.data.data() + offset),
|
||
nameSize);
|
||
while (!attributeName.empty() && attributeName.back() == '\0')
|
||
{
|
||
attributeName.pop_back();
|
||
}
|
||
offset += (nameSize + 7) & ~static_cast<std::size_t>(7);
|
||
const std::size_t typeOffset = offset;
|
||
offset += (typeSize + 7) & ~static_cast<std::size_t>(7);
|
||
const std::size_t spaceOffset = offset;
|
||
offset += (spaceSize + 7) & ~static_cast<std::size_t>(7);
|
||
if (attributeName != name)
|
||
{
|
||
continue;
|
||
}
|
||
if (offset > message.data.size())
|
||
{
|
||
return std::string();
|
||
}
|
||
Type type;
|
||
std::size_t consumed = 0;
|
||
if (!ParseType(message.data.data(), message.data.size(), typeOffset, type, consumed))
|
||
{
|
||
return std::string();
|
||
}
|
||
std::size_t count = 1;
|
||
if (spaceOffset + 4 <= message.data.size())
|
||
{
|
||
const std::uint8_t spaceVersion = message.data[spaceOffset];
|
||
const std::uint8_t rank = message.data[spaceOffset + 1];
|
||
std::size_t dimOffset = spaceOffset + ((spaceVersion == 1) ? 8 : 4);
|
||
count = 1;
|
||
for (std::uint8_t axis = 0; axis < rank && dimOffset + 8 <= message.data.size(); ++axis)
|
||
{
|
||
count *= static_cast<std::size_t>(ReadLE(&message.data[dimOffset], 8));
|
||
dimOffset += 8;
|
||
}
|
||
}
|
||
std::string text;
|
||
if (type.cls == TypeClass::String || type.cls == TypeClass::Integer)
|
||
{
|
||
text.assign(reinterpret_cast<const char*>(message.data.data() + offset),
|
||
std::min(message.data.size() - offset, std::max<std::size_t>(count, 1)));
|
||
while (!text.empty() && text.back() == '\0')
|
||
{
|
||
text.pop_back();
|
||
}
|
||
}
|
||
return text;
|
||
}
|
||
return std::string();
|
||
}
|
||
|
||
bool MatHdf5::ReadText(std::uint64_t address, std::wstring& out) const
|
||
{
|
||
out.clear();
|
||
Dataset dataset;
|
||
if (!ReadDataset(address, dataset))
|
||
{
|
||
return false;
|
||
}
|
||
if (dataset.type.cls == TypeClass::String)
|
||
{
|
||
out.assign(dataset.data.begin(), dataset.data.end());
|
||
while (!out.empty() && out.back() == L'\0')
|
||
{
|
||
out.pop_back();
|
||
}
|
||
return true;
|
||
}
|
||
if (dataset.type.size == 2)
|
||
{
|
||
for (std::size_t index = 0; index + 1 < dataset.data.size(); index += 2)
|
||
{
|
||
const wchar_t character = static_cast<wchar_t>(
|
||
static_cast<std::uint16_t>(dataset.data[index]) |
|
||
(static_cast<std::uint16_t>(dataset.data[index + 1]) << 8));
|
||
if (character != 0)
|
||
{
|
||
out.push_back(character);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
if (dataset.type.size == 1)
|
||
{
|
||
for (const std::uint8_t byte : dataset.data)
|
||
{
|
||
if (byte != 0)
|
||
{
|
||
out.push_back(static_cast<wchar_t>(byte));
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool MatHdf5::ReadNumbers(std::uint64_t address, std::vector<double>& out) const
|
||
{
|
||
out.clear();
|
||
Dataset dataset;
|
||
if (!ReadDatasetInfo(address, dataset) || !dataset.ok)
|
||
{
|
||
return false;
|
||
}
|
||
const std::size_t elementSize = dataset.type.size;
|
||
const bool integer = (dataset.type.cls == TypeClass::Integer);
|
||
const bool floating = (dataset.type.cls == TypeClass::Float);
|
||
if (elementSize == 0 || (!integer && !floating))
|
||
{
|
||
return false;
|
||
}
|
||
const std::uint64_t total = dataset.ElementCount();
|
||
if (total == 0 || total > static_cast<std::uint64_t>(SIZE_MAX) / elementSize)
|
||
{
|
||
return false;
|
||
}
|
||
out.assign(static_cast<std::size_t>(total), 0.0);
|
||
|
||
// ⚠️ 流式转换:分块数据集逐 chunk 解压后**直接**写进 `out`,
|
||
// 不再先生成一份“整个数据集的未压缩字节”(GB 级数据下那份缓冲与 double 输出同样大)。
|
||
return StreamDataset(
|
||
dataset, [&out, elementSize, integer, floating, &dataset](const std::uint8_t* bytes,
|
||
std::size_t byteCount,
|
||
std::uint64_t elementOffset) -> bool
|
||
{
|
||
if (elementSize == 0 || byteCount % elementSize != 0)
|
||
{
|
||
return false;
|
||
}
|
||
const std::size_t count = byteCount / elementSize;
|
||
if (elementOffset + count > out.size())
|
||
{
|
||
return false;
|
||
}
|
||
ConvertNumbers(bytes, count, elementSize, integer, floating, dataset.type.isSigned,
|
||
out.data() + elementOffset);
|
||
return true;
|
||
});
|
||
}
|
||
|
||
bool MatHdf5::ReadDoubleStream(std::uint64_t address, std::vector<double>& out) const
|
||
{
|
||
out.clear();
|
||
Dataset dataset;
|
||
if (!ReadDatasetInfo(address, dataset) || !dataset.ok)
|
||
{
|
||
return false;
|
||
}
|
||
// SDI 的 `#data#` 必须是单字节元素(uint8 字节流),每 8 字节一组是一个 double。
|
||
if (dataset.type.size != 1)
|
||
{
|
||
return false;
|
||
}
|
||
const std::uint64_t totalBytes = dataset.ElementCount();
|
||
if (totalBytes == 0 || totalBytes % sizeof(double) != 0)
|
||
{
|
||
return false;
|
||
}
|
||
const std::size_t sampleCount = static_cast<std::size_t>(totalBytes / sizeof(double));
|
||
out.assign(sampleCount, 0.0);
|
||
|
||
// ⚠️ 分块边界不一定与 8 字节对齐(chunk 维度是任意整数)⇒ 用最多 7 字节的尾巴接续。
|
||
// 关键:**不做整块字节缓冲**,解压出来的字节立即被解释成 double 写进 `out`。
|
||
std::uint8_t tail[8] = {};
|
||
std::size_t tailBytes = 0;
|
||
std::uint64_t tailStart = 0; // 尾巴首字节在数据集中的字节偏移
|
||
bool failed = false;
|
||
const bool ok = StreamDataset(
|
||
dataset, [&](const std::uint8_t* bytes, std::size_t byteCount,
|
||
std::uint64_t elementOffset) -> bool
|
||
{
|
||
// 数据必须连续吐出(分块遍历按坐标升序;中间有空洞说明表里缺块)。
|
||
if (tailBytes > 0 && elementOffset != tailStart + tailBytes)
|
||
{
|
||
failed = true;
|
||
return false;
|
||
}
|
||
std::size_t position = 0;
|
||
if (tailBytes == 0)
|
||
{
|
||
tailStart = elementOffset;
|
||
}
|
||
// 先把尾巴补满一个 double。
|
||
while (tailBytes > 0 && tailBytes < sizeof(double) && position < byteCount)
|
||
{
|
||
tail[tailBytes++] = bytes[position++];
|
||
}
|
||
if (tailBytes == sizeof(double))
|
||
{
|
||
const std::size_t index = static_cast<std::size_t>(tailStart / sizeof(double));
|
||
if (index >= out.size())
|
||
{
|
||
failed = true;
|
||
return false;
|
||
}
|
||
std::memcpy(&out[index], tail, sizeof(double));
|
||
tailStart += sizeof(double);
|
||
tailBytes = 0;
|
||
}
|
||
// 剩余部分按 8 字节整组直接 memcpy(容量已按总字节数算好)。
|
||
const std::size_t groups = (byteCount - position) / sizeof(double);
|
||
if (groups > 0)
|
||
{
|
||
const std::size_t index = static_cast<std::size_t>(tailStart / sizeof(double));
|
||
if (index + groups > out.size())
|
||
{
|
||
failed = true;
|
||
return false;
|
||
}
|
||
std::memcpy(out.data() + index, bytes + position, groups * sizeof(double));
|
||
position += groups * sizeof(double);
|
||
tailStart += groups * sizeof(double);
|
||
}
|
||
// 末尾零头留作下一块的尾巴。
|
||
while (position < byteCount)
|
||
{
|
||
tail[tailBytes++] = bytes[position++];
|
||
}
|
||
return true;
|
||
});
|
||
if (!ok || failed)
|
||
{
|
||
out.clear();
|
||
return false;
|
||
}
|
||
// 尾部不足 8 字节(损坏)视为失败,与旧实现(要求整除)保持一致。
|
||
if (tailBytes != 0)
|
||
{
|
||
out.clear();
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool MatHdf5::ReadReferences(std::uint64_t address, std::vector<std::uint64_t>& out) const
|
||
{
|
||
out.clear();
|
||
Dataset dataset; if (!ReadDataset(address, dataset) || !dataset.ok ||
|
||
dataset.type.cls != TypeClass::Reference)
|
||
{
|
||
return false;
|
||
}
|
||
const std::size_t count = dataset.data.size() / 8;
|
||
out.reserve(count);
|
||
for (std::size_t index = 0; index < count; ++index)
|
||
{
|
||
const std::uint64_t raw = ReadLE(dataset.data.data() + index * 8, 8);
|
||
out.push_back(Resolve(raw));
|
||
}
|
||
return true;
|
||
}
|
||
|
||
} // namespace jdi
|