簡體   English   中英

在字節[]數組中查找第一個特定字節c#

[英]Find First Specific Byte in a Byte[] Array c#

我有一個字節數組,希望找到特定字節的第一次出現(如果有的話)。

你能幫助我一個漂亮,優雅和有效的方法嗎?

 /// Summary
/// Finds the first occurance of a specific byte in a byte array.
/// If not found, returns -1.
public int GetFirstOccurance(byte byteToFind, byte[] byteArray)
{

}
public static int GetFirstOccurance(byte byteToFind, byte[] byteArray)
{
   return Array.IndexOf(byteArray,byteToFind);
}

如果沒有找到它將返回-1

或者Sam指出,一種擴展方法:

public static int GetFirstOccurance(this byte[] byteArray, byte byteToFind)
{
   return Array.IndexOf(byteArray,byteToFind);
}

或者使它通用:

public static int GetFirstOccurance<T>(this T[] array, T element)
{
   return Array.IndexOf(array,element);
}

然后你可以說:

int firstIndex = byteArray.GetFirstOccurance(byteValue);

既然你提到了效率,這里有一些經過深度優化的C#代碼,我寫的是使用本機尋址和最大qword對齊讀取來將內存訪問次數減少8倍。如果有任何更快的方法,我會感到驚訝在.NET中掃描內存中的一個字節。

這將返回從偏移量i (相對於地址src )開始的內存范圍內第一次出現的字節“v”索引 ,並繼續長度c 如果找不到字節v則返回-1

// fast IndexOf byte in memory. (To use this with managed byte[] array, see below)
public unsafe static int IndexOfByte(byte* src, byte v, int i, int c)
{
    ulong t;
    byte* p, pEnd;

    for (p = src + i; ((long)p & 7) != 0; c--, p++)
        if (c == 0)
            return -1;
        else if (*p == v)
            return (int)(p - src);

    ulong r = v; r |= r << 8; r |= r << 16; r |= r << 32;

    for (pEnd = p + (c & ~7); p < pEnd; p += 8)
    {
        t = *(ulong*)p ^ r;
        t = (t - 0x0101010101010101) & ~t & 0x8080808080808080;
        if (t != 0)
        {
            t &= (ulong)-(long)t;
            return (int)(p - src) + dbj8[t * 0x07EDD5E59A4E28C2 >> 58];
        }
    }

    for (pEnd += c & 7; p < pEnd; p++)
        if (*p == v)
            return (int)(p - src);

    return -1;
}

不要被你看到的那個乘法所震驚; 它只執行此函數的每次調用最多一次,以便進行最終的deBruijn查找 用於它的只讀查找表是一個64字節值的簡單共享列表,需要一次性初始化:

// elsewhere in the static class...

readonly static sbyte[] dbj8 =
{
     7, -1, -1, -1, -1,  5, -1, -1, -1,  4, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1,  6, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1,  3, -1, -1, -1, -1, -1, -1,  1, -1,  2,  0, -1, -1,
};

從不訪問-1值,如果需要,可以保留為零,如下面的表格初始化代碼所示,如​​果您願意:

static MyStaticClass()
{
    dbj8 = new sbyte[64];  // initialize the lookup table (alternative to the above)
    dbj8[0x00] = 7;
    dbj8[0x18] = 6;
    dbj8[0x05] = 5;
    dbj8[0x09] = 4;
    dbj8[0x33] = 3;
    dbj8[0x3C] = 2;
    dbj8[0x3A] = 1;
 /* dbj8[0x3D] = 0; */
}

readonly static sbyte[] dbj8, dbj16;

為了完整起見,這里是如何在原始問題中使用OP提供的方法原型的函數。

/// Finds the first occurrence of a specific byte in a byte array.
/// If not found, returns -1.
public static unsafe int GetFirstOccurance(byte byteToFind, byte[] byteArray)
{
    fixed (byte* p = byteArray)
        return IndexOfByte(p, byteToFind, 0, byteArray.Length);
}

討論
我的代碼有點復雜,所以詳細的檢查留給感興趣的讀者練習。 您可以在.NET內部方法Buffer.IndexOfByte中研究另一種關於幫派內存搜索的一般方法,但該代碼與我的相比具有明顯的缺點:

  • 最重要的是,.NET代碼每次只掃描4個字節,而不是像我的那樣掃描8個字節。
  • 這是一種非公開方法,因此您需要使用反射來調用它。
  • .NET代碼有“性能泄漏”,其中t1 != 0檢查給出誤報 ,隨后的四個檢查都被浪費了。 注意他們的“墮落”案例:由於這種誤報,他們需要進行四次最終檢查 - 從而允許墮落 - 以保持正確性,而不是僅僅三次。
  • .NET代碼的誤報是由基於從一個字節到下一個字節的進位的溢出的固有的較低的逐位計算引起的。 這導致了兩個補碼的不對稱性(通過使用常數0x7efefeff0x81010100 )和偶然的“左側出口”(即,丟失)關於最重要字節的信息,這是真正的問題。 相反,我使用下溢計算,使每個字節的計算獨立於其鄰居。 我的方法在所有沒有假陽性或“跌倒”處理的情況下給出了結論性結果。
  • 我的代碼使用無分支技術進行最終查找。 一般認為,少數非分支邏輯運算(在這種情況下加一次乘法)有利於擴展if-else結構的性能,因為后者可能會破壞CPU預測緩存 對於我的8字節掃描器來說,這個問題更為重要,因為在沒有使用查找的情況下,與4字節的組合式掃描器相比,最終檢查中的if-else條件會增加兩倍。

當然,如果你不關心所有這些細節,你可以復制和使用代碼; 我對它進行了詳盡的單元測試,並驗證了所有格式良好的輸入的正確行為。 因此,雖然核心功能可以使用,但您可能希望添加參數檢查。


[編輯:]

String.IndexOf(String s, Char char, int ix_start, int count) ... fast!

因為上面的方法在我的項目中已經成功運行,所以我將其擴展到16位搜索。 以下是適用於搜索16位short,ushort或char原語而不是byte的相同代碼。 這種適應的方法也根據其自身各自的單元測試方法進行獨立驗證。

static MyStaticClass()
{
    dbj16 = new sbyte[64];
 /* dbj16[0x3A] = 0; */
    dbj16[0x33] = 1;
    dbj16[0x05] = 2;
    dbj16[0x00] = 3;
}
readonly static sbyte[] dbj16;

public static int IndexOf(ushort* src, ushort v, int i, int c)
{
    ulong t;
    ushort* p, pEnd;

    for (p = src + i; ((long)p & 7) != 0; c--, p++)
        if (c == 0)
            return -1;
        else if (*p == v)
            return (int)(p - src);

    ulong r = ((ulong)v << 16) | v;
    r |= r << 32;

    for (pEnd = p + (c & ~7); p < pEnd; p += 4)
    {
        t = *(ulong*)p ^ r;
        t = (t - 0x0001000100010001) & ~t & 0x8000800080008000;
        if (t != 0)
        {
            i = dbj16[(t & (ulong)-(long)t) * 0x07EDD5E59A4E28C2 >> 58];
            return (int)(p - src) + i;
        }
    }

    for (pEnd += c & 7; p < pEnd; p++)
        if (*p == v)
            return (int)(p - src);

    return -1;
}

下面是為剩余的16位基元訪問它的各種重載,以及String (顯示的最后一個):

public static int IndexOf(this char[] rg, char v) => IndexOf(rg, v, 0, rg.Length);
public static int IndexOf(this char[] rg, char v, int i, int c = -1)
{
    if (rg != null && (c = c < 0 ? rg.Length - i : c) > 0)
        fixed (char* p = rg)
            return IndexOf((ushort*)p, v, i, c < 0 ? rg.Length - i : c);
    return -1;
}

public static int IndexOf(this short[] rg, short v) => IndexOf(rg, v, 0, rg.Length);
public static int IndexOf(this short[] rg, short v, int i, int c = -1)
{
    if (rg != null && (c = c < 0 ? rg.Length - i : c) > 0)
        fixed (short* p = rg)
            return IndexOf((ushort*)p, (ushort)v, i, c < 0 ? rg.Length - i : c);
    return -1;
}

public static int IndexOf(this ushort[] rg, ushort v) => IndexOf(rg, v, 0, rg.Length);
public static int IndexOf(this ushort[] rg, ushort v, int i, int c = -1)
{
    if (rg != null && (c = c < 0 ? rg.Length - i : c) > 0)
        fixed (ushort* p = rg)
            return IndexOf(p, v, i, c < 0 ? rg.Length - i : c);
    return -1;
}
public static int IndexOf(String s, Char ch, int i = 0, int c = -1)
{
    if (s != null && (c = c < 0 ? s.Length - i : c) > 0)
        fixed (char* p = s)
            return IndexOf((ushort*)p, ch, i, c);
    return -1;
}

請注意, String重載未標記為擴展方法,因為此函數的更高性能替換版本永遠不會以這種方式調用(具有相同名稱的內置方法始終優先於擴展方法)。 要將其用作String實例的擴展,可以更改此方法的名稱。 例如, IndexOf__(this String s,...)會使它出現在Intellisense列表中的內置方法名稱旁邊 ,這可能是一個有用的提示,可以選擇加入。 否則,如果您不需要擴展語法,那么當您想要使用它而不是s.IndexOf(Char ch)時,您可以確保直接將此優化版本稱為其自己類的靜態方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM