You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

技术问询: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:58:05