2020 年 6 月 2 日更新:从 netstandard2.1 开始,您可以将字符串替换为 ReadOnlySpan 并执行无需分配的任务。见https://docs.microsoft.com/en-us/dotnet/api/system.memoryextensions?view=netcore-3.1
你在问how could I manage strings without allocating space?。这是一个答案:你 always can use stackalloc 在没有 GC 压力的情况下在堆栈上分配 char[] 数组,然后创建最终字符串(如果需要)using char* 构造函数。但要小心,因为它是不安全的,而且你不可能只分配一个公共的char[] 或StringBuilder,因为 gen0 的收集几乎没有成本。
您有大量的代码,例如 Words[3].Substring(2).ToString ().Split ('.'),这些代码非常占用内存。只要修好它,你就是金子。但如果对你没有帮助,你必须拒绝使用Substring和其他分配内存的方法,并使用你自己的解析器。
让我们开始优化吧。首先,我们可以修复所有其他分配。你说你已经做过了,但这是我的变种:
private static (double Latitude, double Longitude)? GetCoordinates(string input)
{
// Divide the sentence into words
string[] words = input.Split(',');
// Do we have enough values to describe our location?
if (words[3] == "" || words[4] == "" || words[5] == "" || words[6] == "")
return null;
var latitude = ParseCoordinate(words[3]);
var longitude = ParseCoordinate(words[5]);
return (latitude, longitude);
}
private static double ParseCoordinate(string coordinateString)
{
double wholeValue = double.Parse(coordinateString, NmeaCultureInfo);
int integerPart = (int) wholeValue;
int degrees = integerPart / 100;
int minutes = integerPart % 100;
double seconds = (wholeValue - integerPart) * 60;
return degrees + minutes / 60.0 + seconds / 3600.0;
}
好的,我们假设它仍然很慢,我们想进一步优化它。首先,我们应该替换这个条件:
if (words[3] == "" || words[4] == "" || words[5] == "" || words[6] == "")
return null;
我们在这里做什么?我们只想知道字符串是否包含一些值。我们可以在不解析字符串的情况下研究它。通过进一步的优化,如果出现问题,我们根本不会解析字符串。它可能看起来像:
private static (string LatitudeString, string LongitudeString)? ParseCoordinatesStrings(string input)
{
int latitudeIndex = -1;
for (int i = 0; i < 3; i++)
{
latitudeIndex = input.IndexOf(',', latitudeIndex + 1);
if (latitudeIndex < 0)
return null;
}
int latitudeEndIndex = input.IndexOf(',', latitudeIndex + 1);
if (latitudeEndIndex < 0 || latitudeEndIndex - latitudeIndex <= 1)
return null; // has no latitude
int longitudeIndex = input.IndexOf(',', latitudeEndIndex + 1);
if (longitudeIndex < 0)
return null;
int longitudeEndIndex = input.IndexOf(',', longitudeIndex + 1);
if (longitudeEndIndex < 0 || longitudeEndIndex - longitudeIndex <= 1)
return null; // has no longitude
string latitudeString = input.Substring(latitudeIndex + 1, latitudeEndIndex - latitudeIndex - 1);
string longitudeString = input.Substring(longitudeIndex + 1, longitudeEndIndex - longitudeIndex - 1);
return (latitudeString, longitudeString);
}
现在,将它们组合在一起:
using System;
using System.Globalization;
namespace SO43746933
{
class Program
{
private static readonly CultureInfo NmeaCultureInfo = CultureInfo.InvariantCulture;
static void Main(string[] args)
{
string input =
"$GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62 $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47 $GPGSA,A,3,32,27,03,193,29,23,19,16,21,31,14,,1.18,0.51,1.07*35";
var newCoordinates = GetCoordinatesNew(input);
var oldCoorinates = GetCoordinatesOld(input);
if (newCoordinates == null || oldCoorinates == null)
{
throw new InvalidOperationException("should never throw");
}
Console.WriteLine("Latitude: {0}\t\tLongitude:{1}", newCoordinates.Value.Latitude, newCoordinates.Value.Longitude);
Console.WriteLine("Latitude: {0}\t\tLongitude:{1}", oldCoorinates.Value.Latitude, oldCoorinates.Value.Longitude);
}
private static (double Latitude, double Longitude)? GetCoordinatesNew(string input)
{
// Divide the sentence into words
var coordinateStrings = ParseCoordinatesStrings(input);
// Do we have enough values to describe our location?
if (coordinateStrings == null)
return null;
var latitude = ParseCoordinate(coordinateStrings.Value.LatitudeString);
var longitude = ParseCoordinate(coordinateStrings.Value.LongitudeString);
return (latitude, longitude);
}
private static (string LatitudeString, string LongitudeString)? ParseCoordinatesStrings(string input)
{
int latitudeIndex = -1;
for (int i = 0; i < 3; i++)
{
latitudeIndex = input.IndexOf(',', latitudeIndex + 1);
if (latitudeIndex < 0)
return null;
}
int latitudeEndIndex = input.IndexOf(',', latitudeIndex + 1);
if (latitudeEndIndex < 0 || latitudeEndIndex - latitudeIndex <= 1)
return null; // has no latitude
int longitudeIndex = input.IndexOf(',', latitudeEndIndex + 1);
if (longitudeIndex < 0)
return null;
int longitudeEndIndex = input.IndexOf(',', longitudeIndex + 1);
if (longitudeEndIndex < 0 || longitudeEndIndex - longitudeIndex <= 1)
return null; // has no longitude
string latitudeString = input.Substring(latitudeIndex + 1, latitudeEndIndex - latitudeIndex - 1);
string longitudeString = input.Substring(longitudeIndex + 1, longitudeEndIndex - longitudeIndex - 1);
return (latitudeString, longitudeString);
}
private static double ParseCoordinate(string coordinateString)
{
double wholeValue = double.Parse(coordinateString, NmeaCultureInfo);
int integerPart = (int) wholeValue;
int degrees = integerPart / 100;
int minutes = integerPart % 100;
double seconds = (wholeValue - integerPart) * 60;
return degrees + minutes / 60.0 + seconds / 3600.0;
}
private static (double Latitude, double Longitude)? GetCoordinatesOld(string input)
{
// Divide the sentence into words
string[] Words = input.Split(',');
// Do we have enough values to describe our location?
if (!(Words[3] != "" && Words[4] != "" &
Words[5] != "" && Words[6] != ""))
return null;
// example 5230.5900,N
// 52°30.5900\N
// Yes. Extract latitude and longitude
//Latitude decimal
var wholeLat = double.Parse(Words[3], NmeaCultureInfo);
int integerPart = (int)wholeLat;
int DegreesLat = integerPart / 100;
string[] tempLat = Words[3].Substring(2).Split('.');
int MinutesLat = integerPart % 100;
string SecLat = "0";
if (tempLat.Length >= 2)
{
SecLat = "0." + tempLat[1];
}
double SecondsLat = double.Parse(SecLat, NmeaCultureInfo) * 60;
double Latitude = (DegreesLat + (MinutesLat / 60.0) + (SecondsLat / 3600.0));
//Longitude decimal
double DegreesLon = double.Parse(Words[5].Substring(0, 3), NmeaCultureInfo);
string[] tempLon = Words[5].Substring(3).ToString().Split('.');
double MinutesLon = double.Parse(tempLon[0], NmeaCultureInfo);
string SecLon = "0";
if (tempLon.Length >= 2)
{
SecLon = "0." + tempLon[1];
}
double SecondsLon = double.Parse(SecLon, NmeaCultureInfo) * 60;
double Longitude = (DegreesLon + (MinutesLon / 60) + (SecondsLon / 3600));
return (Latitude, Longitude);
}
}
}
它分配了 2 个临时字符串,但对于 GC 来说应该不是问题。您可能希望ParseCoordinatesStrings 返回(double, double) 而不是(string, string),通过将latitudeString 和longitudeString 设为不从方法返回的局部变量来最小化它们的生命周期。在这种情况下,只需将double.Parse 移动到那里。