2010-06-05 15 views
46

Recientemente encontré un modismo que no había visto antes: ensamblado de cadenas por StringWriter y PrintWriter. Quiero decir, sé cómo usarlos, pero siempre he usado StringBuilder. ¿Hay alguna razón concreta para preferir uno sobre el otro? El método StringBuilder me parece mucho más natural, ¿pero es solo estilo?Conjunto de cadenas por StringBuilder vs StringWriter y PrintWriter

He visto varias preguntas aquí (incluida esta que es la más cercana: StringWriter or StringBuilder), pero ninguna en la que las respuestas abordan realmente la cuestión de si hay una razón para preferir una sobre otra para el montaje simple de cadenas.

Este es el idioma que he visto y usado muchas veces: el montaje de cuerda de StringBuilder:


    public static String newline = System.getProperty("line.separator"); 
    public String viaStringBuilder() { 
     StringBuilder builder = new StringBuilder(); 
     builder.append("first thing").append(newline); // NOTE: avoid doing builder.append("first thing" + newline); 
     builder.append("second thing").append(newline); 
     // ... several things 
     builder.append("last thing").append(newline); 
     return builder.toString(); 
    } 

Y este es el nuevo lenguaje: el montaje de cuerda de StringWriter y PrintStream:


    public String viaWriters() { 
     StringWriter stringWriter = new StringWriter(); 
     PrintWriter printWriter = new PrintWriter(stringWriter); 
     printWriter.println("first thing"); 
     printWriter.println("second thing"); 
     // ... several things 
     printWriter.println("last thing"); 
     printWriter.flush(); 
     printWriter.close(); 
     return stringWriter.toString(); 
    } 

Editar Parece que no hay ninguna razón concreta para preferir uno sobre el otro, por lo que he aceptado la respuesta que mejor se ajusta a mi comprensión, y + 1ed todos los demás ans wers. Además, publiqué una respuesta por mi cuenta, dando los resultados de la evaluación comparativa que realicé, en respuesta a una de las respuestas. Gracias a todos.

Editar nuevo Resulta que no es una razón concreta para preferir uno (específicamente el StringBuilder) sobre el otro. Lo que me perdí la primera vez fue la adición de la nueva línea. Cuando agregas una nueva línea (como la anterior, como un apéndice separado), es un poco más rápida, no enormemente, pero junto con la claridad de intención, definitivamente es mejor. Ver mi respuesta a continuación para los tiempos mejorados.

+5

No debe usar 'constructor.agregar ("lo primero" + nueva línea) '. El problema es que hay una asignación de Cadena adicional que no es necesaria (primero crea "lo primero \ n" y luego copia los datos de cadena al constructor). En cambio, hágalo así: 'builder.append (" first thing "). Append (newline)'. Esto copia directamente '" first first "' al constructor y luego 'newline'. Por supuesto, StringBuilder solo debe usarse si el número de cosas no es constante (por ejemplo, está en un bucle), javac ya optimiza '" a "+" b "+" c "' en 'new StringBuilder(). Append (" a "). anexar (" b "). anexar (" c "). toString()'. – robinst

+0

@robinst Disculpas por la respuesta muy lenta. Acabo de volver a visitar esta pregunta y noté tu comentario. Excelente punto, y he rehecho mi prueba. Ver ediciones. – CPerkins

+3

'Throwable.printStackTrace()' requiere un escritor. – ceving

Respuesta

16

Estilísticamente, el enfoque StringBuilder es más limpio. Se trata de menos líneas de código y está utilizando una clase que se diseñó específicamente para la construcción de cadenas.

La otra consideración es cuál es más eficiente. La mejor forma de responder sería comparar las dos alternativas.Pero hay algunos indicadores claros de que StringBuilder debería ser más rápido. Para empezar, un StringWriter utiliza StringBuilder StringBuffer debajo del capó para mantener los caracteres escritos en la "secuencia".

+0

Gracias, definitivamente estoy de acuerdo con el estilo. Buen punto sobre StringWriter usando un StringBuilder. Realmente debería haber pedido el código. – CPerkins

+1

En realidad, StringWriter parece usar un StringBuffer, no un StringBuilder. Eso introduce la sincronización, que no parece necesaria en este caso. – CPerkins

+1

Probablemente un problema de compatibilidad con versiones anteriores ya que StringBuilder se introdujo en Java 1.5 y con la API StringWriter ya establecida, hubiera sido demasiado tarde para cambiarla. – krock

12

Escritores - PrintWriter escribe en una secuencia. Hace cosas raras como suprimir IOExceptions. System.out es uno de estos. - StringWriter es un escritor que escribe en un StringBuffer (similar a ByteArrayOutputStream para OutputStreams)

StringBuilder - y por supuesto StringBuilder es lo que quiere si simplemente desea construcciones de cadenas en la memoria.

Conclusión

escritores de diseño están diseñados para empujar los datos de caracteres a cabo una corriente (por lo general a un archivo oa través de una conexión) así que la posibilidad de utilizar los escritores simplemente montar cuerdas parece ser un poco de una efecto secundario.

StringBuilder (y su hermana sincronizada StringBuffer) son diseñadas para ensamblar cadenas (leer cadena golpeando). Si miras al StringBuilder API, puedes ver que no solo puedes agregar vainilla sino también reemplazar, invertir, insertar, etc. StringBuilder es tu amigo de la construcción de Cuerdas.

+0

Gracias, sí, entiendo lo que cada uno hace. Pero me he topado con este nuevo modismo (para mí) en el código escrito por algunas personas muy inteligentes, y me pregunto si tenían una buena razón para preferirlo. – CPerkins

+0

OK, no hay problema. He agregado una sección de conclusión a la respuesta. – krock

+0

Así que la razón es básicamente estilística: se basa en la idea de que ** la intención ** de StringBuilder es manipular cadenas, mientras que la capacidad de hacerlo con los Escritores es un efecto secundario. – CPerkins

34

StringWriter es lo que usa cuando quiere escribir en una cadena, pero está trabajando con una API que espera un Writer o un Stream. No es una alternativa, es un compromiso: utiliza StringWriter solo cuando es necesario.

+0

Gracias, y eso tiene sentido, pero he encontrado esto ampliamente utilizado en una base de código escrita por un grupo de personas muy inteligentes, de hecho, en promedio, parecen ser los más inteligentes con los que he trabajado. Así que estoy buscando razones por las que podrían haberlo hecho. – CPerkins

+3

Esta debería ser la respuesta aceptada a esta pregunta. De hecho, me encontré con un escenario en el que me vi obligado a utilizar StringWriter porque la API (API de terceros) espera la instancia de Writer y solo quería escribir los contenidos en la cadena en lugar de file/stdout. @ Alan simplemente lo clavó cuando dijo "Usas StringWriter cuando tienes que hacerlo". –

+2

Me encontré con un escenario en el que me vi obligado a utilizar 'StringWriter' porque la API _of_ _the_' java.lang.Throwable'_class_ no tiene ningún método que reciba un 'StringBuilder' como argumento cuando el seguimiento de la pila de un debe imprimirse la excepción (solo hay métodos que aceptan un ['PrintStream'] (http://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html#printStackTrace%28java.io .PrintStream% 29) o ['PrintWriter'] (http://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html#printStackTrace%28java.io.PrintWriter% 29)). –

18

Bien, ya que las respuestas parecen enfatizar las razones estilísticas para preferir una sobre la otra, decidí generar una prueba de tiempo.

Editar: Siguiendo el comentario de robinst anterior, ahora tengo tres métodos: una que hace PrintStream (StringWriter) Anexión de estilo, y dos que utilizan StringBuilder: una con el append de la nueva línea dentro del modo de adición (como en el original: builder.append(thing + newline);), y el otro hace un apéndice por separado (como arriba: builder.append(thing).append(newline);).

Seguí mi esquema habitual de evaluación comparativa: primero llame a los métodos varios (1000 en este caso) para dar tiempo al optimizador para calentar, luego llame a cada uno una gran cantidad de veces (100,000 en este caso) en secuencia alterna, de modo que cualquier cambio en el entorno de tiempo de ejecución de la estación de trabajo se distribuya más justamente, y ejecute la prueba varias veces y promedie los resultados.

Ah, y por supuesto el número de líneas creadas y la longitud de las líneas se aleatoriza pero se mantiene igual para cada par de llamadas, porque quería evitar cualquier efecto posible del tamaño del búfer o tamaño de línea en el resultados.

El código para esto está aquí: http://pastebin.com/vtZFzuds

Nota: Todavía no he actualizado el código Pastebin para reflejar la nueva prueba.

TL, DR? Los resultados promedio para 100.000 llamadas a cada método son bastante cerca:

  • 0.11908 ms por llamada utilizando StringBuilder (+ salto de línea dentro de la paren)
  • 0.10201 ms por llamada utilizando StringBuilder (nueva línea como un append separado)
  • 0,10803 ms por llamadas que utilizan el PrintStream (StringWriter)

que es tan estrecha que el momento es casi irrelevante para mí, así que voy a seguir haciendo las cosas como siempre lo hacía: StringBuilder (con datos anexados por separado) por razones estilísticas. Por supuesto, mientras trabajo en esta base de códigos establecida y madura, me mantendré mayormente de acuerdo con las convenciones existentes, a fin de minimizar la sorpresa.

+0

Los resultados no son sorprendentes. Suponiendo que cualquier llamada particular a 'write (...)' en 'StringWriter' simplemente ejecuta una llamada coincidente para' append (...) 'en el' StringBuilder' interno, este es exactamente el tipo de repetitivo que el Java JIT está diseñado para optimizar de distancia. – jdmichal

+1

@jdmichal 'StringWriter' usa **' StringBuffer' **, que es la alternativa 'synchronized' al' StringBuilder'. Entonces, el JIT también necesita eliminar bloqueos innecesarios, lo cual es mucho mejor en estos días de lo que solía ser. Aún así, el problema no es trivial y la similitud si el rendimiento es quizás un poco más sorprendente de lo que podría pensar ... –

+1

¡Gracias por llevarlo a cabo! –

7

Veo dos ventajas del método PrintWriter: (1) No tiene que agregar "+ nueva línea" al final de cada línea, lo que realmente da como resultado un código más corto si tiene mucha escritura hacer. (2) No tiene que llamar a System.getProperty(); dejas que el PrintWriter se preocupe por cuál debería ser el carácter de nueva línea.

+0

buenos pensamientos. No estoy tan preocupado por tener que llamar a 'System.getProperty()', ya que es un costo único que se amortiza a cero si, como dices, estás escribiendo una gran cantidad de código. Definitivamente es un buen punto acerca de tener que '+ newline' con' StringBuilder'. – CPerkins

1

en este enlace: http://nohack.eingenetzt.com/java/java-stringwriter-vs-stringbuilder el autor muestra que la StringWriter es incluso un poco más rápido que el StringBuilder.and también dijo: "Por eso, cuando grandes cantidades de datos que entran en juego la StringWriter muestra claramente su superioridad en el rendimiento sobre el StringBuilder. "

Cuestiones relacionadas