starts - endswith c#
Contiene es más rápido que StartsWith? (5)
Un consultor vino ayer y de alguna manera surgió el tema de las cuerdas. Mencionó que había notado que para cadenas de menos de cierta longitud, Contains
es en realidad más rápido que StartsWith
. Tenía que verlo con mis propios ojos, así que escribí una pequeña aplicación y, por supuesto, ¡ Contains
es más rápido!
¿Cómo es esto posible?
DateTime start = DateTime.MinValue;
DateTime end = DateTime.MinValue;
string str = "Hello there";
start = DateTime.Now;
for (int i = 0; i < 10000000; i++)
{
str.Contains("H");
}
end = DateTime.Now;
Console.WriteLine("{0}ms using Contains", end.Subtract(start).Milliseconds);
start = DateTime.Now;
for (int i = 0; i < 10000000; i++)
{
str.StartsWith("H");
}
end = DateTime.Now;
Console.WriteLine("{0}ms using StartsWith", end.Subtract(start).Milliseconds);
Salidas:
726ms using Contains
865ms using StartsWith
¡Lo he probado con cuerdas más largas también!
Examinemos lo que dice ILSpy sobre estos dos ...
public virtual int IndexOf(string source, string value, int startIndex, int count, CompareOptions options)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (value == null)
{
throw new ArgumentNullException("value");
}
if (startIndex > source.Length)
{
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
}
if (source.Length == 0)
{
if (value.Length == 0)
{
return 0;
}
return -1;
}
else
{
if (startIndex < 0)
{
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
}
if (count < 0 || startIndex > source.Length - count)
{
throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count"));
}
if (options == CompareOptions.OrdinalIgnoreCase)
{
return source.IndexOf(value, startIndex, count, StringComparison.OrdinalIgnoreCase);
}
if ((options & ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth)) != CompareOptions.None && options != CompareOptions.Ordinal)
{
throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
}
return CompareInfo.InternalFindNLSStringEx(this.m_dataHandle, this.m_handleOrigin, this.m_sortName, CompareInfo.GetNativeCompareFlags(options) | 4194304 | ((source.IsAscii() && value.IsAscii()) ? 536870912 : 0), source, count, startIndex, value, value.Length);
}
}
Parece que también considera la cultura, pero está predeterminada.
public bool StartsWith(string value, StringComparison comparisonType)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
{
throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
}
if (this == value)
{
return true;
}
if (value.Length == 0)
{
return true;
}
switch (comparisonType)
{
case StringComparison.CurrentCulture:
return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
case StringComparison.CurrentCultureIgnoreCase:
return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
case StringComparison.InvariantCulture:
return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
case StringComparison.InvariantCultureIgnoreCase:
return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
case StringComparison.Ordinal:
return this.Length >= value.Length && string.nativeCompareOrdinalEx(this, 0, value, 0, value.Length) == 0;
case StringComparison.OrdinalIgnoreCase:
return this.Length >= value.Length && TextInfo.CompareOrdinalIgnoreCaseEx(this, 0, value, 0, value.Length, value.Length) == 0;
default:
throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
}
Por el contrario, la única diferencia que veo que parece relevante es una verificación de longitud adicional.
Hice un giro en Reflector y encontré una posible respuesta:
Contiene:
return (this.IndexOf(value, StringComparison.Ordinal) >= 0);
Comienza con:
...
switch (comparisonType)
{
case StringComparison.CurrentCulture:
return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
case StringComparison.CurrentCultureIgnoreCase:
return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
case StringComparison.InvariantCulture:
return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
case StringComparison.InvariantCultureIgnoreCase:
return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
case StringComparison.Ordinal:
return ((this.Length >= value.Length) && (nativeCompareOrdinalEx(this, 0, value, 0, value.Length) == 0));
case StringComparison.OrdinalIgnoreCase:
return ((this.Length >= value.Length) && (TextInfo.CompareOrdinalIgnoreCaseEx(this, 0, value, 0, value.Length, value.Length) == 0));
}
throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
Y hay algunas sobrecargas para que la cultura por defecto sea CurrentCulture.
Entonces, en primer lugar, Ordinal será más rápido (si la cadena está cerca del principio) de todos modos, ¿verdad? Y en segundo lugar, hay más lógica aquí que podría ralentizar las cosas (aunque es tan trivial)
Intente usar StopWatch
para medir la velocidad en lugar de la comprobación de DateTime
.
Cronómetro contra el uso de System.DateTime.Now para eventos de temporización
Creo que la clave está en las siguientes partes importantes en negrita:
Contains
:
Este método realiza una comparación ordinal (sensible a mayúsculas y minúsculas ) e insensible a la cultura .
StartsWith
:
Este método realiza una comparación de palabras (distingue entre mayúsculas y minúsculas y cultura ) utilizando la cultura actual.
Creo que la clave es la comparación ordinal que equivale a:
Una ordenación ordinal compara cadenas basadas en el valor numérico de cada objeto Char en la cadena. Una comparación ordinal automáticamente distingue entre mayúsculas y minúsculas porque las versiones en minúsculas y mayúsculas de un personaje tienen diferentes puntos de código. Sin embargo, si el caso no es importante en su aplicación, puede especificar una comparación ordinal que ignore el caso. Esto es equivalente a convertir la cadena en mayúsculas utilizando la cultura invariante y luego realizar una comparación ordinal en el resultado.
Referencias
http://msdn.microsoft.com/en-us/library/system.string.aspx
http://msdn.microsoft.com/en-us/library/dy85x1sa.aspx
http://msdn.microsoft.com/en-us/library/baketfxw.aspx
Usando Reflector puedes ver el código para los dos:
public bool Contains(string value)
{
return (this.IndexOf(value, StringComparison.Ordinal) >= 0);
}
public bool StartsWith(string value, bool ignoreCase, CultureInfo culture)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
if (this == value)
{
return true;
}
CultureInfo info = (culture == null) ? CultureInfo.CurrentCulture : culture;
return info.CompareInfo.IsPrefix(this, value,
ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
}
Me lo imaginé. Es porque http://msdn.microsoft.com/en-us/library/baketfxw.aspx es sensible a la cultura, mientras que http://msdn.microsoft.com/en-us/library/dy85x1sa.aspx no lo es. Eso significa intrínsecamente que StartsWith
tiene que hacer más trabajo.
FWIW, aquí están mis resultados en Mono con el siguiente punto de referencia (corregido):
1988.7906ms using Contains
10174.1019ms using StartsWith
Me alegraría ver los resultados de las personas en la EM, pero mi punto principal es que si se hace correctamente (y suponiendo optimizaciones similares), creo que StartsWith
tiene que ser más lento:
using System;
using System.Diagnostics;
public class ContainsStartsWith
{
public static void Main()
{
string str = "Hello there";
Stopwatch s = new Stopwatch();
s.Start();
for (int i = 0; i < 10000000; i++)
{
str.Contains("H");
}
s.Stop();
Console.WriteLine("{0}ms using Contains", s.Elapsed.TotalMilliseconds);
s.Reset();
s.Start();
for (int i = 0; i < 10000000; i++)
{
str.StartsWith("H");
}
s.Stop();
Console.WriteLine("{0}ms using StartsWith", s.Elapsed.TotalMilliseconds);
}
}
StartsWith
y Contains
comportan de manera completamente diferente cuando se trata de temas sensibles a la cultura.
En particular, StartsWith
devolviendo true
NO implica que Contains
devolver true
. Debe reemplazar uno de ellos con el otro solo si realmente sabe lo que está haciendo.
using System;
class Program
{
static void Main()
{
var x = "A";
var y = "A/u0640";
Console.WriteLine(x.StartsWith(y)); // True
Console.WriteLine(x.Contains(y)); // False
}
}