Uso eficiente de StringBuffer en Dart

En Dart, los objetos String son inmutables. Esto significa que no puedes modificar un string existente; cualquier operación que parezca “cambiarlo”, como una concatenación con el operador + o una interpolación, en realidad está creando un objeto String completamente nuevo en la memoria con el contenido combinado.

Si intentas construir un texto largo concatenando strings dentro de un bucle, estarás creando miles de objetos intermedios. Esto genera un problema de rendimiento crítico: la complejidad temporal escala de forma cuadrática ($O(n^2)$) y la presión sobre el Garbage Collector aumenta drásticamente, ya que el sistema debe gestionar y limpiar constantemente todos esos objetos temporales que ya no se usan.

Para evitar esto, utilizamos StringBuffer. En lugar de crear un nuevo objeto en cada paso, StringBuffer mantiene un buffer interno (un área de memoria reservada) donde va acumulando los fragmentos de texto. La creación del String final solo ocurre cuando invocas explícitamente el método toString(). Esto reduce la complejidad a un tiempo lineal ($O(n)$), siendo la forma correcta de construir cadenas de texto dinámicas, como la generación de archivos CSV, la construcción de queries SQL complejas o la serialización manual de objetos.

class Transaccion {
  final String id;
  final double monto;
  final String categoria;

  Transaccion(this.id, this.monto, this.categoria);
}

void main() {
  final transacciones = [
    Transaccion('TXN-001', 150.50, 'Alimentación'),
    Transaccion('TXN-002', 25.00, 'Transporte'),
    Transaccion('TXN-003', 300.00, 'Salud'),
  ];

  // Instanciamos el buffer para acumular los datos
  final buffer = StringBuffer();

  // Agregamos la cabecera usando writeln para incluir el salto de línea
  buffer.writeln('ID,Monto,Categoría');

  for (final tx in transacciones) {
    // write() convierte el objeto a string mediante su método toString()
    // y lo añade al buffer sin crear strings intermedios innecesarios
    buffer.write(tx.id);
    buffer.write(',');
    
    // Podemos pasarle cualquier objeto; StringBuffer se encarga de la conversión
    buffer.write(tx.monto);
    buffer.write(',');
    
    buffer.write(tx.categoria);
    
    // Finalizamos la línea actual
    buffer.writeln();
  }

  // El método toString() es el único momento donde se materializa el String final
  final csvFinal = buffer.toString();

  print('--- Resultado CSV ---');
  print(csvFinal);
  print('--- Fin ---');
}

En el ejemplo anterior, hemos evitado la creación de objetos String temporales en cada iteración del bucle. Al llamar a buffer.write(tx.monto), el StringBuffer no está concatenando un string con otro; simplemente está copiando los bytes del valor al buffer interno.

Un detalle importante es que StringBuffer implementa la interfaz StringSink. Esto es fundamental porque si una función que tú escribes necesita recibir algo para escribir texto, es mejor que pida un StringSink como parámetro en lugar de un StringBuffer específico. De esta forma, tu función será más flexible: podrá trabajar con un StringBuffer para construir un reporte, o con un StringIo si estás escribiendo directamente en un archivo o en un flujo de red.

El uso de buffer.writeln() es una conveniencia que equivale a write(objeto) seguido de un salto de línea, lo cual es ideal para estructuras de datos por filas. Por otro lado, buffer.writeAll(iterable, separator) sería la opción ideal si tuvieras una lista de elementos que quieres unir con una coma de forma masiva, optimizando aún más la lógica.

El error frecuente

Un error común es intentar “concatenar” el objeto StringBuffer directamente con otro string usando el operador + dentro de un bucle, creyendo que esto es eficiente.

// ERROR: Esto anula todo el propósito de usar StringBuffer
var buffer = StringBuffer();
for (var i = 0; i < 1000; i++) {
  // MAL: Llamas implícitamente a buffer.toString() y creas un nuevo string
  // cada vez, volviendo al problema de O(n^2)
  buffer = StringBuffer('${buffer} - Item $i'); 
}

Si haces esto, estás forzando a la VM a extraer todo el contenido del buffer, crear un nuevo string concatenado y asignar un nuevo StringBuffer en cada iteración. Para que el StringBuffer sea efectivo, debes usar sus métodos internos (write, writeln, etc.) para alimentar el buffer existente, no reasignar la variable con concatenaciones manuales.

39

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio