如何使用非标准代码页读取EBCDIC数据,而不是弄乱数字?

时间:2011-02-24 19:12:11

标签: .net encoding codepages ebcdic

这是旧手(呃)的手: - )

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

所以我用一个使用System.Text.Encoding的streamreader打开文件,如下所示:

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个字节中,因为它的Big endian,我做

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,我在缓冲区(1)中得到0,在缓冲区(0)中得到3 - 好。但是当两个字节是00 20时,我将128读入缓冲区(0)!

所以经过半天拉我的头发后,我从streamreader声明中删除编码器,现在我将32读入缓冲区(0),就像它应该是!!!

底线,非标准代码页编码器会弄乱字节读数!!!

知道怎么解决这个问题吗?

3 个答案:

答案 0 :(得分:3)

使用StreamReader读取此文件。它将解释文件中的二进制数,就好像它们是字符一样,这​​会弄乱它们的值。使用FileStream和BinaryReader。当您从表示字符串的文件转换一组字节时,使用Encoding.GetString()。

答案 1 :(得分:3)

您无法读取类似于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表示这是跨区记录的“内部”段,即“除第一段或最后一段之外的多段记录的段。”

您可以将可变长度和可变跨区记录视为相同。要读取其中一个,首先需要解析段/记录/描述符字,并从其组成段读取/组装完整记录到byte [],然后执行转换该字节所需的任何操作[]成你可以使用的表格。

答案 2 :(得分:2)

@Hans Passant是正确的。如果您正在读取包含二进制数据的文件(如您的描述所示),那么读取文件就好像它是文本一样。

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

另外,您应该使用双字节VARCHAR长度字段来读取字符串而不是将其丢弃!

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