[英]C# Find offset of byte pattern, check specific byte, change byte, export part of byte array
[英]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中研究另一種關於幫派內存搜索的一般方法,但該代碼與我的相比具有明顯的缺點:
t1 != 0
檢查給出誤報 ,隨后的四個檢查都被浪費了。 注意他們的“墮落”案例:由於這種誤報,他們需要進行四次最終檢查 - 從而允許墮落 - 以保持正確性,而不是僅僅三次。 0x7efefeff
或0x81010100
)和偶然的“左側出口”(即,丟失)關於最重要字節的信息,這是真正的問題。 相反,我使用下溢計算,使每個字節的計算獨立於其鄰居。 我的方法在所有沒有假陽性或“跌倒”處理的情況下給出了結論性結果。 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.