项目需要从实时单向数据流中读取和筛选数据,即当遇到标志数据时,执行某些操作。所有数据只能读一次,不能回溯。我们的场景是监听串口,然后根据监听结果,读取后续数据。
上午写了个算法程序:从实时数据流中搜索数据,监控实时数据流中的数据,发现数据时立即做出应对。然后,写完了之后,总觉得性能有缺陷。仔细考虑了一下,发现问题在于其数据搜索算法不太好:
1)从实时数据流中读入数据;
2)把数据加入缓存;
3)对比缓存和目标数据;
在那个算法中,需要维护一份(同目标数据一样大的)缓存,然后比较的时候,回溯缓存。
我就是在纠结与这个缓存的问题,然后想了一个新的算法:
1)从实时数据流中读入数据;
2)比较数据和目标数据:
- 首字符匹配的,把结果加入队列中;
- 二字符后匹配的,继续;
- 二字符后不匹配的,从队列中删除;
经测试,性能提高超过一半。
上代码,先是适合实时数据流的版本:
public static bool Search(System.IO.Stream stream, byte[] findBytes, int timeout)
{
var bytesLength = findBytes.Length;
var matchQueue = new int[bytesLength];
var matchCount = 0;
var expired = false;
var expires = DateTime.Now.AddMilliseconds(timeout);
var timer = new System.Timers.Timer(10);
timer.Elapsed += (sender, e) =>
{
expired = DateTime.Now > expires;
};
timer.Start();
while (!expired)
{
// 从数据流中读取一个字符
byte b = 0;
try
{
var n = stream.ReadByte();
// 若没有读到新数据,则继续尝试,直到超时
if (n < 0) continue;
else b = (byte)n;
}
catch
{
continue;
}
// 递增匹配队列
for (var i = 0; i < matchCount; i++)
{
matchQueue[i]++;
}
// 如果匹配第一个字符,那么加入匹配队列中
if (b == findBytes[0])
{
matchQueue[matchCount++] = 0;
}
// 否则依次判断每一个队列中的匹配结果是否依旧匹配
else
{
var i = 0;
while (i < matchCount)
{
if (b == findBytes[matchQueue[i]])
{
// 当某一匹配结果匹配了全部字符,则返回结果
if (matchQueue[i] >= bytesLength - 1)
{
timer.Stop();
timer.Close();
return true;
}
i++;
}
else
{
// 对于不再匹配的,从匹配队列中删除
for (var j = i; j < matchCount - 1; j++)
{
matchQueue[j] = matchQueue[j + 1];
}
matchCount--;
}
}
}
}
timer.Stop();
timer.Close();
return false;
}
然后是适合文件流的版本:
public static bool Search(System.IO.Stream stream, byte[] findBytes)
{
var bytesLength = findBytes.Length;
var matchQueue = new int[bytesLength];
var matchCount = 0;
while (true)
{
// 从数据流中读取一个字符
byte b = 0;
try
{
var n = stream.ReadByte();
if (n < 0) break;
else b = (byte)n;
}
catch
{
break;
}
// 递增匹配队列
for (var i = 0; i < matchCount; i++)
{
matchQueue[i]++;
}
// 如果匹配第一个字符,那么加入匹配队列中
if (b == findBytes[0])
{
matchQueue[matchCount++] = 0;
}
// 否则依次判断每一个队列中的匹配结果是否依旧匹配
else
{
var i = 0;
while (i < matchCount)
{
if (b == findBytes[matchQueue[i]])
{
// 当某一匹配结果匹配了全部字符,则返回结果
if (matchQueue[i] >= bytesLength - 1) return true;
i++;
}
else
{
// 对于不再匹配的,从匹配队列中删除
for (var j = i; j < matchCount - 1; j++)
{
matchQueue[j] = matchQueue[j + 1];
}
matchCount--;
}
}
}
}
return false;
}
使用文件流测试了两个算法:
文件大小 | 纯扫描文件耗时 | 算法1耗时 | 算法2耗时 |
100K | 3ms | 22ms | 10ms |
1000M | 881ms | 4956ms | 1265ms |
说明:
算法1,指的是从实时数据流中搜索数据文章所描述的算法;
算法2,指的是本文描述的算法;
纯扫描文件耗时,指的是打开文件,并逐一读入全部文件字节消耗的时间;
算法1耗时和算法2耗时,指的是使用算法程序搜索成功靠后的某一个字符串的时间;
100K的测试文件是ASCII的,1G的测试文件是Unicode的。
根据上表的测试结果,我对这个算法的性能还算满意,只是需要更多测试,以保证其实用性和稳健性,
以上是本文全部内容。