java - obtener - ¿Por qué agregar "" a una memoria de almacenamiento de cadenas?
obtener informacion del sistema con java (9)
Usé una variable con muchos datos, digamos String data
. Quería usar una pequeña parte de esta cadena de la siguiente manera:
this.smallpart = data.substring(12,18);
Después de algunas horas de depuración (con un visualizador de memoria) descubrí que el campo de objetos smallpart
recordaba todos los datos de los data
, aunque solo contenía la subcadena.
Cuando cambié el código a:
this.smallpart = data.substring(12,18)+"";
..¡El problema fue resuelto! ¡Ahora mi aplicación usa muy poca memoria ahora!
¿Cómo es eso posible? ¿Alguien puede explicar esto? Creo que this.smallpart siguió haciendo referencia a los datos, pero ¿por qué?
ACTUALIZACIÓN: ¿Cómo puedo borrar el gran String entonces? ¿Data = new String (data.substring (0,100)) hará la cosa?
Creo que this.smallpart siguió haciendo referencia a los datos, pero ¿por qué?
Debido a que las cadenas de Java consisten en una matriz char, un desplazamiento inicial y una longitud (y un hashCode en caché). Algunas operaciones String como substring()
crean un nuevo objeto String que comparte la matriz char del original y simplemente tiene diferentes campos de desplazamiento y / o longitud. Esto funciona porque la matriz char de una Cadena nunca se modifica una vez que se ha creado.
Esto puede ahorrar memoria cuando muchas subcadenas se refieren a la misma cadena básica sin replicar partes superpuestas. Como habrás notado, en algunas situaciones, puede mantener la recolección de basura de datos que ya no son necesarios.
La forma "correcta" de corregir esto es el new String(String)
constructor new String(String)
, es decir,
this.smallpart = new String(data.substring(12,18));
Por cierto, la mejor solución general sería evitar tener cadenas muy grandes en primer lugar, y procesar cualquier entrada en fragmentos más pequeños, unos pocos KB a la vez.
Agregar "" a una cadena a veces ahorrará memoria.
Digamos que tengo una gran cadena que contiene un libro completo, un millón de caracteres.
Luego creo 20 cadenas que contienen los capítulos del libro como subcadenas.
Luego creo 1000 cadenas que contienen todos los párrafos.
Luego creo 10,000 cadenas que contienen todas las oraciones.
Luego creo 100.000 cadenas que contienen todas las palabras.
Todavía uso solo 1,000,000 de caracteres. Si agrega "" a cada capítulo, párrafo, oración y palabra, usa 5,000,000 de caracteres.
Por supuesto, es completamente diferente si solo extraes una sola palabra de todo el libro, y todo el libro podría ser basura, pero no porque esa palabra contenga una referencia.
Y nuevamente es diferente si tiene una cadena de un millón de caracteres y elimina pestañas y espacios en ambos extremos, por ejemplo, 10 llamadas para crear una subcadena. La forma en que funciona o funciona Java evita copiar un millón de caracteres cada vez. Hay compromiso, y es bueno si sabes cuáles son los compromisos.
Cuando utilizas substring
, en realidad no crea una nueva cadena. Todavía se refiere a la cadena original, con una restricción de desplazamiento y tamaño.
Por lo tanto, para permitir que se recopile la cadena original, debe crear una nueva cadena (utilizando una cadena new String
o lo que tenga).
En Java las cadenas son objetos imputables y una vez que se crea una cadena, permanece en la memoria hasta que el recolector de basura la limpia (y esta limpieza no es algo que pueda darse por sentado).
Cuando se llama al método de subcadena, Java no crea una cadena realmente nueva, sino que simplemente almacena un rango de caracteres dentro de la cadena original.
Entonces, cuando creaste una nueva cadena con este código:
this.smallpart = data.substring(12, 18) + "";
en realidad creó una nueva cadena cuando concatena el resultado con la cadena vacía. Es por eso.
En primer lugar, al llamar a java.lang.String.substring
crea una nueva ventana en la String
original con el uso del desplazamiento y la longitud en lugar de copiar la parte significativa de la matriz subyacente.
Si echamos un vistazo más de cerca al método de substring
, notaremos que un constructor de cadenas llama a String(int, int, char[])
y le pasa todo char[]
que representa la cadena . Eso significa que la subcadena ocupará tanta cantidad de memoria como la cadena original.
Ok, pero ¿por qué + ""
da como resultado una demanda de menos memoria que sin ella?
Hacer un +
en strings
se implementa a través de la llamada al método StringBuilder.append
. Mire la implementación de este método en la clase AbstractStringBuilder
que nos dirá que finalmente hace arraycopy
con la parte que realmente necesitamos (la substring
).
¿Alguna otra solución?
this.smallpart = new String(data.substring(12,18));
this.smallpart = data.substring(12,18).intern();
Haciendo lo siguiente:
data.substring(x, y) + ""
crea un nuevo objeto de cadena (más pequeño) y descarta la referencia a la Cadena creada por subcadena (), lo que habilita la recolección de basura de esta.
Lo importante es darse cuenta de que la substring()
da una ventana a una cadena existente , o mejor dicho, la matriz de caracteres subyacente a la cadena original. Por lo tanto, consumirá la misma memoria que la Cadena original. Esto puede ser ventajoso en algunas circunstancias, pero problemático si desea obtener una subcadena y deshacerse de la Cadena original (tal como lo ha descubierto).
Eche un vistazo al método substring () en la fuente de JDK String para más información.
EDITAR: para responder a su pregunta complementaria, la construcción de una nueva Cadena de la subcadena reducirá el consumo de memoria, siempre que se guarden todas las referencias a la Cadena original.
NOTA (enero de 2013). El comportamiento anterior ha cambiado en Java 7u6 . El patrón flyweight ya no se usa y la substring()
funcionará como era de esperar.
Para resumir, si crea muchas subcadenas a partir de un pequeño número de cuerdas grandes, utilice
String subtring = string.substring(5,23)
Ya que solo usa el espacio para almacenar las cadenas grandes, pero si está extrayendo un puñado de cadenas pequeñas, de las grandes cadenas, entonces
String substring = new String(string.substring(5,23));
Mantendrá el uso de la memoria baja, ya que las cadenas grandes pueden recuperarse cuando ya no se necesiten.
A lo que llamas new String
es un recordatorio útil de que realmente estás obteniendo una nueva cadena, en lugar de una referencia a la original.
Según lo documentado por jwz en 1997 :
Si tiene una cadena enorme, extraiga una subcadena () de ella, agárrese a la subcadena y permita que la cadena más larga se convierta en basura (en otras palabras, la subcadena tiene una vida útil más larga) los bytes subyacentes de la cadena enorme nunca van lejos.
Si miras el origen de la substring(int, int)
, verás que regresa:
new String(offset + beginIndex, endIndex - beginIndex, value);
donde el value
es el char[]
original char[]
. Entonces obtienes un nuevo String pero con el mismo char[]
subyacente char[]
.
Cuando lo haces, data.substring() + ""
, obtienes una nueva cadena con un nuevo char[]
subyacente char[]
.
En realidad, su caso de uso es la única situación donde debería usar el constructor String(String)
:
String tiny = new String(huge.substring(12,18));