C# 在底层数据不是UTF-16时有效地实现DbDataReader.GetChars()

C# 在底层数据不是UTF-16时有效地实现DbDataReader.GetChars(),c#,character-encoding,ado.net,streaming,C#,Character Encoding,Ado.net,Streaming,我需要为ADO.NET提供程序实现DbDataReader.GetChars(),但需要注意的是,单元格中的数据可能不是UTF-16,实际上可能是多种不同编码中的任意一种 该实现专门针对“长数据”,源数据位于服务器上。我与服务器的接口(实际上无法更改)是请求单元格的字节范围。服务器不会以任何方式解释这些字节,它只是二进制数据 我可以在特殊情况下使用明显的实现UTF-16LE和UTF-16BE,但对于其他编码,没有直接的方法将请求“get me UTF-16 codeunits X to X+Y”

我需要为ADO.NET提供程序实现DbDataReader.GetChars(),但需要注意的是,单元格中的数据可能不是UTF-16,实际上可能是多种不同编码中的任意一种

该实现专门针对“长数据”,源数据位于服务器上。我与服务器的接口(实际上无法更改)是请求单元格的字节范围。服务器不会以任何方式解释这些字节,它只是二进制数据

我可以在特殊情况下使用明显的实现UTF-16LE和UTF-16BE,但对于其他编码,没有直接的方法将请求“get me UTF-16 codeunits X to X+Y”转换为请求“get me bytes X'to X'+Y'in encoding Z”

消除明显实施的一些“要求”:

  • 我不希望在任何时候向客户端检索给定单元格的所有数据,除非有必要。单元可能非常大,一个请求几千字节的应用程序不需要处理数百兆的内存才能满足请求
  • 我希望相对有效地支持GetChars()公开的随机访问。在第一次请求代码单元10亿到10亿+10的情况下,我看不到任何方法可以避免在请求的代码点之前从服务器检索单元格中的所有数据,但随后请求代码单元10亿+10到10亿+20,甚至9.99亿、99.9万到10亿的代码点也不应该意味着重新检索所有这些数据
我猜绝大多数应用程序实际上不会“随机”访问长数据单元,但如果这样做了,最好避免糟糕的性能,因此如果我找不到一种相对简单的方法来支持它,我想我将不得不放弃它

我的想法是保持#{UTF-16代码单元}->#{字节的服务器编码数据}的映射,在我从单元格检索数据时对其进行更新,并使用它找到一个“关闭”位置,开始从服务器请求数据(不是每次都从一开始就检索。顺便说一句,.NET framework中缺少类似于C++的std::map::lower_bound的东西,这让我很沮丧。)不幸的是,我发现生成此映射非常困难

我一直在尝试使用Decoder类,特别是将数据逐段转换,但我不知道如何可靠地判断给定数量的源数据映射到X UTF-16编码单元,因为“bytesUsed”参数似乎包括源字节,这些字节只是存储在对象的内部状态中,而不是输出这会导致我在试图从一个部分代码点中间开始或结束时给我带来垃圾的问题。


所以,我的问题是,有没有什么“窍门”可以用来实现这一点(计算出字节到代码单元的精确映射,而不用像在循环中转换、逐字节减小源字节的大小之类的方法)?

你知道你的服务器可能提供哪些编码吗?我这样问是因为有些编码是“有状态的”,这意味着给定字节的含义可能取决于其前面的字节序列。例如(),在编码标准ISO 2022-JP中,0x24 0x2c的两个字节可能表示日语平假名字符或两个ASCII字符“$”和“,”“移位状态”-前面控制序列的存在。在几个unicode之前的日文编码中,这些移位状态可以出现在字符串中的任何位置,直到遇到新的移位控制序列为止。在最坏的情况下,根据,“通常,只有从一开始就线性读取非Unicode文本,才能可靠地检测字符边界”


即使是c#使用的UTF-16编码(在概念上是无状态的),由于和的存在,也比通常实现的复杂。代理项对是一对
char
,它们共同指定了一个给定的字符,例如,我发现了如何处理可能丢失的转换状态:我保留了一个解码器aro的副本und在从相关偏移量重新启动时使用的映射中。这样,我不会丢失它在其内部缓冲区中保留的任何部分代码点。这还可以避免添加编码特定的代码,并处理编码的潜在问题,例如dbc带来的Shift JIS


解码器是不可克隆的,所以我使用序列化+反序列化来制作副本。

结果发现UTF-32解码器中似乎存在一个bug:序列化它并反序列化它似乎可以清除其内部状态(与文档所述相反)。叹气。。。