私はC#でアプリケーションを作成しています。そのアプリケーションでは、16進値を含むバイト配列があります。
ここでは、ビッグエンディアンとしてデータを取得していますが、リトルエンディアンとして必要です。
ここでは、その値を整数に変換するためにBitconverter.toInt32
メソッドを使用しています。
しかし、私の問題は、値を変換する前に、その4バイトのデータをソースバイト配列から一時配列にコピーし、次にその一時バイト配列を逆にする必要があるということです。
他のデータも含まれているため、ソース配列を逆にすることはできません。
そのため、私のアプリケーションは遅くなります。コードここでは、waveData []としてバイトのソース配列が1つあります。これには多くのデータが含まれています。
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);
その変換のための他の方法はありますか?
データがビッグエンディアンであることを知っている場合は、おそらく手動で行ってください。
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;
最初のコードは非常に読みやすく、ブランチや複雑なコードがないため、非常に高速に動作するはずです。 2番目は、一部のプラットフォーム(CPUがすでにビッグエンディアンを実行している)で問題が発生する可能性もあります。
現代の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;
}
このクラスを宣言します:
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...
System.Memory nugetへの参照を追加し、BinaryPrimitives.ReverseEndianness()を使用します。
using System.Buffers.Binary;
number = BinaryPrimitives.ReverseEndianness(number);
符号付き整数と符号なし整数(byte/short/int/long)の両方をサポートします。
逆になった一時的な配列がもう必要ない場合は、4つの割り当てを行う代わりに、パラメーターを渡すときに作成することができます。例えば:
int i = 287;
int value = BitConverter.ToInt32({
waveData(i + 3),
waveData(i + 2),
waveData(i + 1),
waveData(i)
}, 0);
https://jonskeet.uk/csharp/miscutil/ で入手可能なJon Skeetの「Misc Utils」ライブラリも使用できます。
彼のライブラリには多くのユーティリティ関数があります。 Big/Littleエンディアン変換の場合、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年のものですが、それでも関連があると思います。
私は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バイトの値を処理できます。唯一の小さな使用法の特徴は、読み取るバイト数を指定する必要があることです。andは結果を必要な型に具体的にキャストする必要があります。
独自の画像タイプのヘッダーを読み取るために使用したコードの例:
Int16 imageWidth = (Int16) ReadIntFromByteArray(fileData, hdrOffset, 2, true);
Int16 imageHeight = (Int16) ReadIntFromByteArray(fileData, hdrOffset + 2, 2, true);
これは、配列から2つの連続する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);
}