https://github.com/snozbot/FastString
FastString源码:
using System.Collections.Generic; ///<summary> /// Mutable String class, optimized for speed and memory allocations while retrieving the final result as a string. /// Similar use than StringBuilder, but avoid a lot of allocations done by StringBuilder (conversion of int and float to string, frequent capacity change, etc.) /// Author: Nicolas Gadenne contact@gaddygames.com ///</summary> public class FastString { ///<summary>Immutable string. Generated at last moment, only if needed</summary> private string m_stringGenerated = string.Empty; ///<summary>Is m_stringGenerated is up to date ?</summary> private bool m_isStringGenerated; ///<summary>Working mutable string</summary> private char[] m_buffer; private int m_bufferPos; private int m_charsCapacity; ///<summary>Temporary string used for the Replace method</summary> private List<char> m_replacement; private object m_valueControl; private int m_valueControlInt = int.MinValue; public FastString( int initialCapacity = 32 ) { m_buffer = new char[ m_charsCapacity = initialCapacity ]; } public bool IsEmpty() { return (m_isStringGenerated ? (m_stringGenerated == null) : (m_bufferPos == 0)); } ///<summary>Return the string</summary> public override string ToString() { if( !m_isStringGenerated ) // Regenerate the immutable string if needed { m_stringGenerated = new string( m_buffer, 0, m_bufferPos ); m_isStringGenerated = true; } return m_stringGenerated; } // Value controls methods: use a value to check if the string has to be regenerated. ///<summary>Return true if the valueControl has changed (and update it)</summary> public bool IsModified( int newControlValue ) { bool changed = (newControlValue != m_valueControlInt); if( changed ) m_valueControlInt = newControlValue; return changed; } ///<summary>Return true if the valueControl has changed (and update it)</summary> public bool IsModified( object newControlValue ) { bool changed = !(newControlValue.Equals( m_valueControl )); if( changed ) m_valueControl = newControlValue; return changed; } // Set methods: ///<summary>Set a string, no memorry allocation</summary> public void Set( string str ) { // We fill the m_chars list to manage future appends, but we also directly set the final stringGenerated Clear(); Append( str ); m_stringGenerated = str; m_isStringGenerated = true; } ///<summary>Caution, allocate some memory</summary> public void Set( object str ) { Set( str.ToString() ); } ///<summary>Append several params: no memory allocation unless params are of object type</summary> public void Set<T1, T2>( T1 str1, T2 str2 ) { Clear(); Append( str1 ); Append( str2 ); } public void Set<T1, T2, T3>( T1 str1, T2 str2, T3 str3 ) { Clear(); Append( str1 ); Append( str2 ); Append( str3 ); } public void Set<T1, T2, T3, T4>( T1 str1, T2 str2, T3 str3, T4 str4 ) { Clear(); Append( str1 ); Append( str2 ); Append( str3 ); Append( str4 ); } ///<summary>Allocate a little memory (20 byte)</summary> public void Set( params object[] str ) { Clear(); for( int i=0; i<str.Length; i++ ) Append( str[ i ] ); } // Append methods, to build the string without allocation ///<summary>Reset the m_char array</summary> public FastString Clear() { m_bufferPos = 0; m_isStringGenerated = false; return this; } ///<summary>Append a string without memory allocation</summary> public FastString Append( string value ) { ReallocateIFN( value.Length ); int n = value.Length; for( int i=0; i<n; i++ ) m_buffer[ m_bufferPos + i ] = value[ i ]; m_bufferPos += n; m_isStringGenerated = false; return this; } ///<summary>Append an object.ToString(), allocate some memory</summary> public FastString Append( object value ) { Append( value.ToString() ); return this; } ///<summary>Append an int without memory allocation</summary> public FastString Append( int value ) { // Allocate enough memory to handle any int number ReallocateIFN( 16 ); // Handle the negative case if( value < 0 ) { value = -value; m_buffer[ m_bufferPos++ ] = '-'; } // Copy the digits in reverse order int nbChars = 0; do { m_buffer[ m_bufferPos++ ] = (char)('0' + value%10); value /= 10; nbChars++; } while( value != 0 ); // Reverse the result for( int i=nbChars/2-1; i>=0; i-- ) { char c = m_buffer[ m_bufferPos-i-1 ]; m_buffer[ m_bufferPos-i-1 ] = m_buffer[ m_bufferPos-nbChars+i ]; m_buffer[ m_bufferPos-nbChars+i ] = c; } m_isStringGenerated = false; return this; } ///<summary>Append a float without memory allocation.</summary> public FastString Append( float valueF ) { double value = valueF; m_isStringGenerated = false; ReallocateIFN( 32 ); // Check we have enough buffer allocated to handle any float number // Handle the 0 case if( value == 0 ) { m_buffer[ m_bufferPos++ ] = '0'; return this; } // Handle the negative case if( value < 0 ) { value = -value; m_buffer[ m_bufferPos++ ] = '-'; } // Get the 7 meaningful digits as a long int nbDecimals = 0; while( value < 1000000 ) { value *= 10; nbDecimals++; } long valueLong = (long)System.Math.Round( value ); // Parse the number in reverse order int nbChars = 0; bool isLeadingZero = true; while( valueLong != 0 || nbDecimals >= 0 ) { // We stop removing leading 0 when non-0 or decimal digit if( valueLong%10 != 0 || nbDecimals <= 0 ) isLeadingZero = false; // Write the last digit (unless a leading zero) if( !isLeadingZero ) m_buffer[ m_bufferPos + (nbChars++) ] = (char)('0' + valueLong%10); // Add the decimal point if( --nbDecimals == 0 && !isLeadingZero ) m_buffer[ m_bufferPos + (nbChars++) ] = '.'; valueLong /= 10; } m_bufferPos += nbChars; // Reverse the result for( int i=nbChars/2-1; i>=0; i-- ) { char c = m_buffer[ m_bufferPos-i-1 ]; m_buffer[ m_bufferPos-i-1 ] = m_buffer[ m_bufferPos-nbChars+i ]; m_buffer[ m_bufferPos-nbChars+i ] = c; } return this; } ///<summary>Replace all occurences of a string by another one</summary> public FastString Replace( string oldStr, string newStr ) { if( m_bufferPos == 0 ) return this; if( m_replacement == null ) m_replacement = new List<char>(); // Create the new string into m_replacement for( int i=0; i<m_bufferPos; i++ ) { bool isToReplace = false; if( m_buffer[ i ] == oldStr[ 0 ] ) // If first character found, check for the rest of the string to replace { int k=1; while( k < oldStr.Length && m_buffer[ i+k ] == oldStr[ k ] ) k++; isToReplace = (k >= oldStr.Length); } if( isToReplace ) // Do the replacement { i += oldStr.Length-1; if( newStr != null ) for( int k=0; k<newStr.Length; k++ ) m_replacement.Add( newStr[ k ] ); } else // No replacement, copy the old character m_replacement.Add( m_buffer[ i ] ); } // Copy back the new string into m_chars ReallocateIFN( m_replacement.Count - m_bufferPos ); for( int k=0; k<m_replacement.Count; k++ ) m_buffer[ k ] = m_replacement[ k ]; m_bufferPos = m_replacement.Count; m_replacement.Clear(); m_isStringGenerated = false; return this; } private void ReallocateIFN( int nbCharsToAdd ) { if( m_bufferPos + nbCharsToAdd > m_charsCapacity ) { m_charsCapacity = System.Math.Max( m_charsCapacity + nbCharsToAdd, m_charsCapacity * 2 ); char[] newChars = new char[ m_charsCapacity ]; m_buffer.CopyTo( newChars, 0 ); m_buffer = newChars; } } }
测试脚本:
using System; using UnityEngine; using UnityEngine.Profiling; public class FastStringTest : MonoBehaviour { private FastString m_strCustom = new FastString(64); private System.Text.StringBuilder m_strBuilder = new System.Text.StringBuilder(64); private delegate string Test(); private string String_Added() { string str = "PI=" + Mathf.PI + "_373=" + 373; return str.Replace("373", "5428"); } private string String_Concat() { return string.Concat("PI=", Mathf.PI, "_373=", 373).Replace("373", "5428"); } private string StringBuilder() { m_strBuilder.Length = 0; m_strBuilder.Append("PI=").Append(Mathf.PI).Append("_373=").Append(373).Replace("373", "5428"); return m_strBuilder.ToString(); } private string FastString() { m_strCustom.Clear(); m_strCustom.Append("PI=").Append(Mathf.PI).Append("_373=").Append(373).Replace("373", "5428"); return m_strCustom.ToString(); } private void RunTest(string testName, Test test) { Profiler.BeginSample(testName); string lastResult = null; for (int i = 0; i < 1000; i++) lastResult = test(); Profiler.EndSample(); Debug.Log( "Check test result: test=" + testName + " result='" + lastResult + "' ,Length = (" + lastResult.Length + ")" ); } private void Start() { Debug.Log("================="); RunTest("Test #1: string (+) ", String_Added); RunTest("Test #2: string (.concat) ", String_Concat); RunTest("Test #3: StringBuilder ", StringBuilder); RunTest("Test #4: FastString", FastString); } }
通过对比图可以看出,无论是耗时还是GC,FastString都做了的优化..