如何使用 bitConverter.ToInt32 方法从 C# 中的大端数据获取小端数据?

发布于 2024-12-17 20:34:25 字数 576 浏览 0 评论 0原文

我正在 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);

还有其他方法可以实现这种转换吗?

I am making application in C# which has a byte array containing hex values.

I am getting data as a big-endian but I want it as a little-endian and I am using Bitconverter.toInt32 method for converting that value to integer.

My problem is that before converting the value, I have to copy that 4 byte data into temporary array from source byte array and then reverse that temporary byte array.

I can't reverse source array because it also contains other data.

Because of that my application becomes slow.

In the code I have one source array of byte as waveData[] which contains a lot of data.

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);

Is there any other method for that conversion?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

折戟 2024-12-24 20:34:25

添加对 System.Memory nuget 的引用并使用 BinaryPrimitives.ReverseEndianness()。

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

它支持有符号和无符号整数(字节/短/整数/长)。

Add a reference to System.Memory nuget and use BinaryPrimitives.ReverseEndianness().

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

It supports both signed and unsigned integers (byte/short/int/long).

属性 2024-12-24 20:34:25

如果您知道数据是大端字节序,也许只需手动执行即可:

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

这也可以在任何 CPU 上可靠地工作。注意 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;

我发现第一个方法的可读性非常高,并且没有分支/复杂的代码,因此它也应该工作得相当快。第二个也可能在某些平台上遇到问题(CPU 已经运行大端)。

If you know the data is big-endian, perhaps just do it manually:

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

this will work reliably on any CPU, too. Note i is your current offset into the buffer.

Another approach would be to shuffle the array:

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;

I find the first immensely more readable, and there are no branches / complex code, so it should work pretty fast too. The second could also run into problems on some platforms (where the CPU is already running big-endian).

╰つ倒转 2024-12-24 20:34:25

在现代的 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);

:)

In modern-day Linq the one-liner and easiest to understand version would be:

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

You could also...

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

:)

々眼睛长脚气 2024-12-24 20:34:25

最直接的方法是使用 .NET Standard 2.1 中引入的 BinaryPrimitives.ReadInt32BigEndian(ReadOnlySpan) 方法

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

The most straightforward way is to use the BinaryPrimitives.ReadInt32BigEndian(ReadOnlySpan) Method introduced in .NET Standard 2.1

var number = BinaryPrimitives.ReadInt32BigEndian(waveData[297..291]);
扭转时空 2024-12-24 20:34:25

干得好

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;
} 

Here you go

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;
} 
拔了角的鹿 2024-12-24 20:34:25

声明此类:

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...

Declare this class:

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);
    }
}

Example, create a form with a button and a multiline textbox:

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...
花开雨落又逢春i 2024-12-24 20:34:25

我使用以下辅助函数

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);
}

I use the following helper functions

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);
}
她如夕阳 2024-12-24 20:34:25

如果您不再需要那个反向的临时数组,您可以在传递参数时创建它,而不是进行四次赋值。例如:

int i = 287;
int value = BitConverter.ToInt32({
    waveData(i + 3),
    waveData(i + 2),
    waveData(i + 1),
    waveData(i)
}, 0);

If you won't ever again need that reversed, temporary array, you could just create it as you pass the parameter, instead of making four assignments. For example:

int i = 287;
int value = BitConverter.ToInt32({
    waveData(i + 3),
    waveData(i + 2),
    waveData(i + 1),
    waveData(i)
}, 0);
孤城病女 2024-12-24 20:34:25

您还可以使用 Jon Skeet“Misc Utils”库,位于 https://jonskeet.uk/csharp/miscutil /

他的库有很多实用函数。对于大/小尾数转换,您可以检查 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 年的,但我想它仍然有用。

You can also use Jon Skeet "Misc Utils" library, available at https://jonskeet.uk/csharp/miscutil/

His library has many utility functions. For Big/Little endian conversions you can check the MiscUtil/Conversion/EndianBitConverter.cs file.

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

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

His software is from 2009 but I guess it's still relevant.

梦断已成空 2024-12-24 20:34:25

我不喜欢 BitConverter,因为(正如 Marc Gravell 回答的那样)它被指定依赖于系统字节顺序,这意味着从技术上讲,您每次使用 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);
    }
}

这里唯一的要求是,在将输入 arg 传递给函数时,必须将其转换为 64 位无符号整数。

I dislike BitConverter, because (as Marc Gravell answered) it is specced to rely on system endianness, meaning you technically have to do a system endianness check every time you use BitConverter to ensure you don't have to reverse the array. And usually, with saved files, you generally know the endianness you're trying to read, and that might not be the same. You might just be handling file formats with big-endian values, too, like, for instance, PNG chunks.

Because of that, I just wrote my own methods for this, which take a byte array, the read offset and read length as arguments, as well as a boolean to specify the endianness handling, and which uses bit shifting for efficiency:

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;
}

This code can handle any value between 1 and 8 bytes, both little-endian and big-endian. The only small usage peculiarity is that you need to both give the amount of bytes to read, and need to specifically cast the result to the type you want.

Example from some code where I used it to read the header of some proprietary image type:

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

This will read two consecutive 16-bit integers off an array, as signed little-endian values. You can of course just make a bunch of overload functions for all possibilities, like this:

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

But personally I didn't bother with that.

And, here's the same for writing bytes:

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);
    }
}

The only requirement here is that you have to cast the input arg to 64-bit unsigned integer when passing it to the function.

逆夏时光 2024-12-24 20:34:25

与 @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}");

Similar to what @GeorgePolevoy answered but AsSpan(287) should be used instead:

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

Using AsSpan() is around 2-2.5x faster from my tests than the one without. More details here from Microsoft documentation.

Here is a timing test to check on your machine:

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(
quot;Elapsed Time: {stopwatch.Elapsed}");

stopwatch.Restart();
for (int i = 0; i < 1000000; i++)
    BinaryPrimitives.ReadInt32BigEndian(bytes[3..]);
stopwatch.Stop();
Console.WriteLine(
quot;Elapsed Time: {stopwatch.Elapsed}");
魂归处 2024-12-24 20:34:25
public static unsafe int Reverse(int value)
{
    byte* p = (byte*)&value;
    return (*p << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}

如果允许不安全...基于 Marc Gravell 的帖子

public static unsafe int Reverse(int value)
{
    byte* p = (byte*)&value;
    return (*p << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}

If unsafe is allowed... Based on Marc Gravell's post

固执像三岁 2024-12-24 20:34:25

如果允许不安全代码,这将反转内联数据......

fixed (byte* wavepointer = waveData)
   new Span<byte>(wavepointer + offset, 4).Reverse();

This will reverse the data inline if unsafe code is allowed...

fixed (byte* wavepointer = waveData)
   new Span<byte>(wavepointer + offset, 4).Reverse();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文