解决链计算的最快方法

我有一个输入

string input = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34"; // up to 10MB string size int result = Calc(input); // 11 
  • 计算从左到右,按数字计算
  • 数字是0到99
  • 加法前的乘法被忽略,因此14 + 2 * 32512
  • 可能的计算是+-*/
  • 除以0是不可能的,因此a /不能为0

我的方法

 public static int Calc(string sInput) { int iCurrent = sInput.IndexOf(' '); int iResult = int.Parse(sInput.Substring(0, iCurrent)); int iNext = 0; while ((iNext = sInput.IndexOf(' ', iCurrent + 4)) != -1) { iResult = Operate(iResult, sInput[iCurrent + 1], int.Parse(sInput.Substring((iCurrent + 3), iNext - (iCurrent + 2)))); iCurrent = iNext; } return Operate(iResult, sInput[iCurrent + 1], int.Parse(sInput.Substring((iCurrent + 3)))); } public static int Operate(int iReturn, char cOperator, int iOperant) { switch (cOperator) { case '+': return (iReturn + iOperant); case '-': return (iReturn - iOperant); case '*': return (iReturn * iOperant); case '/': return (iReturn / iOperant); default: throw new Exception("Error"); } } 

我需要以最快的方式获得结果。

问题:有没有办法让这个计算更快? 我有多个线程,但我只使用一个。

更新:

测试用例:(我已经删除了除0错误并从StopWatch测量中删除了StringBuilder.ToString()

 Random rand = new Random(); System.Text.StringBuilder input = new System.Text.StringBuilder(); string operators = "+-*/"; input.Append(rand.Next(0, 100)); for (int i = 0; i  0 ? 4 : 3)]; input.Append(" " + coperator + " " + number); } string calc = input.ToString(); System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch(); watch.Start(); int result = Calc(calc); watch.Stop(); 

以下解决方案是有限自动机。 Calc(输入)= O(n)。 为了获得更好的性能,此解决方案不使用IndexOfSubstringParse ,字符串连接或重复读取值(不止一次获取input[i] )…只是一个字符处理器。

  static int Calculate1(string input) { int acc = 0; char last = ' ', operation = '+'; for (int i = 0; i < input.Length; i++) { var current = input[i]; switch (current) { case ' ': if (last != ' ') { switch (operation) { case '+': acc += last - '0'; break; case '-': acc -= last - '0'; break; case '*': acc *= last - '0'; break; case '/': acc /= last - '0'; break; } last = ' '; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (last == ' ') last = current; else { var num = (last - '0') * 10 + (current - '0'); switch (operation) { case '+': acc += num; break; case '-': acc -= num; break; case '*': acc *= num; break; case '/': acc /= num; break; } last = ' '; } break; case '+': case '-': case '*': case '/': operation = current; break; } } if (last != ' ') switch (operation) { case '+': acc += last - '0'; break; case '-': acc -= last - '0'; break; case '*': acc *= last - '0'; break; case '/': acc /= last - '0'; break; } return acc; } 

另一个实现。 它从输入中减少了25%。 我希望它的性能提高25%。

  static int Calculate2(string input) { int acc = 0, i = 0; char last = ' ', operation = '+'; while (i < input.Length) { var current = input[i]; switch (current) { case ' ': if (last != ' ') { switch (operation) { case '+': acc += last - '0'; break; case '-': acc -= last - '0'; break; case '*': acc *= last - '0'; break; case '/': acc /= last - '0'; break; } last = ' '; i++; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (last == ' ') { last = current; i++; } else { var num = (last - '0') * 10 + (current - '0'); switch (operation) { case '+': acc += num; break; case '-': acc -= num; break; case '*': acc *= num; break; case '/': acc /= num; break; } last = ' '; i += 2; } break; case '+': case '-': case '*': case '/': operation = current; i += 2; break; } } if (last != ' ') switch (operation) { case '+': acc += last - '0'; break; case '-': acc -= last - '0'; break; case '*': acc *= last - '0'; break; case '/': acc /= last - '0'; break; } return acc; } 

我实现了另外一个变体:

  static int Calculate3(string input) { int acc = 0, i = 0; var operation = '+'; while (true) { var a = input[i++] - '0'; if (i == input.Length) { switch (operation) { case '+': acc += a; break; case '-': acc -= a; break; case '*': acc *= a; break; case '/': acc /= a; break; } break; } var b = input[i]; if (b == ' ') i++; else { a = a * 10 + (b - '0'); i += 2; } switch (operation) { case '+': acc += a; break; case '-': acc -= a; break; case '*': acc *= a; break; case '/': acc /= a; break; } if (i >= input.Length) break; operation = input[i]; i += 2; } return acc; } 

抽象结果:

  • 计算1 230
  • 计算2 192
  • 计算3 111

编辑编辑:使用The General和Mirai Mann的最新版本进行更新:

如果你想知道哪匹马最快:比赛马匹。 以下是BenchmarkDotNet结果,比较了这个问题的各种答案(我没有将他们的代码合并到我的完整示例中,因为感觉不对 – 只有数字显示)具有可重复但大的随机输入,通过:

 static MyTests() { Random rand = new Random(12345); StringBuilder input = new StringBuilder(); string operators = "+-*/"; var lastOperator = '+'; for (int i = 0; i < 1000000; i++) { var @operator = operators[rand.Next(0, 4)]; input.Append(rand.Next(lastOperator == '/' ? 1 : 0, 100) + " " + @operator + " "); lastOperator = @operator; } input.Append(rand.Next(0, 100)); expression = input.ToString(); } private static readonly string expression; 

通过健全检查(检查他们都做对了):

 Original: -1426 NoSubStrings: -1426 NoSubStringsUnsafe: -1426 TheGeneral4: -1426 MiraiMann1: -1426 

我们得到时间(注意: Original是OP的版本问题; NoSubStrings[Unsafe]是我的版本,以及其他两个版本来自用户名的其他答案):

(较低的“平均值”更好)

(注意;有一个较新版本的Mirai Mann的实现,但我不再设置运行新测试的东西;但是:公平地假设它应该更好!)

运行时:.NET Framework 4.7(CLR 4.0.30319.42000),32位LegacyJIT-v4.7.2633.0

  Method | Mean | Error | StdDev | ------------------- |----------:|----------:|----------:| Original | 104.11 ms | 1.4920 ms | 1.3226 ms | NoSubStrings | 21.99 ms | 0.4335 ms | 0.7122 ms | NoSubStringsUnsafe | 20.53 ms | 0.4103 ms | 0.6967 ms | TheGeneral4 | 15.50 ms | 0.3020 ms | 0.5369 ms | MiraiMann1 | 15.54 ms | 0.3096 ms | 0.4133 ms | 

运行时:.NET Framework 4.7(CLR 4.0.30319.42000),64位RyuJIT-v4.7.2633.0

  Method | Mean | Error | StdDev | Median | ------------------- |----------:|----------:|----------:|----------:| Original | 114.15 ms | 1.3142 ms | 1.0974 ms | 114.13 ms | NoSubStrings | 21.33 ms | 0.4161 ms | 0.6354 ms | 20.93 ms | NoSubStringsUnsafe | 19.24 ms | 0.3832 ms | 0.5245 ms | 19.43 ms | TheGeneral4 | 13.97 ms | 0.2795 ms | 0.2745 ms | 13.86 ms | MiraiMann1 | 15.60 ms | 0.3090 ms | 0.4125 ms | 15.53 ms | 

运行时:.NET Core 2.1.0-preview1-26116-04(CoreCLR 4.6.26116.03,CoreFX 4.6.26116.01),64位RyuJIT

  Method | Mean | Error | StdDev | Median | ------------------- |----------:|----------:|----------:|----------:| Original | 101.51 ms | 1.7807 ms | 1.5786 ms | 101.44 ms | NoSubStrings | 21.36 ms | 0.4281 ms | 0.5414 ms | 21.07 ms | NoSubStringsUnsafe | 19.85 ms | 0.4172 ms | 0.6737 ms | 19.80 ms | TheGeneral4 | 14.06 ms | 0.2788 ms | 0.3723 ms | 13.82 ms | MiraiMann1 | 15.88 ms | 0.3153 ms | 0.5922 ms | 15.45 ms | 

在我添加BenchmarkDotNet之前的原始答案:

如果我正在尝试这个,我很想看看2.1预览中的Span工作 - 关键是Span可以切片而不分配(并且string可以转换为Span没有分配); 这将允许在没有任何分配的情况下执行字符串雕刻和解析。 然而,减少分配并不总是与原始性能完全相同(尽管它们是相关的),所以要知道它是否更快:你需要比赛你的马(即比较它们)。

如果Span不是一个选项:您仍然可以通过手动跟踪int offset并且*从不使用SubString来执行相同的操作

在任何一种情况下( stringSpan ):如果你的操作只需要处理整数表示的某个子集,我可能会试图将一个自定义的int.Parse等价物交给不适用的规则(文化,替代布局等),并且无需Substring - 例如,它可以采用stringref int offset ,其中offset更新为解析停止的位置 (因为它击中了运算符或空格)哪个有效。

就像是:

 static class P { static void Main() { string input = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34"; var val = Evaluate(input); System.Console.WriteLine(val); } static int Evaluate(string expression) { int offset = 0; SkipSpaces(expression, ref offset); int value = ReadInt32(expression, ref offset); while(ReadNext(expression, ref offset, out char @operator, out int operand)) { switch(@operator) { case '+': value = value + operand; break; case '-': value = value - operand; break; case '*': value = value * operand; break; case '/': value = value / operand; break; } } return value; } static bool ReadNext(string value, ref int offset, out char @operator, out int operand) { SkipSpaces(value, ref offset); if(offset >= value.Length) { @operator = (char)0; operand = 0; return false; } @operator = value[offset++]; SkipSpaces(value, ref offset); if (offset >= value.Length) { operand = 0; return false; } operand = ReadInt32(value, ref offset); return true; } static void SkipSpaces(string value, ref int offset) { while (offset < value.Length && value[offset] == ' ') offset++; } static int ReadInt32(string value, ref int offset) { bool isNeg = false; char c = value[offset++]; int i = (c - '0'); if(c == '-') { isNeg = true; i = 0; // todo: what to do here if `-` is not followed by [0-9]? } while (offset < value.Length && (c = value[offset++]) >= '0' && c <= '9') i = (i * 10) + (c - '0'); return isNeg ? -i : i; } } 

接下来,我可能会考虑是否值得切换到unsafe来删除双倍长度检查。 所以我会以两种方式实现它,并使用像BenchmarkDotNet这样的东西来测试它是否值得。


编辑:这是添加了unsafe和BenchmarkDotNet用法:

 using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System; static class P { static void Main() { var summary = BenchmarkRunner.Run(); System.Console.WriteLine(summary); } } public class MyTests { const string expression = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34"; [Benchmark] public int Original() => EvalOriginal.Calc(expression); [Benchmark] public int NoSubStrings() => EvalNoSubStrings.Evaluate(expression); [Benchmark] public int NoSubStringsUnsafe() => EvalNoSubStringsUnsafe.Evaluate(expression); } static class EvalOriginal { public static int Calc(string sInput) { int iCurrent = sInput.IndexOf(' '); int iResult = int.Parse(sInput.Substring(0, iCurrent)); int iNext = 0; while ((iNext = sInput.IndexOf(' ', iCurrent + 4)) != -1) { iResult = Operate(iResult, sInput[iCurrent + 1], int.Parse(sInput.Substring((iCurrent + 3), iNext - (iCurrent + 2)))); iCurrent = iNext; } return Operate(iResult, sInput[iCurrent + 1], int.Parse(sInput.Substring((iCurrent + 3)))); } public static int Operate(int iReturn, char cOperator, int iOperant) { switch (cOperator) { case '+': return (iReturn + iOperant); case '-': return (iReturn - iOperant); case '*': return (iReturn * iOperant); case '/': return (iReturn / iOperant); default: throw new Exception("Error"); } } } static class EvalNoSubStrings { public static int Evaluate(string expression) { int offset = 0; SkipSpaces(expression, ref offset); int value = ReadInt32(expression, ref offset); while (ReadNext(expression, ref offset, out char @operator, out int operand)) { switch (@operator) { case '+': value = value + operand; break; case '-': value = value - operand; break; case '*': value = value * operand; break; case '/': value = value / operand; break; default: throw new Exception("Error"); } } return value; } static bool ReadNext(string value, ref int offset, out char @operator, out int operand) { SkipSpaces(value, ref offset); if (offset >= value.Length) { @operator = (char)0; operand = 0; return false; } @operator = value[offset++]; SkipSpaces(value, ref offset); if (offset >= value.Length) { operand = 0; return false; } operand = ReadInt32(value, ref offset); return true; } static void SkipSpaces(string value, ref int offset) { while (offset < value.Length && value[offset] == ' ') offset++; } static int ReadInt32(string value, ref int offset) { bool isNeg = false; char c = value[offset++]; int i = (c - '0'); if (c == '-') { isNeg = true; i = 0; } while (offset < value.Length && (c = value[offset++]) >= '0' && c <= '9') i = (i * 10) + (c - '0'); return isNeg ? -i : i; } } static unsafe class EvalNoSubStringsUnsafe { public static int Evaluate(string expression) { fixed (char* ptr = expression) { int len = expression.Length; var c = ptr; SkipSpaces(ref c, ref len); int value = ReadInt32(ref c, ref len); while (len > 0 && ReadNext(ref c, ref len, out char @operator, out int operand)) { switch (@operator) { case '+': value = value + operand; break; case '-': value = value - operand; break; case '*': value = value * operand; break; case '/': value = value / operand; break; default: throw new Exception("Error"); } } return value; } } static bool ReadNext(ref char* c, ref int len, out char @operator, out int operand) { SkipSpaces(ref c, ref len); if (len-- == 0) { @operator = (char)0; operand = 0; return false; } @operator = *c++; SkipSpaces(ref c, ref len); if (len == 0) { operand = 0; return false; } operand = ReadInt32(ref c, ref len); return true; } static void SkipSpaces(ref char* c, ref int len) { while (len != 0 && *c == ' ') { c++;len--; } } static int ReadInt32(ref char* c, ref int len) { bool isNeg = false; char ch = *c++; len--; int i = (ch - '0'); if (ch == '-') { isNeg = true; i = 0; } while (len-- != 0 && (ch = *c++) >= '0' && ch <= '9') i = (i * 10) + (ch - '0'); return isNeg ? -i : i; } } 

注意

根据评论,这个答案并不能提供高效的解决方案。 我会把它留在这里,因为有些要考虑/将来可能会对其他人发现这个问题感兴趣; 但正如人们在下面所说的那样,这远不是最有效的解决方案。


原始答案

.net框架已经提供了一种处理以字符串forms给出的公式的方法:

 var formula = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34"; var result = new DataTable().Compute(formula, null); Console.WriteLine(result); //returns 139.125490196078 

基于评论的初步反馈

根据评论主题,我需要指出一些事情:

这是否按照我描述的方式工作?

没有; 这符合正常的数学规则。

我假设你修改后的规则是简化编写代码来处理它们,而不是因为你想支持一个新的数学分支? 如果是这样的话,我会反对。 人们会期望事物以某种方式表现; 因此,您必须确保向您的代码发送方程式的任何人都知道了这些新数学的规则,而不是能够使用他们现有的期望。

这里没有改变规则的选择; 因此,如果您的要求是更改数学规则,这将不适合您。

这是最快的解决方案吗?

没有。但是,如果MS花费大量时间优化代码,它应该表现良好,因此可能比任何手动代码执行速度更快(尽管这个代码不仅支持四个主要运算符) ;所以它没有完全相同)。

根据MatthewWatson的特定注释(即调用DataTable构造函数会产生很大的开销),您需要创建并重新使用此对象的一个​​实例。 根据您的解决方案的外观,有多种方法可以做到这一点; 这是一个:

 interface ICalculator //if we use an interface we can easily switch from datatable to some other calulator; useful for testing, or if we wanted to compare different calculators without much recoding { T Calculate(string expression) where T: struct; } class DataTableCalculator: ICalculator { readonly DataTable dataTable = new DataTable(); public DataTableCalculator(){} public T Calculate(string expression) where T: struct => (T)dataTable.Compute(expression, null); } class Calculator: ICalculator { static ICalculator internalInstance; public Calculator(){} public void InitialiseCalculator (ICalculator calculator) { if (internalInstance != null) { throw new InvalidOperationException("Calculator has already been initialised"); } internalInstance = calculator; } public T Calculate(string expression) where T: struct => internalInstance.Calculate(expression); } //then we use it on our code void Main() { var calculator1 = new Calculator(); calculator1.InitialiseCalculator(new DataTableCalculator()); var equation = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34"; Console.WriteLine(calculator1.Calculate(equation)); //139.125490196078 equation = "1 + 2 - 3 + 4"; Console.WriteLine(calculator1.Calculate(equation)); //4 calculator1 = null; System.GC.Collect(); //in reality we'd pretty much never do this, but just to illustrate that our static variable continues fro the life of the app domain rather than the life of the instance var calculator2 = new Calculator(); //calculator2.InitialiseCalculator(new DataTableCalculator()); //uncomment this and you'll get an error; ie the calulator should only be initialised once. equation = "1 + 2 - 3 + 4 / 5 * 6 - 7 / 8 + 9"; Console.WriteLine(calculator2.Calculate(equation)); //12.925 } 

注意:上面的解决方案使用静态变量; 有些人反对使用静力学。 对于这种情况(即在应用程序的生命周期中,您只需要一种方法进行计算),这是一个合法的用例。 如果您想支持在运行时切换计算器,则需要采用不同的方法。


测试和比较后更新

运行了一些性能测试:

  • DataTable.Compute方法的最大问题是,对于你正在处理它的大小的方程式抛出一个StackOverflowException ; (即基于你的方程生成器的循环for (int i = 0; i < 1000000; i++)
  • 对于具有较小等式( i < 1000 )的单个操作,计算方法(包括double结果上的构造函数和Convert.ToInt32 )花费的时间几乎长100倍。
  • 对于单个操作,我也经常遇到溢出exception; 即因为操作的结果将值推到支持的数据类型的范围之外......
  • 即使我们将构造函数/初始化调用移到定时区域之外并将转换移到int(并运行数千次迭代以获得平均值),您的解决方案的速度比我的快3.5倍。

链接到文档: https : //msdn.microsoft.com/en-us/library/system.data.datatable.compute%28v=vs.110%29.aspx? f = 255 & MSPPError = -2147217396

更新

我最初的答案只是在深夜尝试将其置于unsafe并且我失败了(实际上完全没有工作并且速度较慢)。 但是我决定再拍一次

前提是将内容全部内联,尽可能多地删除IL ,将所有内容保存在intchar* ,并使我的代码漂亮。 我通过移除开关进一步优化了这个,在这种情况下, Ifs将非常高效,我们也可以以最合理的方式对它们进行排序。 最后,如果我们删除对我们所做事情的检查量并假设输入是正确的,我们可以通过假设这样的事情来消除更多的开销; 如果char > '0'则它必须是数字。 如果它是一个空间我们可以做一些计算,否则它必须是一个运算符。

这是我最后一次尝试,10,000次计算运行100次以获得平均值,每次测试都执行GC.Collect();GC.WaitForPendingFinalizers(); 所以我们不会破坏记忆

结果

 Test : ms : Cycles (rough) : Increase ------------------------------------------------------------------- OriginalCalc : 1,295 : 4,407,795,584 : MarcEvalNoSubStrings : 241 : 820,660,220 : 437.34%, * 5.32 MarcEvalNoSubStringsUnsafe : 206 : 701,980,373 : 528.64%, * 6.28 MiraiMannCalc1 : 225 : 765,678,062 : 475.55%, * 5.75 MiraiMannCalc2 : 183 : 623,384,924 : 607.65%, * 7.07 MyCalc4 : 156 : 534,190,325 : 730.12%, * 8.30 MyCalc5 : 146 : 496,185,459 : 786.98%, * 8.86 MyCalc6 : 134 : 455,610,410 : 866.41%, * 9.66 

迄今为止最快的代码

 unsafe int Calc6(ref string expression) { int res = 0, val = 0, op = 0; var isOp = false; // pin the array fixed (char* p = expression) { // Lets not evaluate this 100 million times var max = p + expression.Length; // lets go straight to the source and just increment the pointer for (var i = p; i < max; i++) { // numbers are the most common thing so lets do a loose // basic check for them and push them in to our val if (*i >= '0') { val = val * 10 + *i - 48; continue; } // The second most common thing are spaces if (*i == ' ') { // not every space we need to calculate if (!(isOp = !isOp)) continue; // In this case 4 ifs are more efficient then a switch // do the calculation, reset out val and jump out if (op == '+') { res += val; val = 0; continue; } if (op == '-') { res -= val; val = 0; continue; } if (op == '*') { res *= val; val = 0; continue; } if (op == '/') { res /= val; val = 0; continue; } // this is just for the first op res = val; val = 0; continue; } // anything else is considered an operator op = *i; } if (op == '+') return res + val; if (op == '-') return res - val; if (op == '*') return res * val; if (op == '/') return res / val; throw new IndexOutOfRangeException(); } } 

以前

 unsafe int Calc4(ref string expression) { int res = 0, val = 0, op = 0; var isOp = false; fixed (char* p = expression) { var max = p + expression.Length; for (var i = p; i < max; i++) switch (*i) { case ' ': isOp = !isOp; if (!isOp) continue; switch (op) { case '+': res += val; val = 0; continue; case '-': res -= val; val = 0; continue; case '*': res *= val; val = 0; continue; case '/': res /= val; val = 0; continue; default: res = val; val = 0; continue; } case '+': case '-': case '*': case '/': op = *i; continue; default: val = val * 10 + *i - 48; continue; } switch (op) { case '+': return res + val; case '-': return res - val; case '*': return res * val; case '/': return res / val; default : return -1; } } } 

我如何测量螺纹循环

 static class NativeMethods { public static ulong GetThreadCycles() { ulong cycles; if (!QueryThreadCycleTime(PseudoHandle, out cycles)) throw new System.ComponentModel.Win32Exception(); return cycles; } [DllImport("kernel32.dll", SetLastError = true)] private static extern bool QueryThreadCycleTime(IntPtr hThread, out ulong cycles); private static readonly IntPtr PseudoHandle = (IntPtr)(-2); } 

原帖

我认为id试图变得聪明并且使用fixed:/并且最多使用数百万次计算

 public static unsafe int Calc2(string sInput) { var buf = ""; var start = sInput.IndexOf(' '); var value1 = int.Parse(sInput.Substring(0, start)); string op = null; var iResult = 0; var isOp = false; fixed (char* p = sInput) { for (var i = start + 1; i < sInput.Length; i++) { var cur = *(p + i); if (cur == ' ') { if (!isOp) { op = buf; isOp = true; } else { var value2 = int.Parse(buf); switch (op[0]) { case '+': iResult += value1 + value2; break; case '-': iResult += value1 - value2; break; case '*': iResult += value1 * value2; break; case '/': iResult += value1 / value2; break; } value1 = value2; isOp = false; } buf = ""; } else { buf += cur; } } } return iResult; } private static void Main(string[] args) { var input = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34"; var sb = new StringBuilder(); sb.Append(input); for (var i = 0; i < 10000000; i++) sb.Append(" + " + input); var sw = new Stopwatch(); sw.Start(); Calc2(sb.ToString()); sw.Stop(); Console.WriteLine($"sw : {sw.Elapsed:c}"); } 

结果比原来慢2秒!

这是一个Java有趣的事实。 我在Java中实现了相同的function,它的运行速度比使用C#的Mirai Mann快20倍。 在我的机器上,100M字符输入字符串大约需要353毫秒。

下面是创建和测试结果的代码。

另外,请注意,虽然它是一个优秀的Java / C#性能测试程序,但这不是最佳解决方案。 通过multithreading可以实现更好的性能。 可以计算字符串的一部分,然后组合结果。

 public class Test { public static void main(String...args){ new Test().run(); } private void run() { long startTime = System.currentTimeMillis(); Random random = new Random(123); int result = 0; StringBuilder input = new StringBuilder(); input.append(random.nextInt(99) + 1); while (input.length() < 100_000_000){ int value = random.nextInt(100); switch (random.nextInt(4)){ case 0: input.append("-"); result -= value; break; case 1: // + input.append("+"); result += value; break; case 2: input.append("*"); result *= value; break; case 3: input.append("/"); while (value == 0){ value = random.nextInt(100); } result /= value; break; } input.append(value); } String inputData = input.toString(); System.out.println("Test created in " + (System.currentTimeMillis() - startTime)); startTime = System.currentTimeMillis(); int testResult = test(inputData); System.out.println("Completed in " + (System.currentTimeMillis() - startTime)); if(result != testResult){ throw new Error("Oops"); } } private int test(String inputData) { char[] input; try { Field val = String.class.getDeclaredField("value"); val.setAccessible(true); input = (char[]) val.get(inputData); } catch (NoSuchFieldException | IllegalAccessException e) { throw new Error(e); } int result = 0; int startingI = 1; { char c = input[0]; if (c >= '0' && c <= '9') { result += c - '0'; c = input[1]; if (c >= '0' && c <= '9') { result += (c - '0') * 10; startingI++; } } } for (int i = startingI, length = input.length, value=0; i < length; i++) { char operation = input[i]; i++; char c = input[i]; if(c >= '0' && c <= '9'){ value += c - '0'; c = input[i + 1]; if(c >= '0' && c <= '9'){ value = value * 10 + (c - '0'); i++; } } switch (operation){ case '-': result -= value; break; case '+': result += value; break; case '*': result *= value; break; case '/': result /= value; break; } value = 0; } return result; } } 

当您阅读代码时,您可以看到我在将字符串转换为char数组时使用了一个小的hack。 我改变了字符串,以避免为char数组添加额外的内存。