10

拡張メソッドを使用して、float配列をバイト配列に変換します。

public static unsafe byte[] ToByteArray(this float[] floatArray, int count)
{
    int arrayLength = floatArray.Length > count ? count : floatArray.Length;
    byte[] byteArray = new byte[4 * arrayLength];
    fixed (float* floatPointer = floatArray)
    {
        fixed (byte* bytePointer = byteArray)
        {
            float* read = floatPointer;
            float* write = (float*)bytePointer;
            for (int i = 0; i < arrayLength; i++)
            {
                *write++ = *read++;
            }
        }
    }
    return byteArray;
}

配列は、要素のタイプと数に関する情報に関連付けられたメモリへのポインタであることを理解しています。また、上記のようにデータをコピーせずにバイト配列との間で変換を行う方法はないように思われます。

私はこれを理解しましたか?データをコピーせずに、ポインタ、型、長さから配列を作成するためにILを作成することさえ不可能でしょうか?

編集:答えてくれてありがとう、私はいくつかの基本を学び、新しいトリックを試してみることができました!

Davy Landmanの答えを最初に受け入れた後、彼の素晴らしいStructLayoutハックはバイト配列をfloat配列に変換しますが、その逆は機能しないことがわかりました。実証するために:

[StructLayout(LayoutKind.Explicit)]
struct UnionArray
{
    [FieldOffset(0)]
    public Byte[] Bytes;

    [FieldOffset(0)]
    public float[] Floats;
}

static void Main(string[] args)
{
    // From bytes to floats - works
    byte[] bytes = { 0, 1, 2, 4, 8, 16, 32, 64 };
    UnionArray arry = new UnionArray { Bytes = bytes };
    for (int i = 0; i < arry.Bytes.Length / 4; i++)
        Console.WriteLine(arry.Floats[i]);

    // From floats to bytes - index out of range
    float[] floats = { 0.1f, 0.2f, 0.3f };
    arry = new UnionArray { Floats = floats };
    for (int i = 0; i < arry.Floats.Length * 4; i++)
        Console.WriteLine(arry.Bytes[i]);
}

CLRは、両方の配列が同じ長さであると見なしているようです。構造体がfloatデータから作成されている場合、バイト配列の長さが短すぎます。

4

7 に答える 7

17

本当に醜いハックを使用して、メモリ操作を使用して配列を一時的に byte[] に変更できます。

これは、データのクローンを作成して反復する必要がないため、非常に高速で効率的です。

このハックは 32 ビット OS と 64 ビット OS の両方でテストしたので、移植可能です。

ソースとサンプルの使用法はhttps://gist.github.com/1050703で管理されていますが、便宜上、ここにも貼り付けます。

public static unsafe class FastArraySerializer
{
    [StructLayout(LayoutKind.Explicit)]
    private struct Union
    {
        [FieldOffset(0)] public byte[] bytes;
        [FieldOffset(0)] public float[] floats;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    private struct ArrayHeader
    {
        public UIntPtr type;
        public UIntPtr length;
    }

    private static readonly UIntPtr BYTE_ARRAY_TYPE;
    private static readonly UIntPtr FLOAT_ARRAY_TYPE;

    static FastArraySerializer()
    {
        fixed (void* pBytes = new byte[1])
        fixed (void* pFloats = new float[1])
        {
            BYTE_ARRAY_TYPE = getHeader(pBytes)->type;
            FLOAT_ARRAY_TYPE = getHeader(pFloats)->type;
        }
    }

    public static void AsByteArray(this float[] floats, Action<byte[]> action)
    {
        if (floats.handleNullOrEmptyArray(action)) 
            return;

        var union = new Union {floats = floats};
        union.floats.toByteArray();
        try
        {
            action(union.bytes);
        }
        finally
        {
            union.bytes.toFloatArray();
        }
    }

    public static void AsFloatArray(this byte[] bytes, Action<float[]> action)
    {
        if (bytes.handleNullOrEmptyArray(action)) 
            return;

        var union = new Union {bytes = bytes};
        union.bytes.toFloatArray();
        try
        {
            action(union.floats);
        }
        finally
        {
            union.floats.toByteArray();
        }
    }

    public static bool handleNullOrEmptyArray<TSrc,TDst>(this TSrc[] array, Action<TDst[]> action)
    {
        if (array == null)
        {
            action(null);
            return true;
        }

        if (array.Length == 0)
        {
            action(new TDst[0]);
            return true;
        }

        return false;
    }

    private static ArrayHeader* getHeader(void* pBytes)
    {
        return (ArrayHeader*)pBytes - 1;
    }

    private static void toFloatArray(this byte[] bytes)
    {
        fixed (void* pArray = bytes)
        {
            var pHeader = getHeader(pArray);

            pHeader->type = FLOAT_ARRAY_TYPE;
            pHeader->length = (UIntPtr)(bytes.Length / sizeof(float));
        }
    }

    private static void toByteArray(this float[] floats)
    {
        fixed(void* pArray = floats)
        {
            var pHeader = getHeader(pArray);

            pHeader->type = BYTE_ARRAY_TYPE;
            pHeader->length = (UIntPtr)(floats.Length * sizeof(float));
        }
    }
}

そして使用法は次のとおりです。

var floats = new float[] {0, 1, 0, 1};
floats.AsByteArray(bytes =>
{
    foreach (var b in bytes)
    {
        Console.WriteLine(b);
    }
});
于 2010-08-26T16:27:43.083 に答える
2

この質問は、float[]をbyte[]に変換する最速の方法は何ですか?の逆です。

私は、データのコピー全体をスキップするために、組合のようなハックで答えました。これは簡単に元に戻すことができます(長さ=長さ* sizeof(Double)。

于 2009-03-07T09:08:26.713 に答える
2

配列間の迅速な変換のために似たようなものを書きました。これは基本的に、見事な解決策というよりも醜い概念実証です。;)

public static TDest[] ConvertArray<TSource, TDest>(TSource[] source)
    where TSource : struct
    where TDest : struct {

    if (source == null)
        throw new ArgumentNullException("source");

        var sourceType = typeof(TSource);
        var destType = typeof(TDest);

        if (sourceType == typeof(char) || destType == typeof(char))
            throw new NotSupportedException(
                "Can not convert from/to a char array. Char is special " +
                "in a somewhat unknown way (like enums can't be based on " +
                "char either), and Marshal.SizeOf returns 1 even when the " +
                "values held by a char can be above 255."
            );

        var sourceByteSize = Buffer.ByteLength(source);
        var destTypeSize = Marshal.SizeOf(destType);
        if (sourceByteSize % destTypeSize != 0)
            throw new Exception(
                "The source array is " + sourceByteSize + " bytes, which can " +
                "not be transfered to chunks of " + destTypeSize + ", the size " +
                "of type " + typeof(TDest).Name + ". Change destination type or " +
                "pad the source array with additional values."
            );

        var destCount = sourceByteSize / destTypeSize;
        var destArray = new TDest[destCount];

        Buffer.BlockCopy(source, 0, destArray, 0, sourceByteSize);

        return destArray;
    }
}
于 2009-03-07T09:15:06.993 に答える
2

はい、型情報とデータは同じメモリブロックにあるため、float配列の型情報を上書きして、システムをバイト配列だと騙さない限り、これは不可能です。それは本当に醜いハックであり、簡単に爆破する可能性があります...

必要に応じて、安全でないコードなしでフロートを変換する方法は次のとおりです。

public static byte[] ToByteArray(this float[] floatArray) {
    int len = floatArray.Length * 4;
    byte[] byteArray = new byte[len];
    int pos = 0;
    foreach (float f in floatArray) {
        byte[] data = BitConverter.GetBytes(f);
        Array.Copy(data, 0, byteArray, pos, 4);
        pos += 4;
    }
    return byteArray;
}
于 2009-03-07T08:04:11.483 に答える
1

同様の質問に対する私の回答を確認する必要があります: float[] を byte[] に変換する最速の方法は何ですか? .

その中には、データをコピーせずに浮動小数点配列をバイト配列として、またはその逆に表示できる移植可能なコード (32/64 ビット互換) があります。これは、私が知っている限り、そのようなことを行うための最速の方法です。

コードだけに興味がある場合は、https://gist.github.com/1050703で管理されています。

于 2011-10-28T13:27:35.323 に答える
1
    public byte[] ToByteArray(object o)
    {
        int size = Marshal.SizeOf(o);
        byte[] buffer = new byte[size];
        IntPtr p = Marshal.AllocHGlobal(size);
        try
        {
            Marshal.StructureToPtr(o, p, false);
            Marshal.Copy(p, buffer, 0, size);
        }
        finally
        {
            Marshal.FreeHGlobal(p);
        }
        return buffer;
    }

これは、オブジェクトをバイト配列に変換するのに役立ちます。

于 2011-03-31T07:41:00.770 に答える