我有一个字节数组,希望找到特定字节的第一次出现(如果有的话)。
你们能用一种优雅而有效的方式来帮助我吗?
/// 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)
{
}
答案 0 :(得分:24)
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);
答案 1 :(得分:11)
答案 2 :(得分:0)
由于您提到了效率,这里有一些经过大量优化的C#代码我已经编写过使用本机寻址和最大qword对齐读取来将内存访问次数减少8倍如果有更快的方法在 .NET 中扫描内存中的字节,我会感到惊讶。
这将返回从offset i
开始的内存范围内第一次出现的字节&#39; v&#39; 的索引(相对于地址src
),并继续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 lookup。用于它的只读查找表是一个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
检查提供误报的地方,以及后面的四项检查都被浪费了。注意他们的&#34;堕落&#34;案例:由于这种假阳性,他们需要进行四次最终检查 - 从而允许堕落 - 以保持正确性,而不是仅仅三次。0x7efefeff
或0x81010100
来证明)和偶尔出现的左边出口&#34;关于最重要字节的信息(即丢失),这是真正的问题。相反,我使用 下溢 计算,这使得每个字节的计算独立于其邻居&#39;。我的方法在所有没有误报的情况下给出了结论性的结果,或者#34;直播&#34;处理if-else
结构的性能,因为后者可能会破坏CPU predictive caching。对于我的8字节扫描程序来说,这个问题更为重要,因为在不使用查找的情况下,我在最终检查中的if-else
条件是4字节组合扫描程序的两倍{/ li}。当然,如果你不关心所有这些细节,你可以复制并使用代码;我对其进行了彻底的单元测试,并验证了所有格式良好的输入的正确行为。因此,虽然核心功能可以使用,但您可能希望添加参数检查。
[编辑:]
String.IndexOf(String s, Char char, int ix_start, int count) ... fast!
由于上述方法在我的项目中已经成功运行,因此我将其扩展为涵盖16位搜索。以下是适用于搜索 16位短,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)
时,您可以确保直接将此优化版本称为其自己类的静态方法。