Как читать данные EBCDIC с нестандартной кодовой страницей и не перепутать числа?

Вот один для старых (эр) рук :-)

Я читаю двоичный дамп из таблицы 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 в соответствии с их длиной в массивы символов, используя

sr.ReadBlock(buffer, 0, iFieldBufferSize)

Всегда помнить, что первые 2 байта в столбце VARCHAR следует отбросить и получить правильную строку с помощью

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, я получаю 0 в буфере (1) и 3 в буфере (0) - хорошо. НО, когда два байта равны 00 20, я получаю 128, прочитанных в буфер (0)!

Итак, после полдня дергания за волосы, я удаляю кодировщик из объявления streamreader, и теперь я получаю 32 чтения в буфер (0), как и должно быть!!!

Суть в том, что нестандартный кодировщик кодовой страницы искажает показания байтов !!!

Любая идея, как обойти это?


person GilShalit    schedule 24.02.2011    source источник


Ответы (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 указывает, что это «внутренний» сегмент составной записи, то есть «сегмент многосегментной записи, отличный от первого или последнего сегмента».

Вы можете рассматривать записи переменной длины и составные записи переменных как идентичные. Чтобы прочитать один из них, вам сначала нужно разобрать слово сегмента/записи/дескриптора и прочитать/собрать полную запись в байт [] из составляющих ее сегментов, а затем сделать все, что нужно сделать, чтобы преобразовать этот байт. [] в форму, которую вы можете использовать.

person Nicholas Carey    schedule 24.02.2011
comment
Николай, очень полезно! Не могли бы вы добавить вспомогательный метод для FLOAT? У меня есть несколько столбцов FLOAT(53). - person GilShalit; 25.02.2011
comment
Плыть довольно сложно. Мейнфреймы IBM не используют IEEE 754. Они используют формат с плавающей запятой на базе 16, который предшествует IEEE 754. У Microsoft есть статья базы знаний с некоторым кодом по адресу support.microsoft.com/kb/235856. Также посмотрите [i]Принципы работы[/i] IBM. Вы можете получить более старую версию с hack.org/mc/texts/principles. -of-operation.pdf и текущие версии от IBM по адресу www-01.ibm.com/support/ (но вам необходимо зарегистрироваться в IBM). - person Nicholas Carey; 25.02.2011
comment
Nichlas, завтра посмотрю... Большое спасибо! - person GilShalit; 25.02.2011
comment
Один момент о вашей реализации для readCharVarying. Как у вас есть, если ширина столбца больше, чем количество фактически используемых байтов, Reader останется в неправильном положении. Поэтому я добавил дополнительный вызов ReadOctets(ColumnWidth-size-2). - person GilShalit; 25.02.2011
comment
Значит, они выгружают поля varchar с фиксированной шириной и префиксом длины? Шиш. И еще одно: с 1998 года мэйнфреймы IBM также имеют поддержку IEEE 754: вам нужно знать, какая разновидность операций с плавающей запятой у вас есть. Однако даже если это IEEE с плавающей запятой, порядок байтов все равно будет сетевым порядком байтов (с обратным порядком байтов), поэтому его необходимо будет изменить на противоположный. - person Nicholas Carey; 25.02.2011
comment
Николас, я только что отправил дополнительный вопрос на stackoverflow.com/q/10516759/149769. - person GilShalit; 09.05.2012
comment
Николас, я только что отправил дополнительный вопрос на stackoverflow.com/q/10516759/149769. Я блуждаю, если вы можете взглянуть. Спасибо! - person GilShalit; 09.05.2012

не используйте StreamReader для чтения этого файла. Он будет интерпретировать двоичные числа в файле, как если бы они были символами, и это исказит их значение. Используйте FileStream и BinaryReader. Только используйте Encoding.GetString() при переводе группы байтов из файла, представляющего строку.

person Hans Passant    schedule 24.02.2011
comment
Спасибо! Направил меня в нужном направлении. Я и не мечтал, что на вопрос о файлах EBCDIC будет дан ответ в течение нескольких минут после публикации! - person GilShalit; 25.02.2011

@ Ханс Пассант прав. Если вы читаете файл, содержащий двоичные данные (как указано в вашем описании), то неправильно читать файл, как будто это текст.

К счастью, класс BinaryReader включает конструктор, который принимает кодировку символов в качестве одного из параметров. Вы можете использовать это для автоматического преобразования любых строк EBCDIC на иврите в файле в обычные строки Unicode, не влияя на интерпретацию нетекстовой (двоичной) части.

Кроме того, вам, вероятно, следует использовать двухбайтовое поле длины VARCHAR для чтения ваших строк, а не просто выбрасывать его!

Метод ReadString() в этом случае работать не будет, так как файл не был закодирован с помощью класса .NET BinaryWriter. Вместо этого вы должны получить длину VARCHAR (или жестко заданную длину поля CHAR) и передать ее методу ReadChars(int). Затем создайте результирующую строку из возвращаемого массива символов.

person Jeffrey L Whitledge    schedule 24.02.2011