3

这是给老(呃)手的一个:-)

我正在从大型机 DB2 表中读取二进制转储。该表具有 varchar、char、smallint、integer 和 float 列。为了让它更有趣,DB2 使用了代码页 424(希伯来语)。我需要我的代码独立于代码页。

因此,我使用 System.Text.Encoding 使用流读取器打开文件,如下所示:

Dim encoding As System.Text.Encoding = System.Text.Encoding.GetEncoding(20424)
Dim sr As New StreamReader(item.Key, encoding)

并继续使用 VARCHAR 和 CHAR 数据根据它们的长度读入 char 数组

sr.ReadBlock(buffer, 0, iFieldBufferSize)

始终记住 VARCHAR 列中的前 2 个字节应该被丢弃并获得正确的字符串

SringValue = encoding.GetString(encoding.GetBytes(buffer))

一切都很好!

但现在我到了 SMALLINT 专栏,我遇到了麻烦。有符号数的值存储在 2 个字节中,因为它的大端,我做

Dim buffer(iFieldBufferSize - 1) As Byte
buffer(1) = sr.Read ''switch the bytes around!
buffer(0) = sr.Read
Dim byteBuffer(iFieldBufferSize - 1) As Byte
Dim i16 As Int16 = BitConverter.ToUInt16(buffer, 0)

我得到错误的数字!例如,如果字节是 00 03,我在 buffer(1) 中得到 0,在 buffer(0) 中得到 3 - 很好。但是当两个字节为 00 20 时,我将 128 读入缓冲区(0)!

因此,在拉了半天的头发之后,我将编码器从流读取器声明中删除,现在我将 32 读入缓冲区(0),就像它应该的那样!!!

最重要的是,非标准代码页编码器弄乱了字节读数!!!

知道如何解决这个问题吗?

4

3 回答 3

4

不要使用StreamReader 读取此文件。它将文件中的二进制数解释为字符,这会弄乱它们的值。使用 FileStream 和 BinaryReader。 在从表示字符串的文件中转换一组字节时才使用 Encoding.GetString()。

于 2011-02-24T19:52:34.643 回答
4

您无法将 EBCDIC 文件转储之类的内容作为流读取。StreamReader 类是 TextReader 的一种,用于读取字符。您正在阅读一条记录——一种包含混合二进制和文本的复杂数据结构。

您需要使用 FileStream 进行读取,并根据需要读取八位字节块。您将需要一些简单的辅助方法,例如:

private byte[] ReadOctets( Stream input , int size )
{
    if ( size < 0 ) throw new ArgumentOutOfRangeException() ;

    byte[] octets      = new byte[size] ;
    int    octets_read = input.Read( octets , 0 , size ) ;

    if ( octets_read != size ) throw new InvalidDataException() ;

    return octets ;
}

public string readCharVarying( Stream input )
{
    short    size        = readShort( input ) ;

    return readCharFixed( input , size ) ;
}

public string readCharFixed( Stream input , int size )
{
    Encoding e           = System.Text.Encoding.GetEncoding(20424) ;
    byte[]   octets      = ReadOctets( input , size ) ;
    string   value       = e.GetString( octets ) ;

    return value ;
}

private short readShort( Stream input )
{
    byte[] octets            = ReadOctets(input,2) ;
    short  bigEndianValue    = BitConverter.ToInt16(octets,0) ;
    short  littleEndianValue = System.Net.IPAddress.NetworkToHostOrder( bigEndianValue ) ;

    return littleEndianValue ;
}

private int readInt( Stream input )
{
    byte[] octets            = ReadOctets(input,4) ;
    int    bigEndianValue    = BitConverter.ToInt32(octets,0) ;
    int    littleEndianValue = System.Net.IPAddress.NetworkToHostOrder( bigEndianValue ) ;

    return littleEndianValue ;
}

private long readLong( Stream input )
{
    byte[] octets            = ReadOctets(input,8) ;
    long   bigEndianValue    = BitConverter.ToInt64(octets,0) ;
    long   littleEndianValue = System.Net.IPAddress.NetworkToHostOrder( bigEndianValue ) ;

    return littleEndianValue ;
}

IBM 大型机通常在其文件系统中具有固定或可变长度的记录。固定长度很容易:您只需要知道记录长度,就可以在一次调用 Read() 方法中读取记录的所有字节,然后根据需要转换片段。

可变长度记录有点棘手,它们以 4 字节的记录描述符字开头,由 2 字节(16 位)的逻辑记录长度组成,然后是 2 字节(16 位)的 0 值。逻辑记录长度不包括 4 字节记录描述符字。

您可能还会看到可变的跨区记录。这些类似于可变长度记录,除了 4 字节前缀是段描述符字。前 2 个八位字节包含段长度,下一个八位位组标识段类型,最后一个八位位组为 NUL (0x00)。段类型如下:

  • 0x00 表示完整的逻辑记录
  • 0x01 表示这是跨区记录的第一段
  • 0x10 表示这是跨越记录的最后一段
  • 0x11 表示这是一个跨越记录的“内部”段,即“除第一个或最后一个段之外的多段记录的段”。

您可以将可变长度和可变跨度记录视为相同。要读取其中一个,您首先需要解析段/记录/描述符字并将完整记录从其组成段中读取/组装成一个字节[],然后做任何需要做的事情来转换该字节[] 转换成可以使用的形式。

于 2011-02-24T20:52:30.213 回答
3

@Hans Passant 是正确的。如果您正在读取包含二进制数据的文件(如您的描述所示),那么将文件视为文本读取是不正确的。

幸运的是,BinaryReader 类包含一个将字符编码作为参数之一的构造函数。您可以使用它自动将文件中的任何希伯来语 EBCDIC 字符串转换为普通的 Unicode 字符串,而不会影响对非文本(二进制)部分的解释。

此外,您可能应该使用两字节的 VARCHAR 长度字段来读取您的字符串,而不是仅仅将其丢弃!

在这种情况下,ReadString() 方法将不起作用,因为文件未使用 .NET BinaryWriter 类进行编码。相反,您应该获取 VARCHAR 的长度(或 CHAR 字段的硬编码长度)并将其传递给 ReadChars(int) 方法。然后从返回的字符数组构造您的结果字符串。

于 2011-02-24T20:43:14.433 回答