Как получить данные с прямым порядком байтов из Big Endian в C# с помощью метода bitConverter.ToInt32?

Я делаю приложение в C#. В этом приложении у меня есть байтовый массив, содержащий шестнадцатеричные значения.

Здесь я получаю данные с прямым порядком байтов, но я хочу, чтобы они были с прямым порядком байтов.

Здесь я использую Bitconverter.toInt32 метод для преобразования этого значения в целое число.

Но моя проблема заключается в том, что перед преобразованием значения мне нужно скопировать эти 4-байтовые данные во временный массив из исходного байтового массива, а затем перевернуть этот временный байтовый массив.

Я не могу перевернуть исходный массив, потому что он содержит и другие данные.

Из-за этого мое приложение становится медленным. код Здесь у меня есть один исходный массив байтов как waveData[]. Он содержит много данных.

byte[] tempForTimestamp=new byte[4];
tempForTimestamp[0] = waveData[290];
tempForTimestamp[1] = waveData[289];
tempForTimestamp[2] = waveData[288];
tempForTimestamp[3] = waveData[287];
int number = BitConverter.ToInt32(tempForTimestamp, 0);

Есть ли другой метод для этого преобразования?

13 ответов

Решение

Если вы знаете, что данные являются прямым порядком байтов, возможно, просто сделайте это вручную:

int value = (buffer[i++] << 24) | (buffer[i++] << 16)
          | (buffer[i++] << 8) | buffer[i++];

это также будет надежно работать на любом процессоре. Заметка i ваше текущее смещение в буфер.

Другой подход - перетасовать массив:

byte tmp = buffer[i+3];
buffer[i+3] = buffer[i];
buffer[i] = tmp;
tmp = buffer[i+2];
buffer[i+2] = buffer[i+1];
buffer[i+1] = tmp;
int value = BitConverter.ToInt32(buffer, i);
i += 4;

Я нахожу первый намного более читабельным, и в нем нет веток / сложного кода, поэтому он тоже должен работать довольно быстро. Второй также может столкнуться с проблемами на некоторых платформах (где ЦП уже работает с прямым порядком байтов).

Добавьте ссылку на System.Memory nuget и используйте BinaryPrimitives.ReverseEndianness().

using System.Buffers.Binary;
number = BinaryPrimitives.ReverseEndianness(number);

Он поддерживает целые числа со знаком и без знака (byte/short/int/long).

В современном Linq однострочная и самая легкая для понимания версия была бы:

int number = BitConverter.ToInt32(waveData.Skip(286).Take(4).Reverse().ToArray(), 0);

Вы могли бы также...

byte[] tempForTimestamp = new byte[4];
Array.Copy(waveData, 287, tempForTimestamp, 0, 4);
Array.Reverse(tempForTimestamp);
int number = BitConverter.ToInt32(tempForTimestamp);

:)

Ну вот

public static int SwapEndianness(int value)
{
    var b1 = (value >> 0) & 0xff;
    var b2 = (value >> 8) & 0xff;
    var b3 = (value >> 16) & 0xff;
    var b4 = (value >> 24) & 0xff;

    return b1 << 24 | b2 << 16 | b3 << 8 | b4 << 0;
} 

Самый простой способ - использовать метод BinaryPrimitives.ReadInt32BigEndian(ReadOnlySpan), представленный в.NET Standard 2.1.

var number = BinaryPrimitives.ReadInt32BigEndian(waveData[297..291]);

Объявите этот класс:

using static System.Net.IPAddress;

namespace BigEndianExtension
{
    public static class BigEndian
    {
        public static short ToBigEndian(this short value)   => HostToNetworkOrder(value);
        public static int   ToBigEndian(this int value)     => HostToNetworkOrder(value);
        public static long  ToBigEndian(this long value)    => HostToNetworkOrder(value);
        public static short FromBigEndian(this short value) => NetworkToHostOrder(value);
        public static int   FromBigEndian(this int value)   => NetworkToHostOrder(value);
        public static long  FromBigEndian(this long value)  => NetworkToHostOrder(value);
    }
}

Например, создайте форму с кнопкой и многострочным текстовым полем:

using BigEndianExtension;

private void button1_Click(object sender, EventArgs e)
{
    short int16 = 0x1234;
    int int32   = 0x12345678;
    long int64  = 0x123456789abcdef0;
    string text = string.Format("LE:{0:X4}\r\nBE:{1:X4}\r\n", int16, int16.ToBigEndian());

    text += string.Format("LE:{0:X8}\r\nBE:{1:X8}\r\n", int32, int32.ToBigEndian());
    text += string.Format("LE:{0:X16}\r\nBE:{1:X16}\r\n", int64, int64.ToBigEndian());
    textBox1.Text = text;
}
//Some code...

Мне не нравится BitConverterпотому что (как ответил Марк Гравелл) предполагается, что он полагается на системный порядок байтов, а это означает, что технически вы должны выполнять проверку системного байта каждый раз, когда используете BitConverter чтобы убедиться, что вам не нужно инвертировать массив. Как правило, с сохраненными файлами вы обычно знаете порядок байтов, который вы пытаетесь прочитать, и это может не совпадать. Возможно, вы просто обрабатываете форматы файлов со значениями с прямым порядком байтов, например, PNG.

Из-за этого я просто написал свои собственные методы для этого, которые принимают байтовый массив, смещение чтения и длину чтения в качестве аргументов, а также логическое значение для определения обработки порядка байтов и которое использует сдвиг битов для эффективности:

public static UInt64 ReadIntFromByteArray(Byte[] data, Int32 startIndex, Int32 bytes, Boolean littleEndian)
{
    Int32 lastByte = bytes - 1;
    if (data.Length < startIndex + bytes)
        throw new ArgumentOutOfRangeException("startIndex", "Data array is too small to read a " + bytes + "-byte value at offset " + startIndex + ".");
    UInt64 value = 0;
    for (Int32 index = 0; index < bytes; index++)
    {
        Int32 offs = startIndex + (littleEndian ? index : lastByte - index);
        value += (UInt64)(data[offs] << (8 * index));
    }
    return value;
}

Этот код может обрабатывать любое значение от 1 до 8 байтов, как с прямым порядком байтов, так и с прямым порядком байтов. Единственная небольшая особенность использования заключается в том, что вам нужно как указать количество байтов для чтения, так и конкретное приведение результата к нужному типу.

Пример из некоторого кода, где я использовал его для чтения заголовка какого-то проприетарного типа изображения:

Int16 imageWidth = (Int16) ReadIntFromByteArray(fileData, hdrOffset, 2, true);
Int16 imageHeight = (Int16) ReadIntFromByteArray(fileData, hdrOffset + 2, 2, true);

Это будет считывать два последовательных 16-битных целых числа из массива в виде значений со знаком с прямым порядком байтов. Конечно, вы можете просто сделать несколько функций перегрузки для всех возможностей, например так:

public Int16 ReadInt16FromByteArrayLe(Byte[] data, Int32 startIndex)
{
    return (Int16) ReadIntFromByteArray(data, startIndex, 2, true);
}

Но лично я не беспокоился об этом.

И вот то же самое для записи байтов:

public static void WriteIntToByteArray(Byte[] data, Int32 startIndex, Int32 bytes, Boolean littleEndian, UInt64 value)
{
    Int32 lastByte = bytes - 1;
    if (data.Length < startIndex + bytes)
        throw new ArgumentOutOfRangeException("startIndex", "Data array is too small to write a " + bytes + "-byte value at offset " + startIndex + ".");
    for (Int32 index = 0; index < bytes; index++)
    {
        Int32 offs = startIndex + (littleEndian ? index : lastByte - index);
        data[offs] = (Byte) (value >> (8*index) & 0xFF);
    }
}

Единственным требованием здесь является то, что вы должны приводить входной аргумент к 64-битному целому числу без знака при передаче его в функцию.

Я использую следующие вспомогательные функции

public static Int16 ToInt16(byte[] data, int offset)
{
    if (BitConverter.IsLittleEndian)
        return BitConverter.ToInt16(BitConverter.IsLittleEndian ? data.Skip(offset).Take(2).Reverse().ToArray() : data, 0);
    return BitConverter.ToInt16(data, offset);
}
public static Int32 ToInt32(byte[] data, int offset)
{
    if (BitConverter.IsLittleEndian)
        return BitConverter.ToInt32(BitConverter.IsLittleEndian ? data.Skip(offset).Take(4).Reverse().ToArray() : data, 0);
    return BitConverter.ToInt32(data, offset);
}
public static Int64 ToInt64(byte[] data, int offset)
{
    if (BitConverter.IsLittleEndian)
        return BitConverter.ToInt64(BitConverter.IsLittleEndian ? data.Skip(offset).Take(8).Reverse().ToArray() : data, 0);
    return BitConverter.ToInt64(data, offset);
}

Вы также можете использовать библиотеку Джона Скита "Разное", доступную по адресу https://jonskeet.uk/csharp/miscutil/

В его библиотеке много полезных функций. Для преобразования Big/Little endian вы можете проверить MiscUtil/Conversion/EndianBitConverter.cs файл.

var littleEndianBitConverter = new MiscUtil.Conversion.LittleEndianBitConverter();
littleEndianBitConverter.ToInt64(bytes, offset);

var bigEndianBitConverter = new MiscUtil.Conversion.BigEndianBitConverter();
bigEndianBitConverter.ToInt64(bytes, offset);

Его программное обеспечение с 2009 года, но я думаю, что оно все еще актуально.

Похоже на то, что ответил @GeorgePolevoy, ноAsSpan(287)вместо этого следует использовать:

      BinaryPrimitives.ReadInt32BigEndian(waveData.AsSpan(287));

С использованиемAsSpan()По моим тестам, примерно в 2-2,5 раза быстрее, чем без него. Более подробная информация здесь из документации Microsoft.

Вот тест синхронизации для проверки вашей машины:

      Stopwatch stopwatch = new();

byte[] bytes = { 0x00, 0x00, 0x00, 0x00, 0x53, 0xb3, 0xd8 };

stopwatch.Start();
for (int i = 0; i < 1000000; i++)
    BinaryPrimitives.ReadInt32BigEndian(bytes.AsSpan(3));
stopwatch.Stop();
Console.WriteLine($"Elapsed Time: {stopwatch.Elapsed}");

stopwatch.Restart();
for (int i = 0; i < 1000000; i++)
    BinaryPrimitives.ReadInt32BigEndian(bytes[3..]);
stopwatch.Stop();
Console.WriteLine($"Elapsed Time: {stopwatch.Elapsed}");

Если вам больше не понадобится этот перевернутый временный массив, вы можете просто создать его при передаче параметра вместо четырех назначений. Например:

int i = 287;
int value = BitConverter.ToInt32({
    waveData(i + 3),
    waveData(i + 2),
    waveData(i + 1),
    waveData(i)
}, 0);
public static unsafe int Reverse(int value)
{
    byte* p = (byte*)&value;
    return (*p << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}

Если небезопасный разрешено... На основании Marc Gravell Поста

Это изменит встроенные данные, если разрешен небезопасный код...

fixed (byte* wavepointer = waveData)
   new Span<byte>(wavepointer + offset, 4).Reverse();
Другие вопросы по тегам