¿Cuántas cadenas se crean en la memoria al concatenar cadenas en Java?

17

Me preguntaron sobre cadenas inmutables en Java. Me encargaron escribir una función que concatenara varias "a" en una cadena.

Lo que escribí:

public String foo(int n) {
    String s = "";
    for (int i = 0; i < n; i++) {
        s = s + "a"
    }
    return s;
}

Luego me preguntaron cuántas cadenas generaría este programa, suponiendo que la recolección de basura no ocurra. Mis pensamientos para n = 3 eran

  1. ""
  2. "un"
  3. "un"
  4. "Automóvil club británico"
  5. "un"
  6. "aaa"
  7. "un"

Esencialmente se crean 2 cadenas en cada iteración del bucle. Sin embargo, la respuesta fue n 2 . ¿Qué cadenas se crearán en la memoria con esta función y por qué es así?

ahalbert
fuente
15
Si te ofrecen este trabajo, huye, corre muy rápido .......
mattnz
@mattnz por múltiples razones (y no solo por el código escrito).
3
Esto toma tiempo de ejecución O (n ^ 2) a menos que el JIT optimice el ciclo, pero no crea n ^ 2 cadenas.
user2357112 es compatible con Monica el

Respuestas:

26

Luego me preguntaron cuántas cadenas generaría este programa, suponiendo que la recolección de basura no ocurra. Mis pensamientos para n = 3 fueron (7)

Las cadenas 1 ( "") y 2 ( "a") son las constantes en el programa, no se crean como parte de las cosas sino que se 'internan' porque son constantes que el compilador conoce. Lea más sobre esto en String interning en Wikipedia.

Esto también elimina las cadenas 5 y 7 del recuento, ya que son lo mismo "a"que la Cadena # 2. Esto deja las cadenas # 3, # 4 y # 6. La respuesta es "se crean 3 cadenas para n = 3" utilizando su código.

El recuento de n 2 obviamente es incorrecto porque en n = 3, esto sería 9 e incluso por su respuesta en el peor de los casos, eso fue solo 7. Si sus cadenas no internadas eran correctas, la respuesta debería haber sido 2n + 1.

Entonces, la pregunta de cómo debe hacer esto?

Como la cadena es inmutable , desea algo mutable, algo que pueda cambiar sin crear nuevos objetos. Ese es el StringBuilder .

Lo primero a tener en cuenta son los constructores. En este caso, sabemos cuánto tiempo durará la cadena, y hay un constructor, lo StringBuilder(int capacity) que significa que asignamos exactamente lo que necesitamos.

A continuación, "a"no necesita ser una cadena , sino que puede ser un personaje 'a'. Esto tiene un aumento de rendimiento menor cuando se llama append(String)vs append(char): con el append(String)método, necesita descubrir cuánto tiempo dura la cadena y hacer algo de trabajo en eso. Por otro lado, charsiempre tiene exactamente un carácter de largo.

Las diferencias de código se pueden ver en StringBuilder.append (String) vs StringBuilder.append (char) . No es algo de lo que preocuparse demasiado , pero si está tratando de impresionar al empleador, es mejor utilizar las mejores prácticas posibles.

Entonces, ¿cómo se ve esto cuando lo juntas?

public String foo(int n) {
    StringBuilder sb = new StringBuilder(n);
    for (int i = 0; i < n; i++) {
        sb.append('a');
    }
    return sb.toString();
}

Se han creado un StringBuilder y un String. No se necesitan cadenas adicionales para ser internado.


Escriba algunos otros programas simples en Eclipse. Instala pmd y ejecútalo en el código que escribes. Tenga en cuenta de qué se queja y arregle esas cosas. Hubiera encontrado la modificación de una Cadena con + en un bucle, y si la hubiera cambiado a StringBuilder, tal vez habría encontrado la capacidad inicial, pero ciertamente detectaría la diferencia entre .append("a")y.append('a')

Comunidad
fuente
9

En cada iteración, Stringel +operador crea un nuevo y se le asigna s. Después del regreso, todos ellos, excepto el último, se recolectan como basura.

Las constantes de cadena les gusta ""y "a"no se crean cada vez, estas son cadenas internas . Como las cadenas son inmutables, se pueden compartir libremente; Esto le sucede a las constantes de cadena.

Para concatenar cadenas de manera eficiente, use StringBuilder.

9000
fuente
Las personas en la entrevista realmente debatieron sobre si el literal era o no, y decidieron que los literales se creaban cada vez. Pero esto tiene más sentido.
ahalbert
66
¿Cómo "debatir" lo que hace un idioma, seguramente leer la especificación y saber con certeza, o no está definido y por lo tanto, no hay una respuesta correcta .....
Mattnz
@mattnz Puede ser interesante saber qué hace el compilador / tiempo de ejecución que está utilizando, incluso cuando se trata de detalles de implementación. Esto se aplica especialmente al rendimiento.
svick
1
@svick: Puede ganar mucho haciendo suposiciones, luego se actualiza el compilador, se cambia una optimización, etc. El comportamiento cambia causando errores porque confió en el comportamiento no especificado en lugar del comportamiento definido. Usted sabe lo que dicen sobre la optimización: a) déjelo en manos de expertos yb) todavía no es un experto. :) Si la dependencia se basa únicamente en el rendimiento, pero sigue siendo la especificación del idioma, solo perderá el rendimiento. Muchas veces he visto que el código que se basaba en comportamientos inespecíficos o específicos del compilador se rompía de manera inesperada (principalmente C y C ++).
mattnz
@mattnz Entonces, ¿cómo propone tomar decisiones relacionadas con el rendimiento? Por lo general, lo mejor que puede obtener de la especificación / documentación son las complejidades big-O, pero eso no es suficiente. En cualquier caso, el rendimiento siempre dependerá de la implementación, por lo que creo que está bien confiar en los detalles de la implementación cuando se trata del rendimiento.
svick
4

Como MichaelT explica en su respuesta, su código asigna cadenas O (n). Pero también asigna O (n 2 ) bytes de memoria y se ejecuta en tiempo O (n 2 ).

Asigna O (n 2 ) bytes, porque las cadenas que está asignando tienen longitudes 0, 1, 2, ..., n-1, n, que suman a (n 2 + n) / 2 = O (n 2 ).

El tiempo también es O (n 2 ), porque la asignación de la cadena i-ésima requiere la copia de la cadena (i-1), que tiene una longitud i-1. Esto significa que cada byte asignado debe copiarse, lo que llevará tiempo O (n 2 ).

¿Quizás esto es lo que querían decir los entrevistadores?

svick
fuente
¿No debería ser la ecuación (n ^ 2 + n) / 2, como aquí ?
HeyJude