Как получить данные с прямым порядком байтов из 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();