Files
Just-DataInspector/Utilities/Src/MatH5.cpp
T
DeepSeek V4.1 Flash 53b981a2a9 全项目大检查:修复数据层/UI 真 BUG、清理死代码与过期注释、并评估二进制体积
按「性能 / BUG / 错误 / 过期注释 / 命名与实际含义不符」四项对全仓做了一次审查,
四个只读子审查交叉核对后逐条回到源码确认再改。

一、数据层真 BUG
- MatH5:U8/U16/U32/U64 用逐字节 Cached() 拼装,而 Cached 只服务「完全落在同一个
  64KB 块内」的请求 => 跨块时静默读成 0(U32 整段为 0、U64 高 32 位为 0),分块 B-tree
  的 chunk 偏移/子节点地址被破坏且无任何报错。新增 ScalarLE():命中缓存用缓存,
  否则退回一次 ReadAt;U64 顺带从 8 次缓存查找降到 1 次。
- Mf4Loader::IsBlock 未校验链接表是否落在块内 => DataOffset 越界,随后
  block_len - (data - offset) 在 uint64 上下溢,得到一个约 2^64 的"可用字节数",
  下游 vector(usable+1) 直接 bad_alloc。同时把 offset + bytes > size 改成不产生回绕的写法。
- DecodeRawValue 的整数路径:bit_offset%8 != 0 且位宽 64 时需 9 个字节,先拼成 64 位
  再整体右移会把最高字节静默丢弃 => 改为逐字节带位偏移累加。
- cg_cycle_count 加「数据段总字节 / 记录字节」上限(压缩段按 deflate 最大膨胀比估算)。
- CsvLoader 表头分支同样跳过 #/% 注释行(否则整份文件按 1 列解析并被丢弃)。
- MatLoader:double -> int 的越界转换加了范围检查;普通对象递归加深度上限与成环检测。
- Utf8Util::BuildTicks:double -> long long 越界转换前先判商是否落在 ±9e18 内。

二、界面 BUG
- 工具栏按钮永远不显示悬停高亮:Paint 读的 Button::hover 全仓从未赋值,改为按 m_hoverIndex 判断。
- 菜单打开时"取消底层拖拽"对每个消息都执行,把菜单自己的 Target::EDIT_FIELD 也清掉
  => 菜单内联输入框无法拖拽框选。排除 EDIT_FIELD。
- GdiplusShutdown 之前未释放 PlotView 的 GDI+ 位图(Application 是进程单例,成员析构在
  WinMain 之后)=> 新增 PlotView::ReleaseGraphicsResources() 并在 Shutdown 里调用。
- WM_KILLFOCUS 现在会提交标签页/绘图区编辑并复位捕获与拖拽状态。
- WM_LBUTTONUP 不再抢走中键平移的鼠标捕获。
- TextEdit:被过滤器拒绝的字符返回 false;无选区时 Ctrl+X 只复制、不再清空整段文本。
- SignalPanel::InvalidateRows 作废编辑/待确认/改名计时等行下标状态。
- EndFilterEdit 改走 ApplyFilterText(否则输入纯空格后回车,列表仍停在筛选结果)。
- 新增 SignalBadgeVisible 作为唯一判据(原来命中只看"可见",绘制还要求"已分配网格")。
- 折起动画结束后夹取滚动量;双击分组名改名时清掉该组正在播放的折起动画。
- ScrollToSignal 复用 SignalScrollTarget(含悬浮组名避让)。
- RowNameRect 与绘制使用同一最小宽度(原来退化时返回空矩形)。
- 分隔条拖拽上限改用与 Relayout 同一公式;UpdateDragCursor 补 SPLITTER/EDIT_FIELD。
- SignalPanel 里几个守卫改为判断真正被解引用的指针;分组行 ✕ 也返回手型光标。

三、PlotView
- FindStyleOnlyLanes 改用完整的 SceneStyleEntry::operator==(原来只比 4 个字段,
  另一个网格同时改"调整"时带着旧像素被当成最新)。
- 宽基底图层不再烘焙拖拽光晕(LaneLayerHash 不含它),改由 PaintHoverHighlight 每帧叠加。
- PaintLaneLegend 提前返回前清掉本网格的命中矩形(否则能点中看不见的图例条目)。
- 面板主体改为完全不透明(消除平移后 4% 透出的旧波形,零额外代价)。
- PAN / Shift+滚轮改走 SetTimeView,使"自动点线"判据随平移重算。
- 内联输入框命中字体与绘制统一为 monoSmall。
- 派生曲线取值与窗口按 SourceIndexFor 映射(水平镜像时不再读到镜像另一端)。
- EdgePanelWidth 补 style/axis 空指针守卫;二分结果夹到 count。

四、清理
- 删除 Toolbar::ActionLabel、Theme::statusBg、PopupMenu 的 editResult 死机制、
  Toolbar::Layout 的重复 clear()、HitRow 的恒假分支、OnDoubleClick 的重复判定、
  PaintUtil 里孤立的注释块。
- 修正一批过期/错误注释(包络带"不做加宽"、LaneLayerHash 的图例说法、
  均值滤波曲线的窗口说法、样式区行数与「调整」按钮顺序、FIT_ALL 退出缩放模式、
  RemoveSignal 的 ✕ 位置、.mat/.dat 格式文档、MatH5 的"映射视图"、DataModel 的
  "点线/被移除文件"、SignalPanel 的"行高连续缩小"、TextEdit 的 PaintUtil 引用等)。

五、二进制体积评估(按要求只评估,未做压缩修改)
- release 809,472 字节;.text 652KB、.xdata+.pdata 约 75KB、.rdata 42KB、
  .rsrc 20KB、.idata 11.5KB。
- 去掉 -ld3d11 -ldwmapi -ld3dcompiler(仓内无任何调用)实测 0 字节收益
  (静态库只拉取被引用的对象)=> 删链接库不是压缩手段,只是配置整洁。
- 仓内已无 <iostream>/<sstream>/<filesystem>/<regex>/<locale> 等体积炸弹。
- 结论:已无明显的浪费可捡;要显著变小只能改变取舍(动态 CRT / UPX / 砍功能),
  需要用户决策,本轮未动。

验证
- editcheck 20/20、round3check 31/31、round4check 34/34、round5check 5/5、convcheck 18/18。
- check 136/137:唯一失败项经 git stash 回到改动前复现同一数值,属该探针自身的既有
  不稳定项(取决于前置用例留下的缓存/时序),与本轮改动无关。
- 真实数据:4 个 MF4 => 283/8/18/24 个信号;.mat 样例 8 信号、
  带无障碍对照-防夹力曲线数据 8 信号、BIG_MAT.mat 5 信号(私有内存 +741MB)。
- make release / make debug 零警告。

文档:Documents/agent_mem.md 同步 §10 变更日志、§11.2 第 171-180 条硬约束
与文末本轮小节(含体积评估数据)。
2026-10-20 16:10:29 +08:00

1479 lines
44 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 连续布局的读取切片大小。
*
* 数据读取走「`ReadFile` 随机读 + 按需预读」(内存映射已移除),因此切片越大
* 单次 I/O 的摊销越好,但也会把峰值临时缓冲抬高;4MB 是本机实测的折中点。
*/
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
namespace
{
/** @brief 元数据缓存的块大小与槽数(16 × 64KB = 1MB,足够覆盖对象头/B-tree 热点)。 */
constexpr std::size_t kMetaBlockBytes = 64u * 1024u;
constexpr std::size_t kMetaBlockSlots = 16;
/** @brief 顺序预读窗口上限。 */
constexpr std::size_t kReadAheadBytes = 4u * 1024u * 1024u;
/**
* @brief 数据读取的最小粒度:按请求大小向上对齐读取,命中窗口时直接复用。
*
* ⚠️ 分块数据集的 chunk 在文件里**并非完全顺序**(多个样本流交错写入,
* 每段连续区域约 1.8MB)。实测 `BIG_MAT.mat`:固定预读 4MB 会让每个连续段
* 末尾浪费 2.2MB,总读盘量达到文件大小的 **2.2 倍**(2.87GB / 1.29GB)。
* 改为「按需 + 64KB 对齐」后,读盘量回到文件大小附近。
*/
constexpr std::size_t kReadGranularity = 64u * 1024u;
}
bool MatHdf5::ReadAt(std::uint64_t offset, void* dst, std::size_t bytes) const
{
if (m_fileHandle == nullptr || bytes == 0)
{
return bytes == 0;
}
if (offset > m_size || bytes > m_size - offset)
{
return false;
}
const HANDLE file = static_cast<HANDLE>(m_fileHandle);
LARGE_INTEGER position = {};
position.QuadPart = static_cast<LONGLONG>(offset);
if (!SetFilePointerEx(file, position, nullptr, FILE_BEGIN))
{
return false;
}
std::size_t done = 0;
while (done < bytes)
{
DWORD read = 0;
const DWORD want = static_cast<DWORD>(std::min<std::size_t>(bytes - done, 1u << 30));
if (!ReadFile(file, static_cast<std::uint8_t*>(dst) + done, want, &read, nullptr))
{
return false;
}
if (read == 0)
{
return false; // 文件被截断
}
done += read;
}
m_bytesRead += bytes;
return true;
}
const std::uint8_t* MatHdf5::Cached(std::uint64_t offset, std::size_t bytes) const
{
if (offset >= m_size || bytes > kMetaBlockBytes)
{
return nullptr;
}
if (m_cache.empty())
{
m_cache.resize(kMetaBlockSlots);
}
const std::uint64_t base = offset - (offset % kMetaBlockBytes);
// 快路径:连续读同一块(U16/U32/U64 与 B-tree 遍历几乎都是这种模式)。
{
CacheSlot& slot = m_cache[m_lastSlot];
if (slot.length > 0 && slot.base == base)
{
slot.stamp = ++m_stamp;
const std::uint64_t delta = offset - slot.base;
return (delta + bytes <= slot.length) ? (slot.bytes.data() + delta) : nullptr;
}
}
CacheSlot* victim = nullptr;
for (std::size_t index = 0; index < m_cache.size(); ++index)
{
CacheSlot& slot = m_cache[index];
if (slot.length > 0 && slot.base == base)
{
slot.stamp = ++m_stamp;
m_lastSlot = index;
const std::uint64_t delta = offset - slot.base;
return (delta + bytes <= slot.length) ? (slot.bytes.data() + delta) : nullptr;
}
if (slot.length == 0 || victim == nullptr || slot.stamp < victim->stamp)
{
victim = &slot;
}
}
if (victim == nullptr)
{
return nullptr;
}
// 未命中:整块读入(块可能超出文件末尾,按可用长度裁剪)。
const std::size_t wanted =
static_cast<std::size_t>(std::min<std::uint64_t>(kMetaBlockBytes, m_size - base));
if (bytes > wanted)
{
return nullptr;
}
victim->bytes.resize(wanted);
victim->base = base;
victim->length = static_cast<std::uint32_t>(wanted);
victim->stamp = ++m_stamp;
m_lastSlot = static_cast<std::size_t>(victim - m_cache.data());
if (!ReadAt(base, victim->bytes.data(), wanted))
{
victim->length = 0;
return nullptr;
}
return victim->bytes.data() + (offset - base);
}
bool MatHdf5::Tag(std::uint64_t offset, const char* id) const
{
const std::uint8_t* bytes = Cached(offset, 4);
if (bytes == nullptr)
{
return false;
}
return std::memcmp(bytes, id, 4) == 0;
}
void MatHdf5::ReadCString(std::uint64_t offset, std::string& out) const
{
out.clear();
std::uint64_t position = offset;
while (position < m_size && out.size() < 4096)
{
const std::uint8_t* bytes = Cached(position, 1);
if (bytes == nullptr)
{
return;
}
// 只消费本缓存块内剩余的字节,保证下一次循环拿到的新块仍然有效。
const std::uint64_t blockBase = position - (position % kMetaBlockBytes);
const std::size_t limit =
static_cast<std::size_t>(std::min<std::uint64_t>(kMetaBlockBytes - (position - blockBase),
m_size - position));
std::size_t used = 0;
while (used < limit)
{
if (bytes[used] == '\0')
{
return;
}
out.push_back(static_cast<char>(bytes[used]));
++used;
}
position += used;
}
}
void MatHdf5::DropReadAhead() const
{
m_readAheadValid = false;
m_readAheadLength = 0;
}
const std::uint8_t* MatHdf5::ReadData(std::uint64_t offset, std::size_t bytes) const
{
if (bytes > kReadAheadBytes || offset > m_size || bytes > m_size - offset)
{
return nullptr;
}
if (m_readAheadValid && offset >= m_readAheadBase &&
(offset - m_readAheadBase) + bytes <= m_readAheadLength)
{
return m_readAhead.data() + (offset - m_readAheadBase);
}
// 按需读取 + 64KB 对齐:命中当前窗口就直接复用,否则只读「这次需要的一段」。
// (固定 4MB 预读在交错存放的分块数据集上会浪费 2 倍以上的 I/O。)
const std::size_t requested = std::max<std::size_t>(bytes, kReadGranularity);
std::size_t wanted = static_cast<std::size_t>(std::min<std::uint64_t>(
m_size - offset, std::min<std::size_t>(kReadAheadBytes,
((requested + kReadGranularity - 1) / kReadGranularity) *
kReadGranularity)));
if (m_readAhead.size() < wanted)
{
m_readAhead.resize(wanted);
}
if (!ReadAt(offset, m_readAhead.data(), wanted))
{
DropReadAhead();
return nullptr;
}
m_readAheadBase = offset;
m_readAheadLength = static_cast<std::uint32_t>(wanted);
m_readAheadValid = true;
if (m_readProgress && !m_readProgress(m_bytesRead, m_size))
{
m_readCancelled = true;
DropReadAhead();
return nullptr;
}
return m_readAhead.data();
}
std::uint64_t MatHdf5::ScalarLE(std::uint64_t offset, std::size_t bytes) const
{
if (bytes == 0 || bytes > 8)
{
return 0;
}
const std::uint8_t* cached = Cached(offset, bytes);
std::uint8_t local[8] = {};
const std::uint8_t* data = cached;
if (data == nullptr)
{
// 跨缓存块边界(或越界):退回一次直接读取,绝不把缺失字节当成 0。
if (offset > m_size || bytes > m_size - offset)
{
return 0;
}
if (!ReadAt(offset, local, bytes))
{
return 0;
}
data = local;
}
std::uint64_t value = 0;
for (std::size_t index = 0; index < bytes; ++index)
{
value |= static_cast<std::uint64_t>(data[index]) << (8u * index);
}
return value;
}
std::uint8_t MatHdf5::U8(std::uint64_t offset) const
{
return static_cast<std::uint8_t>(ScalarLE(offset, 1));
}
std::uint16_t MatHdf5::U16(std::uint64_t offset) const
{
return static_cast<std::uint16_t>(ScalarLE(offset, 2));
}
std::uint32_t MatHdf5::U32(std::uint64_t offset) const
{
return static_cast<std::uint32_t>(ScalarLE(offset, 4));
}
std::uint64_t MatHdf5::U64(std::uint64_t offset) const
{
return ScalarLE(offset, 8);
}
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」三份内存;而内存映射在文件被截断 / 网络共享掉线 /
// 移动介质拔出时会触发 SEH(页访问异常),GCC 下的 `try/catch` 捕获不到 ⇒ 进程直接崩溃。
// 这里改用 `ReadFile` + 元数据块缓存:任何 I/O 失败都只会变成 `false`。
const HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
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;
}
m_fileHandle = file;
m_size = static_cast<std::uint64_t>(fileSize.QuadPart);
m_bytesRead = 0;
m_readCancelled = false;
// 打开阶段已知道总大小:立即上报一次,遮罩层可以显示「0 / 总大小」,
// 之后的真实进度由 `ReadAt` 累计的已读字节数驱动。
if (progress && !progress(0, m_size))
{
Close();
error.clear(); // 用户取消:不显示错误
return false;
}
// 超级块:签名可能出现在 0、512、1024…(MATLAB 用 512 字节 user block)。
std::size_t signature = static_cast<std::size_t>(m_size);
for (std::size_t index = 0; index + 8 <= m_size && index <= 4096; index += 512)
{
if (Tag(index, "\x89HDF"))
{
signature = index;
break;
}
}
if (signature == m_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_size)
{
error = L"HDF5 超级块损坏。";
return false;
}
return true;
}
void MatHdf5::Close()
{
DropReadAhead();
m_cache.clear();
m_lastSlot = 0;
if (m_fileHandle != nullptr)
{
CloseHandle(static_cast<HANDLE>(m_fileHandle));
m_fileHandle = nullptr;
}
m_size = 0;
m_bytesRead = 0;
m_readCancelled = false;
m_base = 0;
m_root = 0;
m_heaps.clear();
m_readProgress = ReadProgress();
}
void MatHdf5::ReadMessages(std::uint64_t address, std::vector<Message>& messages) const
{
messages.clear();
if (address + 8 > m_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_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_size)
{
break;
}
message.data.resize(messageSize);
if (messageSize > 0 && !ReadAt(offset, message.data.data(), messageSize))
{
break;
}
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_size && guard++ < 100000)
{
if (!Tag(node, "TREE"))
{
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_size || !Tag(node, "SNOD") || 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_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_size)
{
continue;
}
std::string name;
ReadCString(heapData + nameOffset, name);
out.emplace_back(std::move(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_size && Tag(heap, "HEAP"))
{
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_size) ? (m_size - dataset.dataAddress)
: 0);
const std::uint64_t bytes = std::min(totalBytes, available);
// ⚠️ 元数据声明了 N 个元素、文件里却不够:明确失败,而不是静默截短
//(静默截短会让曲线“看起来只有一半”,很难诊断)。
if (bytes < totalBytes)
{
return false;
}
std::size_t slice = kStreamSliceBytes - (kStreamSliceBytes % elementSize);
if (slice == 0)
{
slice = elementSize;
}
if (m_slice.size() < slice)
{
m_slice.resize(slice);
}
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 (!ReadAt(dataset.dataAddress + offset, m_slice.data(), count))
{
return false;
}
if (m_readProgress && !m_readProgress(m_bytesRead, m_size))
{
m_readCancelled = true;
return false;
}
if (!sink(m_slice.data(), 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_size || !Tag(node, "TREE"))
{
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_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_size)
{
continue;
}
// 顺序预读:分块数据通常连续存放,逐 chunk 读会退化成大量小系统调用。
const std::uint8_t* source = nullptr;
if (chunkSize <= kReadAheadBytes)
{
source = ReadData(child, chunkSize);
}
if (source == nullptr)
{
if (m_chunk.size() < chunkSize)
{
m_chunk.resize(chunkSize);
}
if (!ReadAt(child, m_chunk.data(), chunkSize))
{
continue;
}
if (m_readProgress && !m_readProgress(m_bytesRead, m_size))
{
m_readCancelled = true;
return false;
}
source = m_chunk.data();
}
if (m_readCancelled)
{
return false;
}
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 次)。
//
// 快路径(rank == 2 且第二维完全对齐):整块在目标里就是一段连续区域,
// 直接一次回调,省掉「每行算坐标 + 判断合并」的循环。
// 实测 BIG_MAT.mat:`#data#` 为 129,139,768 字节 × 32KB chunk,每 chunk 32768 行,
// 逐行循环共 12.9 亿次迭代(约占总加载时间的一半以上)。
if (rank == 2 && chunkDims[1] == dims[1] && chunkDims[1] > 0)
{
const std::uint64_t startRow =
std::min<std::uint64_t>(chunkOffset[0], dims[0]);
const std::uint64_t rowCount =
std::min<std::uint64_t>(chunkDims[0], dims[0] - startRow);
const std::uint64_t byteCount = rowCount * rowBytes;
if (startRow < dims[0] && byteCount <= static_cast<std::uint64_t>(sourceSize))
{
if (!sink(source, static_cast<std::size_t>(byteCount),
startRow * chunkDims[1]))
{
return false;
}
}
continue;
}
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