2009-05-01 21 views
14

Tengo una cadena que puede tener caracteres en blanco alrededor y quiero comprobar si está esencialmente vacía.¿Cuál es la forma más eficiente de determinar si una cadena no recortada está vacía en C#?

Hay bastantes maneras de hacer esto:

1 if (myString.Trim().Length == 0) 
2 if (myString.Trim() == "") 
3 if (myString.Trim().Equals("")) 
4 if (myString.Trim() == String.Empty) 
5 if (myString.Trim().Equals(String.Empty)) 

Soy consciente de que esto por lo general sería un claro caso de la optimización prematura, pero tengo curiosidad y hay una posibilidad de que esto será hecho lo suficiente para tener un impacto en el rendimiento.

¿Cuál de estos es el método más eficiente?

¿Hay algún método mejor en el que no haya pensado?


Editar: Notas para los visitantes a esta pregunta:

  1. Ha habido algunas investigaciones increíblemente detalladas sobre esta cuestión - en particular de Andy y Jon Skeet.

  2. Si ha tropezado con la pregunta mientras buscaba algo, bien vale la pena leer al menos las publicaciones de Andy y Jon en su totalidad.

Parece que hay algunos métodos muy eficientes y la más eficiente depende de los contenidos de las cadenas que tengo que tratar.

Si no puedo predecir las cadenas (que no puedo en mi caso), los métodos de Jon IsEmptyOrWhiteSpace parecen ser más rápidos en general.

Gracias por su contribución. Voy a seleccionar la respuesta de Andy como la "correcta" simplemente porque merece el impulso de reputación por el esfuerzo que puso y Jon ya tiene una reputación de mil setecientos millones.

Respuesta

19

Edición: Nuevas pruebas:

Test orders: 
x. Test name 
Ticks: xxxxx //Empty String 
Ticks: xxxxx //two space 
Ticks: xxxxx //single letter 
Ticks: xxxxx //single letter with space 
Ticks: xxxxx //long string 
Ticks: xxxxx //long string with space 

1. if (myString.Trim().Length == 0) 
ticks: 4121800 
ticks: 7523992 
ticks: 17655496 
ticks: 29312608 
ticks: 17302880 
ticks: 38160224 

2. if (myString.Trim() == "") 
ticks: 4862312 
ticks: 8436560 
ticks: 21833776 
ticks: 32822200 
ticks: 21655224 
ticks: 42358016 


3. if (myString.Trim().Equals("")) 
ticks: 5358744 
ticks: 9336728 
ticks: 18807512 
ticks: 30340392 
ticks: 18598608 
ticks: 39978008 


4. if (myString.Trim() == String.Empty) 
ticks: 4848368 
ticks: 8306312 
ticks: 21552736 
ticks: 32081168 
ticks: 21486048 
ticks: 41667608 


5. if (myString.Trim().Equals(String.Empty)) 
ticks: 5372720 
ticks: 9263696 
ticks: 18677728 
ticks: 29634320 
ticks: 18551904 
ticks: 40183768 


6. if (IsEmptyOrWhitespace(myString)) //See John Skeet's Post for algorithm 
ticks: 6597776 
ticks: 9988304 
ticks: 7855664 
ticks: 7826296 
ticks: 7885200 
ticks: 7872776 

7. is (string.IsNullOrEmpty(myString.Trim()) //Cloud's suggestion 
ticks: 4302232 
ticks: 10200344 
ticks: 18425416 
ticks: 29490544 
ticks: 17800136 
ticks: 38161368 

Y el código utilizado:.

public void Main() 
{ 

    string res = string.Empty; 

    for (int j = 0; j <= 5; j++) { 

     string myString = ""; 

     switch (j) { 

      case 0: 
       myString = ""; 
       break; 
      case 1: 
       myString = " "; 
       break; 
      case 2: 
       myString = "x"; 
       break; 
      case 3: 
       myString = "x "; 
       break; 
      case 4: 
       myString = "this is a long string for testing triming empty things."; 
       break; 
      case 5: 
       myString = "this is a long string for testing triming empty things. "; 

       break; 
     } 

     bool result = false; 
     Stopwatch sw = new Stopwatch(); 

     sw.Start(); 
     for (int i = 0; i <= 100000; i++) { 


      result = myString.Trim().Length == 0; 
     } 
     sw.Stop(); 


     res += "ticks: " + sw.ElapsedTicks + Environment.NewLine; 
    } 


    Console.ReadKey(); //break point here to get the results 
} 
+1

¿Podría comparar también mi respuesta, por favor? –

+0

+1 para realmente probar la diferencia – pyrocumulus

+0

¿Podría comparar mi respuesta (IsNullOrEmpty) también? Entonces tenemos todos los métodos en una situación de banco y en un sistema. – pyrocumulus

4

Realmente no sé cuál es más rápido; aunque mi instinto me dice que es el número uno. Pero aquí hay otro método:

if (String.IsNullOrEmpty(myString.Trim())) 
+0

No pensar en que uno, y es probablemente más seguro también ... – Damovisa

+1

excepto si es nulo arrojará una NullPointerException –

+2

Eso es cierto, sin embargo, ninguno de los otros métodos pasa la "myString = null" -situation. Entonces, en relación con esta pregunta, no es gran cosa. – pyrocumulus

3

Comprobación de la longitud de una cadena por ser cero es la forma más eficiente para comprobar una cadena vacía, así que diría que el número 1:

if (myString.Trim().Length == 0) 

La única manera de para optimizar esto más podría ser para evitar el recorte mediante el uso de una expresión regular compilada (Edición: esto es en realidad mucho más lento que el uso de Trim(). Longitud).

Editar: La sugerencia de utilizar Length proviene de una guía FxCop. También lo probé: es 2-3 veces más rápido que compararlo con una cadena vacía. Sin embargo, ambos enfoques son extremadamente rápidos (estamos hablando de nanosegundos), así que no importa cuál uses. Recortar es mucho más un cuello de botella, es cientos de veces más lento que la comparación real al final.

+0

¿Tiene algún argumento para respaldar esa declaración? – pyrocumulus

+0

¿Puedes explicar por qué? No me malinterpretes, te creo, pero me interesaría. – Damovisa

+0

Las cadenas se almacenan con un encabezado que indica la longitud de la cadena, por lo tanto una cadena. La verificación larga simplemente devuelve ese valor. –

15

(EDIT: Vea la parte inferior del poste de puntos de referencia sobre diferentes micro-optimizaciones del método)

No corte - que podría crear una nueva cadena, que en realidad no se necesita. En su lugar, mirar a través de la cadena de caracteres que no son espacios en blanco (por cualquier definición que desea). Por ejemplo:

public static bool IsEmptyOrWhitespace(string text) 
{ 
    // Avoid creating iterator for trivial case 
    if (text.Length == 0) 
    { 
     return true; 
    } 
    foreach (char c in text) 
    { 
     // Could use Char.IsWhiteSpace(c) instead 
     if (c==' ' || c=='\t' || c=='\r' || c=='\n') 
     { 
      continue; 
     } 
     return false; 
    } 
    return true; 
} 

También puede considerar lo que quiere el método de hacer si es textnull.

Posibles nuevas micro-optimizaciones para experimentar con:

  • Es foreach más rápido o más lento que usando un bucle for como la de abajo? Tenga en cuenta que con el bucle for puede eliminar la prueba "if (text.Length==0)" al principio.

    for (int i = 0; i < text.Length; i++) 
    { 
        char c = text[i]; 
        // ... 
    
  • Igual que el anterior, pero izar la llamada Length. Tenga en cuenta que esto no es bueno para matrices normales, pero poder ser útil para cuerdas. No lo he probado.

    int length = text.Length; 
    for (int i = 0; i < length; i++) 
    { 
        char c = text[i]; 
    
  • En el cuerpo del bucle, ¿hay alguna diferencia (en velocidad) entre lo que tenemos y:

    if (c != ' ' && c != '\t' && c != '\r' && c != '\n') 
    { 
        return false; 
    } 
    
  • ¿Un switch/case ser más rápido?

    switch (c) 
    { 
        case ' ': case '\r': case '\n': case '\t': 
         return false;    
    } 
    

Actualización sobre el comportamiento Recorte

He estado buscando en la forma Trim puede ser tan eficiente como este. Parece que Trim sólo creará una nueva cadena si es necesario. Si se puede volver this o "" lo hará:

using System; 

class Test 
{ 
    static void Main() 
    { 
     CheckTrim(string.Copy("")); 
     CheckTrim(" "); 
     CheckTrim(" x "); 
     CheckTrim("xx"); 
    } 

    static void CheckTrim(string text) 
    { 
     string trimmed = text.Trim(); 
     Console.WriteLine ("Text: '{0}'", text); 
     Console.WriteLine ("Trimmed ref == text? {0}", 
          object.ReferenceEquals(text, trimmed)); 
     Console.WriteLine ("Trimmed ref == \"\"? {0}", 
          object.ReferenceEquals("", trimmed)); 
     Console.WriteLine(); 
    } 
} 

Esto significa que es muy importante que todos los puntos de referencia en esta pregunta deben utilizar una combinación de datos:

  • Cadena vacía
  • espacios en blanco
  • Los espacios en blanco que rodea el texto
  • texto sin espacios en blanco

Por supuesto, el equilibrio del "mundo real" entre estos cuatro es imposible predecir ...

puntos de referencia me he encontrado algunos puntos de referencia de las sugerencias originales vs mía, y la mía parece ganar en todo lo que arrojo, lo que me sorprende dados los resultados en otras respuestas.Sin embargo, también comparé la diferencia entre foreach, for usando text.Length, for usando text.Length una vez y luego invirtiendo el orden de iteración, y for con una longitud de elevación.

Básicamente, el ciclo for es muy ligeramente más rápido, pero al levantar la verificación de longitud es más lento que foreach. Invertir la dirección de bucle for es muy ligeramente más lenta que foreach también. Tengo la firme sospecha de que el JIT está haciendo cosas interesantes aquí, en cuanto a la eliminación de duplicados grada cheques etc.

Código: (ver my benchmarking blog entry para el marco de esto está escrito en contra)

using System; 
using BenchmarkHelper; 

public class TrimStrings 
{ 
    static void Main() 
    { 
     Test(""); 
     Test(" "); 
     Test(" x "); 
     Test("x"); 
     Test(new string('x', 1000)); 
     Test(" " + new string('x', 1000) + " "); 
     Test(new string(' ', 1000)); 
    } 

    static void Test(string text) 
    { 
     bool expectedResult = text.Trim().Length == 0; 
     string title = string.Format("Length={0}, result={1}", text.Length, 
            expectedResult); 

     var results = TestSuite.Create(title, text, expectedResult) 
/*   .Add(x => x.Trim().Length == 0, "Trim().Length == 0") 
      .Add(x => x.Trim() == "", "Trim() == \"\"") 
      .Add(x => x.Trim().Equals(""), "Trim().Equals(\"\")") 
      .Add(x => x.Trim() == string.Empty, "Trim() == string.Empty") 
      .Add(x => x.Trim().Equals(string.Empty), "Trim().Equals(string.Empty)") 
*/ 
      .Add(OriginalIsEmptyOrWhitespace) 
      .Add(IsEmptyOrWhitespaceForLoop) 
      .Add(IsEmptyOrWhitespaceForLoopReversed) 
      .Add(IsEmptyOrWhitespaceForLoopHoistedLength) 
      .RunTests()       
      .ScaleByBest(ScalingMode.VaryDuration); 

     results.Display(ResultColumns.NameAndDuration | ResultColumns.Score, 
         results.FindBest()); 
    } 

    public static bool OriginalIsEmptyOrWhitespace(string text) 
    { 
     if (text.Length == 0) 
     { 
      return true; 
     } 
     foreach (char c in text) 
     { 
      if (c==' ' || c=='\t' || c=='\r' || c=='\n') 
      { 
       continue; 
      } 
      return false; 
     } 
     return true; 
    } 

    public static bool IsEmptyOrWhitespaceForLoop(string text) 
    { 
     for (int i=0; i < text.Length; i++) 
     { 
      char c = text[i]; 
      if (c==' ' || c=='\t' || c=='\r' || c=='\n') 
      { 
       continue; 
      } 
      return false; 
     } 
     return true; 
    } 

    public static bool IsEmptyOrWhitespaceForLoopReversed(string text) 
    { 
     for (int i=text.Length-1; i >= 0; i--) 
     { 
      char c = text[i]; 
      if (c==' ' || c=='\t' || c=='\r' || c=='\n') 
      { 
       continue; 
      } 
      return false; 
     } 
     return true; 
    } 

    public static bool IsEmptyOrWhitespaceForLoopHoistedLength(string text) 
    { 
     int length = text.Length; 
     for (int i=0; i < length; i++) 
     { 
      char c = text[i]; 
      if (c==' ' || c=='\t' || c=='\r' || c=='\n') 
      { 
       continue; 
      } 
      return false; 
     } 
     return true; 
    } 
} 

Resultados:

============ Length=0, result=True ============ 
OriginalIsEmptyOrWhitespace    30.012 1.00 
IsEmptyOrWhitespaceForLoop    30.802 1.03 
IsEmptyOrWhitespaceForLoopReversed  32.944 1.10 
IsEmptyOrWhitespaceForLoopHoistedLength 35.113 1.17 

============ Length=1, result=True ============ 
OriginalIsEmptyOrWhitespace    31.150 1.04 
IsEmptyOrWhitespaceForLoop    30.051 1.00 
IsEmptyOrWhitespaceForLoopReversed  31.602 1.05 
IsEmptyOrWhitespaceForLoopHoistedLength 33.383 1.11 

============ Length=3, result=False ============ 
OriginalIsEmptyOrWhitespace    30.221 1.00 
IsEmptyOrWhitespaceForLoop    30.131 1.00 
IsEmptyOrWhitespaceForLoopReversed  34.502 1.15 
IsEmptyOrWhitespaceForLoopHoistedLength 35.690 1.18 

============ Length=1, result=False ============ 
OriginalIsEmptyOrWhitespace    31.626 1.05 
IsEmptyOrWhitespaceForLoop    30.005 1.00 
IsEmptyOrWhitespaceForLoopReversed  32.383 1.08 
IsEmptyOrWhitespaceForLoopHoistedLength 33.666 1.12 

============ Length=1000, result=False ============ 
OriginalIsEmptyOrWhitespace    30.177 1.00 
IsEmptyOrWhitespaceForLoop    33.207 1.10 
IsEmptyOrWhitespaceForLoopReversed  30.867 1.02 
IsEmptyOrWhitespaceForLoopHoistedLength 31.837 1.06 

============ Length=1002, result=False ============ 
OriginalIsEmptyOrWhitespace    30.217 1.01 
IsEmptyOrWhitespaceForLoop    30.026 1.00 
IsEmptyOrWhitespaceForLoopReversed  34.162 1.14 
IsEmptyOrWhitespaceForLoopHoistedLength 34.860 1.16 

============ Length=1000, result=True ============ 
OriginalIsEmptyOrWhitespace    30.303 1.01 
IsEmptyOrWhitespaceForLoop    30.018 1.00 
IsEmptyOrWhitespaceForLoopReversed  35.475 1.18 
IsEmptyOrWhitespaceForLoopHoistedLength 40.927 1.36 
+0

¿El foreach (carácter de texto) realmente hace una búsqueda en línea o creará una nueva serie de caracteres? – Damovisa

+0

Creará un IEnumerator apropiado asociado con la cadena. No copia la cadena. –

+0

Acabo de probar este método con un millón de iteraciones, es un poco más lento que con Trim(). Sin embargo, creo que podrías ahorrar un poco en la línea evitando GC. –

4

myString.Trim() == 0 Longitud Tomó: 421 ms

myString.Trim() == '' prendieron 468 ms

si (myString.Trim() es igual ("").) Tomó: 515 ms

si (myString.Trim() == String.Empty) Tomó: 484 ms

si (myString.Trim() es igual a (String.Empty)) Tomó:. 500 ms

si (String.IsNullOrEmpty (miCadena .trim())) Tomó: 437 ms

En mis pruebas, parece que myString.Trim() Duración == 0 y sorprendentemente, String.IsNullOrEmpty (myString.Trim()) fueron consistentemente los más rápidos.. Los resultados anteriores son un resultado típico de hacer 10,000,000 comparaciones.

+0

@womp: string.IsNullOrEmpty también verifica la longitud de la cadena (además de hacer la comprobación nula); en esta situación, preferiría mucho consultarlo directamente - opción 1. –

+0

Sí, me sorprendió lo bien que resultó ser. – womp

1

Desde que empecé no puedo comentar, así que aquí está.

if (String.IsNullOrEmpty(myString.Trim())) 

Trim() llamada fallará si miCadena es nula ya que no puede llamar a métodos en un objeto que es nulo (NullReferenceException).

Así que la sintaxis correcta sería algo como esto:

if (!String.IsNullOrEmpty(myString)) 
{ 
    string trimmedString = myString.Trim(); 
    //do the rest of you code 
} 
else 
{ 
    //string is null or empty, don't bother processing it 
} 
+0

Iba a hacer este comentario también, me alegra ver que lo hizo. – Felan

+0

¿cómo vas a recortar cuando myString es nulo? – nawfal

+0

No entendí tu comentario nawfal. – Oakcool

0
public static bool IsNullOrEmpty(this String str, bool checkTrimmed) 
{ 
    var b = String.IsNullOrEmpty(str); 
    return checkTrimmed ? b && str.Trim().Length == 0 : b; 
} 
3

String.IsNullOrWhitespace in .NET 4 Beta 2 también juega en este espacio y tampoco necesitan ser escritas a medida

+1

Este método fue realmente agregado en .NET 4.0 (en 2010), vea el método ['String.IsNullOrWhiteSpace'] (http://msdn.microsoft.com/en-us/library/system.string.isnullorwhitespace.aspx). –

Cuestiones relacionadas