我同意 OP:有时您只是需要注意效率。我不认为提供 API 的例子是最好的,因为这肯定需要安全和简单而不是效率。
然而,一个简单的例子是在处理大量包含无数记录的巨大二进制文件时,例如在编写解析器时。如果不使用诸如 System.ArraySegment 之类的机制,解析器就会成为一个大内存占用者,并且会通过创建无数新数据元素、复制所有内存以及从堆中分片来大大减慢速度。这是一个非常现实的性能问题。我一直在为电信行业编写此类解析器,这些解析器每天在多个类别中的每个类别中生成数百万条记录,这些记录来自许多具有可变长度二进制结构的交换机,需要将其解析到数据库中。
使用 System.ArraySegment 机制与为每条记录创建新的结构副本相比,极大地加快了解析速度,并大大降低了解析器的峰值内存消耗。这些都是非常真实的优势,因为服务器运行多个解析器,经常运行它们,并且速度和内存节省 = 非常实际的成本节省,因为不必有这么多专用于解析的处理器。
System.Array 段非常易于使用。这是一个简单的示例,它提供了一种基本方法来跟踪典型的大二进制文件中的各个记录,该文件充满了具有固定长度标题和可变长度记录大小的记录(明显的异常控制已删除):
public struct MyRecord
{
ArraySegment<byte> header;
ArraySegment<byte> data;
}
public class Parser
{
const int HEADER_SIZE = 10;
const int HDR_OFS_REC_TYPE = 0;
const int HDR_OFS_REC_LEN = 4;
byte[] m_fileData;
List<MyRecord> records = new List<MyRecord>();
bool Parse(FileStream fs)
{
int fileLen = (int)fs.FileLength;
m_fileData = new byte[fileLen];
fs.Read(m_fileData, 0, fileLen);
fs.Close();
fs.Dispose();
int offset = 0;
while (offset + HEADER_SIZE < fileLen)
{
int recType = (int)m_fileData[offset];
switch (recType) { /*puke if not a recognized type*/ }
int varDataLen = ((int)m_fileData[offset + HDR_OFS_REC_LEN]) * 256
+ (int)m_fileData[offset + HDR_OFS_REC_LEN + 1];
if (offset + varDataLen > fileLen) { /*puke as file has odd bytes at end*/}
MyRecord rec = new MyRecord();
rec.header = new ArraySegment(m_fileData, offset, HEADER_SIZE);
rec.data = new ArraySegment(m_fileData, offset + HEADER_SIZE,
varDataLen);
records.Add(rec);
offset += HEADER_SIZE + varDataLen;
}
}
}
上面的示例为您提供了一个包含文件中每条记录的 ArraySegments 列表,同时将所有实际数据保留在每个文件的一个大数组中。唯一的开销是 MyRecord 结构中每条记录的两个数组段。处理记录时,您拥有 MyRecord.header.Array 和 MyRecord.data.Array 属性,它们允许您对每条记录中的元素进行操作,就好像它们是它们自己的 byte[] 副本一样。