使用LINQ在字节数组中搜索以某个字节开始/停止的所有子数组

我正在处理COM端口应用程序,我们有一个定义的可变长度数据包结构,我正在与微控制器通信。 数据包具有开始和停止字节的分隔符。 麻烦的是,有时读缓冲区可能包含无关的字符。 看起来我总是得到整个数据包,只是在实际数据之前/之后的一些额外的喋喋不休。 所以我有一个缓冲区,只要从COM端口收到新数据,我就会附加数据。 搜索此缓冲区以查找可能出现的数据包的最佳方法是什么? 例如:

假设我的数据包分隔符是0xFF ,我有一个数组

 { 0x00, 0xFF, 0x02, 0xDA, 0xFF, 0x55, 0xFF, 0x04 } 

如何创建一个函数/ LINQ-statment来返回以分隔符开头和结尾的所有子数组(几乎就像带有通配符的滑动相关器)?

该示例将返回以下3个数组:

 {0xFF, 0x02, 0xDA, 0xFF}, {0xFF, 0x55, 0xFF}, and {0xFF, 0x02, 0xDA, 0xFF, 0x55, 0xFF} 

以下是使用LINQ执行此操作的方法…

 int[] list = new int[] { 0x00, 0xFF, 0x02, 0xDA, 0xFF, 0x55, 0xFF, 0x04 }; int MAXLENGTH = 10; var windows = list.Select((element, i) => list.Skip(i).Take(MAXLENGTH)); var matched = windows.Where(w => w.First() == 0xFF); var allcombinations = matched.SelectMany(m => Enumerable.Range(1, m.Count()) .Select(i => m.Take(i)).Where(x => x.Count() > 2 && x.Last() == 0xFF)); 

或者使用索引:

 int length = list.Count(); var indexes = Enumerable.Range(0, length) .SelectMany(i => Enumerable.Range(3, Math.Min(length-i, MAXLENGTH)) .Select(count => new {i, count})); var results = indexes.Select(index => list.Skip(index.i).Take(index.count)) .Where(x => x.First() == 0xFF && x.Last() == 0xFF); 

虽然Trystan的答案在技术上是正确的,但他正在制作大量原始arrays的副本。 如果起始数组很大并且有一堆分隔符,则会很快变大。 这种方法通过仅使用原始数组和正在评估的当前段的数组来避免大量内存消耗。

 public static List> GetSubArrays(this byte[] array, byte delimeter) { if (array == null) throw new ArgumentNullException("array"); List> retval = new List>(); for (int i = 0; i < array.Length; i++) { if (array[i] == delimeter) { for (int j = i + 1; j < array.Length; j++) { if (array[j] == delimeter) { retval.Add(new ArraySegment(array, i + 1, j - i - 1)); } } } } return retval; } 

可以这样使用:

 static void Main(string[] args) { byte[] arr = new byte[] { 0x00, 0xFF, 0x02, 0xDA, 0xFF, 0x55, 0xFF, 0x04 }; List> retval = GetSubArrays(arr, 0xFF); // this also works (looks like LINQ): //List> retval = arr.GetSubArrays(0xFF); byte[] buffer = new byte[retval.Select(x => x.Count).Max()]; foreach (var x in retval) { Buffer.BlockCopy(x.Array, x.Offset, buffer, 0, x.Count); Console.WriteLine(String.Join(", ", buffer.Take(x.Count).Select(b => b.ToString("X2")).ToArray())); } Console.ReadLine(); } 

如果你真的想使用LINQ,这应该可以很快地工作(即使没有像一个好的for循环那么快):

 public static IEnumerable GetPackets(this IList buffer, T delimiter) { // gets delimiters' indexes var delimiterIdxs = Enumerable.Range(0, buffer.Count()) .Where(i => buffer[i].Equals(delimiter)) .ToArray(); // creates a list of delimiters' indexes pair (startIdx,endIdx) var dlmtrIndexesPairs = delimiterIdxs.Take(delimiterIdxs.Count() - 1) .SelectMany( (startIdx, idx) => delimiterIdxs.Skip(idx + 1) .Select(endIdx => new { startIdx, endIdx }) ); // creates array of packets var packets = dlmtrIndexesPairs.Select(p => buffer.Skip(p.startIdx) .Take(p.endIdx - p.startIdx + 1) .ToArray()) .ToArray(); return packets; } 

我不会尝试用linq这样做,所以这里是一个常规方法,返回你想要的相同输出。

 public List GetSubArrays(byte[] array, byte delimeter) { if (array == null) throw new ArgumentNullException("array"); List subArrays = new List(); for (int i = 0; i < array.Length; i++) { if (array[i] == delimeter && i != array.Length - 1) { List subList = new List() { delimeter }; for (int j = i+1; j < array.Length; j++) { subList.Add(array[j]); if (array[j] == delimeter) { subArrays.Add(subList.ToArray()); } } } } return subArrays; } 

如果它必须是就地lambda表达式,那么只需将第一行更改为(byte[] array, byte delimeter) => (不带方法修饰符和名称)并以此方式调用它。

虽然分隔符结构看起来有点模糊,但我不会使用linq并执行类似下面的操作(不执行大量测试)。 它将返回所有子集(由分隔符包围的字节),不包括分隔符(无论如何它都是给定的,为什么要包括它?)。 它也不返回结果的并集,但总是可以手动组合。

 public IEnumerable GetArrays(byte[] data, byte delimiter) { List arrays = new List(); int start = 0; while (start >= 0 && (start = Array.IndexOf(data, delimiter, start)) >= 0) { start++; if (start >= data.Length - 1) { break; } int end = Array.IndexOf(data, delimiter, start); if (end < 0) { break; } byte[] sub = new byte[end - start]; Array.Copy(data, start, sub, 0, end - start); arrays.Add(sub); start = end; } return arrays; } 

您可以使用Linq聚合器执行此操作,但它不如此处建议的其他解决方案简单明了,还必须添加一个特殊情况来覆盖已完成的数组,如上所述。

 byte[] myArray = new byte[] { 0x00, 0xFF, 0x02, 0xDA, 0xFF, 0x55, 0xFF, 0x04 }; var arrayList = myArray.Aggregate( new { completedLists = new List>(), activeList = new List() }, (seed, s) => { if (s == 0xFF) { if (seed.activeList.Count == 0) { seed.activeList.Add(s); } else { seed.activeList.Add(s); var combinedLists = new List>(); foreach (var l in seed.completedLists) { var combinedList = new List(l); combinedList.AddRange(seed.activeList.Skip(1)); combinedLists.Add(combinedList); } seed.completedLists.AddRange(combinedLists); seed.completedLists.Add(new List(seed.activeList)); seed.activeList.Clear(); seed.activeList.Add(s); } } else { if (seed.activeList.Count > 0) seed.activeList.Add(s); } return seed; }).completedLists;