技术问询:Unity+C#安卓GPS程序中如何读取大文本并存入哈希表?
嘿,恭喜你已经把GPS地点检测的核心功能跑通了!针对你问的「读取大型文本文件并存到哈希表」的需求,我来给你一步步拆解具体实现,结合Unity和C#的特性来做~
为什么用哈希表(Dictionary)?
首先,C#里我们常用Dictionary<TKey, TValue>来实现哈希表功能,它的优势是查找、插入操作接近O(1),比你之前用的List遍历高效太多,尤其是当预设地点数量很大的时候,能大幅提升GPS位置变化时的检测效率。
第一步:定义地点数据类
首先我们需要一个结构化的类来存每个预设地点的信息,替换原来的字符串列表,这样数据更清晰:
[System.Serializable] public class LocationData { public string locationName; // 地点名称 public float latitude; // 纬度 public float longitude; // 经度 public float alertRadius; // 触发提示的半径(单位:米/千米,根据你的GPS逻辑调整) }
第二步:高效读取大型文本文件
大型文本文件不能一次性加载到内存(容易导致内存溢出),所以还是用逐行读取的方式,但要配合using语句自动释放资源,避免内存泄漏。同时要考虑Unity安卓平台的特殊文件读取规则。
情况1:文件在本地存储(比如安卓的外部SD卡)
如果你的大文件是放在用户可访问的本地路径,用StreamReader逐行读取:
private Dictionary<string, LocationData> _locationHashTable = new Dictionary<string, LocationData>(); public void LoadLargeLocationFile(string localFilePath) { // 先清空哈希表,避免重复加载 _locationHashTable.Clear(); // using语句会自动释放StreamReader资源,不用手动Close using (StreamReader reader = new StreamReader(localFilePath)) { string line; // 如果你的文本文件第一行是表头(比如"名称,纬度,经度,半径"),可以先跳过 // reader.ReadLine(); // 逐行读取直到文件末尾 while ((line = reader.ReadLine()) != null) { // 拆分每行数据(假设你的文本格式是用逗号分隔:"XX公园,39.9042,116.4074,500") string[] dataParts = line.Split(','); if (dataParts.Length != 4) { Debug.LogWarning($"跳过格式错误的行:{line}"); continue; } // 安全解析数值(用TryParse避免格式错误导致崩溃) if (float.TryParse(dataParts[1], out float lat) && float.TryParse(dataParts[2], out float lon) && float.TryParse(dataParts[3], out float radius)) { LocationData location = new LocationData { locationName = dataParts[0], latitude = lat, longitude = lon, alertRadius = radius }; // 生成唯一Key(避免重复地点覆盖,这里用名称+经纬度组合) string uniqueKey = $"{dataParts[0]}_{lat}_{lon}"; if (!_locationHashTable.ContainsKey(uniqueKey)) { _locationHashTable.Add(uniqueKey, location); } else { Debug.LogWarning($"发现重复地点Key:{uniqueKey},已跳过"); } } else { Debug.LogWarning($"跳过解析失败的行:{line}"); } } } Debug.Log($"大文件加载完成,共存入{_locationHashTable.Count}个地点"); }
情况2:文件在Unity的StreamingAssets目录
安卓平台下,StreamingAssets里的文件是打包在APK中的,无法直接用File类读取,需要用UnityWebRequest来加载:
public IEnumerator LoadLargeLocationFromStreamingAssets(string fileName) { _locationHashTable.Clear(); string filePath = Path.Combine(Application.streamingAssetsPath, fileName); if (Application.platform == RuntimePlatform.Android) { using (UnityWebRequest webRequest = UnityWebRequest.Get(filePath)) { yield return webRequest.SendWebRequest(); if (webRequest.result != UnityWebRequest.Result.Success) { Debug.LogError($"加载文件失败:{webRequest.error}"); yield break; } // 将加载到的文本按行拆分 string[] allLines = webRequest.downloadHandler.text.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); // 遍历每行解析(逻辑和上面一致) foreach (string line in allLines) { string[] dataParts = line.Split(','); if (dataParts.Length != 4) { Debug.LogWarning($"跳过格式错误的行:{line}"); continue; } if (float.TryParse(dataParts[1], out float lat) && float.TryParse(dataParts[2], out float lon) && float.TryParse(dataParts[3], out float radius)) { LocationData location = new LocationData { locationName = dataParts[0], latitude = lat, longitude = lon, alertRadius = radius }; string uniqueKey = $"{dataParts[0]}_{lat}_{lon}"; if (!_locationHashTable.ContainsKey(uniqueKey)) { _locationHashTable.Add(uniqueKey, location); } } } } } else { // 非安卓平台直接用StreamReader读取 using (StreamReader reader = new StreamReader(filePath)) { string line; while ((line = reader.ReadLine()) != null) { // 同样的解析逻辑,和上面的本地文件读取一致 string[] dataParts = line.Split(','); if (dataParts.Length != 4) continue; if (float.TryParse(dataParts[1], out float lat) && float.TryParse(dataParts[2], out float lon) && float.TryParse(dataParts[3], out float radius)) { LocationData location = new LocationData { locationName = dataParts[0], latitude = lat, longitude = lon, alertRadius = radius }; string uniqueKey = $"{dataParts[0]}_{lat}_{lon}"; if (!_locationHashTable.ContainsKey(uniqueKey)) { _locationHashTable.Add(uniqueKey, location); } } } } } Debug.Log($"StreamingAssets文件加载完成,共存入{_locationHashTable.Count}个地点"); }
第三步:GPS位置变化时的高效检测
现在你可以用哈希表来存储所有地点,当GPS位置更新时,遍历哈希表中的地点计算距离(或者如果数据量极大,可以考虑用空间索引优化,比如划分网格,但一般几万条数据遍历也没问题):
public void CheckNearbyLocations(float currentLat, float currentLon) { foreach (var kvp in _locationHashTable) { LocationData location = kvp.Value; // 计算当前位置和预设地点的距离(这里用简化的平面距离公式,精确的话用Haversine公式) float distance = CalculateDistance(currentLat, currentLon, location.latitude, location.longitude); if (distance <= location.alertRadius) { // 触发提示逻辑 Debug.Log($"靠近预设地点:{location.locationName}"); // 你的提示代码(比如弹窗、震动) } } } // 可选:实现更精确的球面距离计算(Haversine公式) private float CalculateDistance(float lat1, float lon1, float lat2, float lon2) { const float earthRadiusKm = 6371f; float dLat = (lat2 - lat1) * Mathf.Deg2Rad; float dLon = (lon2 - lon1) * Mathf.Deg2Rad; float a = Mathf.Sin(dLat / 2) * Mathf.Sin(dLat / 2) + Mathf.Cos(lat1 * Mathf.Deg2Rad) * Mathf.Cos(lat2 * Mathf.Deg2Rad) * Mathf.Sin(dLon / 2) * Mathf.Sin(dLon / 2); float c = 2 * Mathf.Atan2(Mathf.Sqrt(a), Mathf.Sqrt(1 - a)); // 返回距离(单位:千米,转米的话乘1000) return earthRadiusKm * c * 1000; }
一些注意事项
- 文本格式一致性:如果你的地点名称里包含逗号,建议换用其他分隔符(比如
|),或者用专业的CSV解析库(比如CsvHelper,导入Unity即可使用)。 - 安卓权限:如果读取外部存储的文件,需要在AndroidManifest.xml中添加
READ_EXTERNAL_STORAGE权限(Android 13+还需要READ_MEDIA_IMAGES等)。 - 内存优化:如果文件特别大(比如几十万条数据),可以考虑分批次加载,或者在后台线程加载(Unity里用
Task.Run注意线程安全)。 - 重复数据处理:根据你的需求,要么跳过重复数据,要么覆盖旧数据,调整
ContainsKey的判断逻辑即可。
内容的提问来源于stack exchange,提问作者Hargy001
相关产品推荐
相关产品推荐

