C# .NET控制台应用:读取车辆位置dat文件失败,无法查找最近车辆
问题解决:二进制文件读取与高效最近车辆位置查找
一、解决二进制文件读取错误
你用StreamReader读取二进制文件是错误的,二进制文件不能按文本编码解析,这就是你看到乱码的原因。必须用BinaryReader按照给定的二进制结构逐字段读取。
正确的文件读取代码
首先定义对应的数据模型:
public class VehiclePosition { public int PositionId { get; set; } public string VehicleRegistration { get; set; } public float Latitude { get; set; } public float Longitude { get; set; } public ulong RecordedTimeUTC { get; set; } }
然后编写读取逻辑:
public static List<VehiclePosition> ReadVehiclePositions(string filePath) { var positions = new List<VehiclePosition>(); using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) using (var reader = new BinaryReader(stream)) { while (stream.Position < stream.Length) { // 读取PositionId(Int32,4字节) var positionId = reader.ReadInt32(); // 读取null终止的ASCII字符串 var registrationBytes = new List<byte>(); byte b; while ((b = reader.ReadByte()) != 0) { registrationBytes.Add(b); } var registration = Encoding.ASCII.GetString(registrationBytes.ToArray()); // 读取Latitude(Float,4字节) var latitude = reader.ReadSingle(); // 读取Longitude(Float,4字节) var longitude = reader.ReadSingle(); // 读取RecordedTimeUTC(UInt64,8字节) var recordedTime = reader.ReadUInt64(); positions.Add(new VehiclePosition { PositionId = positionId, VehicleRegistration = registration, Latitude = latitude, Longitude = longitude, RecordedTimeUTC = recordedTime }); } } return positions; }
二、高效最近位置查找方案
基准测试是10次遍历200万条数据,时间复杂度为O(10*N),我们可以用KD-Tree构建空间索引,将查询时间降到O(logN),整体时间复杂度为O(N logN)(构建索引) + O(10 logN)(查询),远快于基准方案。
1. 实现KD-Tree用于二维坐标最近邻查找
public class KdNode { public VehiclePosition Position { get; set; } public KdNode Left { get; set; } public KdNode Right { get; set; } public int Axis { get; set; } // 0=Latitude, 1=Longitude } public class KdTree { private KdNode _root; public KdTree(List<VehiclePosition> positions) { _root = BuildTree(positions, 0); } private KdNode BuildTree(List<VehiclePosition> points, int depth) { if (points.Count == 0) return null; int axis = depth % 2; // 按当前轴排序 var sortedPoints = axis == 0 ? points.OrderBy(p => p.Latitude).ToList() : points.OrderBy(p => p.Longitude).ToList(); int medianIndex = sortedPoints.Count / 2; var medianPoint = sortedPoints[medianIndex]; return new KdNode { Position = medianPoint, Axis = axis, Left = BuildTree(sortedPoints.Take(medianIndex).ToList(), depth + 1), Right = BuildTree(sortedPoints.Skip(medianIndex + 1).ToList(), depth + 1) }; } public VehiclePosition FindNearest(float targetLat, float targetLon) { var nearest = new KeyValuePair<KdNode, double>(); SearchNearest(_root, targetLat, targetLon, ref nearest); return nearest.Key?.Position; } private void SearchNearest(KdNode node, float targetLat, float targetLon, ref KeyValuePair<KdNode, double> nearest) { if (node == null) return; double currentDistance = CalculateDistanceSquared(node.Position.Latitude, node.Position.Longitude, targetLat, targetLon); // 更新最近点 if (!nearest.Key.HasValue || currentDistance < nearest.Value) { nearest = new KeyValuePair<KdNode, double>(node, currentDistance); } // 确定搜索方向 int axis = node.Axis; double targetValue = axis == 0 ? targetLat : targetLon; double nodeValue = axis == 0 ? node.Position.Latitude : node.Position.Longitude; KdNode firstBranch = targetValue < nodeValue ? node.Left : node.Right; KdNode secondBranch = targetValue < nodeValue ? node.Right : node.Left; // 先搜索当前分支 SearchNearest(firstBranch, targetLat, targetLon, ref nearest); // 检查另一分支是否可能有更近的点 double axisDistanceSquared = Math.Pow(targetValue - nodeValue, 2); if (axisDistanceSquared < nearest.Value) { SearchNearest(secondBranch, targetLat, targetLon, ref nearest); } } // 计算平面距离的平方(避免开根号,提升速度,比较距离时等价) private double CalculateDistanceSquared(float lat1, float lon1, float lat2, float lon2) { double dLat = lat1 - lat2; double dLon = lon1 - lon2; return dLat * dLat + dLon * dLon; } // 如果需要精确球面距离,用Haversine公式: // private double CalculateHaversineDistance(float lat1, float lon1, float lat2, float lon2) // { // const double R = 6371000; // 地球半径,单位米 // double dLat = ToRadians(lat2 - lat1); // double dLon = ToRadians(lon2 - lon1); // double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + // Math.Cos(ToRadians(lat1)) * Math.Cos(ToRadians(lat2)) * // Math.Sin(dLon / 2) * Math.Sin(dLon / 2); // double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a)); // return R * c; // } // private double ToRadians(double degrees) => degrees * Math.PI / 180; }
2. 完整的主程序逻辑
class Program { static void Main(string[] args) { string dataFilePath = @"C:\Users\Downloads\VehiclePositions_DataFile\VehiclePositions.dat"; // 1. 读取所有车辆位置 Console.WriteLine("正在读取数据文件..."); var allPositions = ReadVehiclePositions(dataFilePath); Console.WriteLine($"读取完成,共{allPositions.Count}条记录"); // 2. 构建KD-Tree索引 Console.WriteLine("正在构建空间索引..."); var kdTree = new KdTree(allPositions); Console.WriteLine("索引构建完成"); // 3. 定义需要查询的10个坐标(修正了你代码中Lat/Lon颠倒的问题) var targetCoords = new List<(float Latitude, float Longitude)> { (34.544909f, -102.100843f), (32.345544f, -99.123124f), (33.234235f, -100.214124f), (35.195739f, -95.348899f), (31.895839f, -97.789573f), (32.895839f, -101.789573f), (34.115839f, -100.225732f), (32.335839f, -99.992232f), (33.535339f, -94.792232f), (32.234235f, -100.22222f) }; // 4. 执行查询并输出结果 Console.WriteLine("\n开始查询最近车辆位置:"); for (int i = 0; i < targetCoords.Count; i++) { var coord = targetCoords[i]; var nearest = kdTree.FindNearest(coord.Latitude, coord.Longitude); Console.WriteLine($"坐标{i+1} ({coord.Latitude}, {coord.Longitude}) 的最近车辆:"); Console.WriteLine($" PositionId: {nearest.PositionId}"); Console.WriteLine($" 车牌号: {nearest.VehicleRegistration}"); Console.WriteLine($" 坐标: ({nearest.Latitude}, {nearest.Longitude})"); Console.WriteLine($" 记录时间(UTC秒): {nearest.RecordedTimeUTC}\n"); } } // 这里粘贴之前的ReadVehiclePositions方法 public static List<VehiclePosition> ReadVehiclePositions(string filePath) { // ... 代码同上 ... } }
三、关键优化点说明
- 二进制文件读取:用
BinaryReader严格按照字段类型和顺序读取,避免文本解析的乱码问题。 - 空间索引(KD-Tree):仅需一次构建索引,后续每个查询的时间复杂度为O(logN),相比基准的O(N)查询,200万条数据下速度提升非常明显。
- 距离计算优化:使用距离平方代替实际距离,避免开根号运算,减少计算开销。如果需要精确的球面距离,可以替换为Haversine公式。
- 数据模型修正:修正了你代码中Latitude和Longitude颠倒的错误,确保坐标匹配。
内容的提问来源于stack exchange,提问作者Aubz
相关产品推荐
相关产品推荐

