是的,在结构化存储(如 OLE 复合文件)中,通过流名称可以直接访问该流的数据。这种设计类似于通过文件名直接访问文件内容,但需注意其底层机制和结构化存储的特性。以下是详细解释:
1. 直接访问的含义
当调用 OpenStream
并指定流名称后:
- 无需索引或映射:流名称本身就是数据的唯一标识符,不需要通过哈希表、索引键或其他中间结构来定位数据。
- 直接获取接口指针:成功打开流后,会返回
IStream*
接口指针,通过该指针可直接调用Read
、Write
、Seek
等方法操作数据。
// 示例:打开流并读取数据
IStream* pStream = nullptr;
HRESULT hr = lpStorage->OpenStream(L"MyDataStream", ..., &pStream);
if (SUCCEEDED(hr)) {
BYTE buffer[1024];
ULONG bytesRead = 0;
hr = pStream->Read(buffer, sizeof(buffer), &bytesRead);
// 直接操作数据...
pStream->Release();
}
2. 结构化存储的底层实现
尽管流名称是直接访问的入口,但结构化存储的实现可能依赖以下机制:
- 目录表(Directory Entries):复合文件内部维护了一个类似文件系统的目录结构,记录所有流和子存储的名称及其物理位置。
- 直接定位:打开流时,系统通过名称在目录表中查找对应的元数据,直接定位到流数据的存储位置(类似于文件系统的 inode 查找)。
3. 与文件系统的关键区别
虽然流名称的访问方式类似文件名,但结构化存储在以下方面有所不同:
特性 | 文件系统 | 结构化存储 |
---|---|---|
数据存储单元 | 文件(File) | 流(Stream) |
层级结构 | 目录(Directory)和文件 | 存储(Storage)和流 |
访问粒度 | 文件级(整个文件为单位) | 流级(可精细控制字节流) |
并发控制 | 依赖操作系统文件锁 | 通过 STGM_SHARE_* 标志控制 |
持久化方式 | 独立文件 | 嵌入复合文件(如 .doc, .xls) |
4. 直接访问的限制
流名称的直接访问需满足以下条件:
- 名称必须存在:流名称需在当前存储中已定义,否则返回错误(如
STG_E_FILENOTFOUND
)。 - 名称唯一性:同一存储下不能有同名的流和子存储。
- 访问权限匹配:必须以兼容模式打开父存储(例如,若父存储以只读模式打开,则无法通过流写入数据)。
5. 应用场景示例
场景:读取 Word 文档中的嵌入式图片
- 打开根存储(Root Storage)。
- 通过流名称直接访问文档属性流(如
\x05SummaryInformation
)。 - 通过子存储名称进入嵌套存储(如
Images
存储)。 - 通过流名称直接读取图片数据流(如
Image1.jpg
)。
// 伪代码示例:访问嵌套流
IStorage* pRootStorage = ...; // 打开根存储
IStorage* pSubStorage = nullptr;
pRootStorage->OpenStorage(L"Images", ..., &pSubStorage); // 打开子存储
IStream* pImageStream = nullptr;
pSubStorage->OpenStream(L"Image1.jpg", ..., &pImageStream); // 直接访问流
// 读取图片数据...
6. 为什么说这是“直接”访问?
- 无中间解析层:流名称到数据的映射由结构化存储系统内部管理,开发者无需关心物理存储细节(如数据块的位置、分配策略等)。
- 接口直接暴露数据流:
IStream
接口提供类似文件句柄的操作(Read
/Write
),直接面向字节流。
7. 注意事项
- 名称大小写敏感:某些结构化存储实现(如 Windows 复合文件)的流名称是大小写敏感的。
- 保留字符:避免在名称中使用
/
、\
等可能被解析为路径分隔符的字符。 - 性能影响:频繁打开/关闭流可能影响性能,建议缓存常用流的
IStream
指针。
结论
通过流名称可以直接访问结构化存储中的流数据,这种设计结合了文件系统的直观性和结构化存储的封装性。其底层通过目录表实现快速定位,但开发者无需关心物理细节,只需通过名称和标准的 IStream
接口操作数据。这种机制使得结构化存储非常适合管理复杂、嵌套的二进制数据(如 Office 文档)。