¿Es una buena práctica usar java.lang.String.intern ()?

194

El Javadoc sobre String.intern()no da muchos detalles. (En pocas palabras: devuelve una representación canónica de la cadena, lo que permite comparar las cadenas internas usando ==)

  • ¿Cuándo usaría esta función a favor String.equals()?
  • ¿Hay efectos secundarios no mencionados en el Javadoc, es decir, más o menos optimización por parte del compilador JIT?
  • ¿Hay otros usos de String.intern()?
Daniel Rikowski
fuente
14
Llamar a intern () tiene su propio impacto en el rendimiento, usar intern () para mejorar el rendimiento necesita ser probado para asegurar que realmente acelere su programa significativamente para que valga la complejidad adicional. También puede usar esto para reducir el consumo de memoria para tablas grandes con valores relativamente repetitivos. Sin embargo, en ambos casos hay otras opciones que podrían ser mejores.
Peter Lawrey
Sí, intern () tiene su propio impacto en el rendimiento. Especialmente porque el costo interno () aumenta linealmente a medida que pasa cadenas y mantiene una referencia a ellas. Al menos en un sol / oráculo 1.6.0_30 vm.
lacroix1547

Respuestas:

125

¿Cuándo usaría esta función en favor de String.equals ()

cuando necesita velocidad ya que puede comparar cadenas por referencia (== es más rápido que igual)

¿Hay efectos secundarios no mencionados en el Javadoc?

La desventaja principal es que debes recordar asegurarte de que realmente haces () todas las cadenas que vas a comparar. Es fácil olvidar intern () todas las cadenas y luego puede obtener resultados confusamente incorrectos. Además, por el bien de todos, asegúrese de documentar muy claramente que confía en las cadenas que se están internalizando.

La segunda desventaja si decide internalizar cadenas es que el método intern () es relativamente costoso. Tiene que administrar el conjunto de cadenas únicas para que haga un poco de trabajo (incluso si la cadena ya se ha internalizado). Por lo tanto, tenga cuidado en el diseño de su código para que, por ejemplo, intern () todas las cadenas apropiadas en la entrada para que no tenga que preocuparse más por eso.

(de JGuru)

Tercera desventaja (solo Java 7 o menos): las cadenas internadas viven en el espacio PermGen, que generalmente es bastante pequeño; puede encontrarse con un OutOfMemoryError con mucho espacio de almacenamiento dinámico libre.

(de Michael Borgwardt)

dfa
fuente
64
Una tercera desventaja: los Strings internos viven en el espacio PermGen, que generalmente es bastante pequeño; puede encontrarse con un OutOfMemoryError con mucho espacio de almacenamiento dinámico libre.
Michael Borgwardt
15
Las máquinas virtuales más nuevas de AFAIK también recolectan basura del espacio PermGen.
Daniel Rikowski el
31
El pasante se trata de gestión de memoria, no de velocidad de comparación. La diferencia entre if (s1.equals(s2))y if (i1 == i2)es mínima a menos que tenga muchas cadenas largas con los mismos personajes principales. En la mayoría de los usos del mundo real (que no sean URL), las cadenas diferirán entre los primeros caracteres. Y las largas cadenas if-else son un olor a código de todos modos: use enumeraciones y mapas de functor.
kdgregory
25
aún puede usar la sintaxis s1.equals en todo su programa, NO use ==, .equals use == internamente para la evaluación de cortocircuito
gtrak
15
Michael Borgwardt NO dijo que los hilos internados no se pueden recolectar basura. Y esa es una afirmación FALSA. Lo que dicen los comentarios de Michael (correctamente) es más sutil que eso.
Stephen C
193

Esto no tiene (casi) nada que ver con la comparación de cadenas. El internamiento de cadenas está destinado a ahorrar memoria si tiene muchas cadenas con el mismo contenido en su aplicación. Al usar String.intern()la aplicación, solo tendrá una instancia a largo plazo y un efecto secundario es que puede realizar una comparación de igualdad de referencia rápida en lugar de una comparación de cadena normal (pero esto generalmente no es aconsejable porque es realmente fácil de romper olvidando internarse solo una sola instancia)

Daniel Brückner
fuente
44
Eso no está bien. El internamiento de cadenas ocurre siempre, automáticamente, cuando se evalúa cada expresión de cadena. Siempre hay una copia para cada cadena de caracteres única utilizada y se "comparte internamente" si se producen varios usos. Llamar a String.intern () no hace que todo esto suceda, solo devuelve la representación canónica interna. Ver javadoc.
Glen Best
16
Es necesario aclarar: la internación siempre ocurre automáticamente para las cadenas constantes en tiempo de compilación (literales y expresiones fijas). Además, ocurre cuando se llama a String.intern () en tiempo de ejecución evaluado dinámicamente cadenas.
Glen Best
¿Quiere decir que si hay 1000 objetos de "Hola" en Heap y realizo intern () en uno de ellos, entonces el resto de 999 objetos serán destruidos automáticamente?
Arun Raaj
@ArunRaaj no, tendrá sus 1000 todavía en el montón, y uno adicional en el grupo interno, que puede estar listo para volver a usarlo más tarde str.intern()cuando lo stresté "Hello".
Matthieu
37

String.intern()definitivamente es basura recolectada en las JVM modernas.
Lo siguiente NUNCA se queda sin memoria, debido a la actividad de GC:

// java -cp . -Xmx128m UserOfIntern

public class UserOfIntern {
    public static void main(String[] args) {
        Random random = new Random();
        System.out.println(random.nextLong());
        while (true) {
            String s = String.valueOf(random.nextLong());
            s = s.intern();
        }
    }
}

Vea más (de mí) sobre el mito de String.intern no GCed () .

Gili Nachum
fuente
26
OutOfMemoryException- no, no el código anterior, en mi cerebro : enlace al artículo de Java, que apunta a este artículo, que apunta al artículo de Java, que ... :-)
user85421
Aunque puede ver que la publicación fue editada para agregar ese enlace;)
Riking
3
Es posible que desee mencionar que usted también es el autor de la referencia externa a la que se vincula.
Thorbjørn Ravn Andersen
11
@Carlos vinculando una referencia externa que se vincula de nuevo a stackoverflow debería causar un ... Stackoverflow :)
Seiti
2
@Seiti Las referencias circulares se detectan fácilmente en estos días: p
Ajay
16

Recientemente escribí un artículo sobre la implementación de String.intern () en Java 6, 7 y 8: String.intern en Java 6, 7 y 8 - agrupación de cadenas .

Espero que contenga suficiente información sobre la situación actual con la agrupación de cadenas en Java.

En una palabra:

  • Evite String.intern()en Java 6, porque entra en PermGen
  • Prefiero String.intern()en Java 7 y Java 8: usa 4-5 veces menos memoria que rodar su propio grupo de objetos
  • Asegúrese de sintonizar -XX:StringTableSize(el valor predeterminado es probablemente demasiado pequeño; establezca un número Prime)
mik1
fuente
3
Por favor, no solo publique enlaces en su blog, algunos lo consideran SPAM. Además, los enlaces de blog tienen una notable tendencia a morir 404. Resuma su artículo en línea aquí, o deje ese enlace en un comentario a la pregunta.
Mat
3
Gracias por escribir que @ mik1! Artículo muy informativo, claro y actualizado. (
Regresé
1
Gracias por mencionar el argumento -XX. También puede usar esto para ver las estadísticas de la tabla: -XX: + PrintStringTableStatistics
csadler
13

Comparar cadenas con == es mucho más rápido que con equals ()

5 Tiempo más rápido, pero dado que la comparación de cadenas generalmente representa solo un pequeño porcentaje del tiempo de ejecución total de una aplicación, la ganancia general es mucho menor que eso, y la ganancia final se diluirá a un pequeño porcentaje.

String.intern () separa la cadena de Heap y la coloca en PermGen

Las cadenas internalizadas se colocan en un área de almacenamiento diferente: Generación permanente, que es un área de la JVM que está reservada para objetos que no son de usuario, como Clases, Métodos y otros objetos JVM internos. El tamaño de esta área es limitado y es mucho más valioso que el montón. Al ser esta área más pequeña que Heap, hay más probabilidades de usar todo el espacio y obtener una OutOfMemoryException.

La cadena String.intern () es basura recolectada

En las nuevas versiones de JVM, las cadenas internalizadas son basura recolectada cuando ningún objeto hace referencia a ellas.

Teniendo en cuenta los 3 puntos anteriores, podría deducir que String intern () podría ser útil solo en pocas situaciones cuando realiza muchas comparaciones de cadenas, sin embargo, es mejor no usar cadenas internas si no sabe exactamente qué estás haciendo ...

aleroot
fuente
1
Solo para agregar, las excepciones de memoria de almacenamiento dinámico a veces se pueden recuperar, especialmente en modelos con subprocesos como aplicaciones web. Cuando permgen se agota, una aplicación normalmente no funcionará permanentemente y, a menudo, se agotarán los recursos hasta que se elimine.
Taylor
7

¿Cuándo usaría esta función en favor de String.equals ()

Dado que hacen cosas diferentes, probablemente nunca.

Internar cadenas por razones de rendimiento para que pueda compararlas para la igualdad de referencia solo será beneficioso si mantiene referencias a las cadenas por un tiempo: las cadenas que provienen de la entrada del usuario o IO no serán internados.

Eso significa que en su aplicación usted recibe información de una fuente externa y la procesa en un objeto que tiene un valor semántico, como dice un identificador, pero ese objeto tiene un tipo indistinguible de los datos sin procesar y tiene diferentes reglas sobre cómo debería ser el programador úsalo.

Casi siempre es mejor crear un UserIdtipo que está internado (es fácil crear un mecanismo de internamiento genérico seguro para subprocesos) y que actúa como una enumeración abierta, que sobrecargar el java.lang.Stringtipo con semántica de referencia si se trata de una ID de usuario.

De esta forma, no se confunde si una Cadena en particular ha sido internada o no, y puede encapsular cualquier comportamiento adicional que necesite en la enumeración abierta.

Pete Kirkham
fuente
6

No conozco ninguna ventaja, y si hubiera una, pensaría que equals () usaría internamente () internamente (lo que no hace).

Rompiendo mitos internos ()

objetos
fuente
77
A pesar de que dice que usted no es consciente de ninguna ventaja, el ligado de comparación identifica publicado a través == como 5 veces más rápido y por lo tanto importante para el código performant de texto centrado
Brian Agnew
3
Cuando tenga que comparar muchos textos, eventualmente se quedará sin espacio en PermGen. Cuando no hay tanta comparación de texto para hacer, la diferencia de velocidad no importa. De cualquier manera, simplemente no intern () tus cadenas. Que no vale la pena.
Bombe
También continúa diciendo que la ganancia relativa general generalmente será pequeña.
objetos
No creo que ese tipo de lógica sea válida. Buen enlace sin embargo!
Daniel Rikowski el
1
@DR: ¿qué lógica? Esa es una gran falacia. @objects: lo siento, pero sus argumentos no son suficientes. Hay muy buenas razones para usar intern, y muy buenas razones para que equalsno lo haga de manera predeterminada. El enlace que publicaste es completo. El último párrafo incluso admite que interntiene un escenario de uso válido: procesamiento de texto pesado (por ejemplo, un analizador sintáctico). Concluir que "[XYZ] es peligroso si no sabes lo que estás haciendo" es tan banal que duele físicamente.
Konrad Rudolph el
4

Daniel Brückner tiene toda la razón. El internamiento de cadenas está destinado a ahorrar memoria (montón). Nuestro sistema actualmente tiene un hashmap gigante para almacenar ciertos datos. A medida que el sistema se escala, el hashmap será lo suficientemente grande como para hacer que el montón se quede sin memoria (como hemos probado). Al internar todas las cadenas duplicadas de todos los objetos en el mapa hash, nos ahorra una cantidad significativa de espacio de almacenamiento dinámico.

También en Java 7, las cadenas internadas ya no viven en PermGen, sino en el montón. Por lo tanto, no necesita preocuparse por su tamaño y sí, se recolecta la basura:

En JDK 7, las cadenas internas ya no se asignan en la generación permanente del montón de Java, sino que se asignan en la parte principal del montón de Java (conocidas como las generaciones jóvenes y viejas), junto con los otros objetos creados por la aplicación . Este cambio dará como resultado más datos que residen en el montón principal de Java, y menos datos en la generación permanente, y por lo tanto puede requerir que se ajusten los tamaños de montón. La mayoría de las aplicaciones solo verán diferencias relativamente pequeñas en el uso del montón debido a este cambio, pero las aplicaciones más grandes que cargan muchas clases o hacen un uso intensivo del método String.intern () verán diferencias más significativas.

xli
fuente
Debo aclarar eso: en mi software, un volcado del montón mostró que las Stringinstancias usaban la mayor parte del espacio del montón . Al mirar su contenido, vi muchos duplicados y decidí cambiar a intern(), lo que ahorró cientos de MB.
Matthieu
4

¿Hay efectos secundarios no mencionados en el Javadoc, es decir, más o menos optimización por parte del compilador JIT?

No sé sobre el nivel JIT, pero existe un soporte directo de código de bytes para el conjunto de cadenas , que se implementa de manera mágica y eficiente con una CONSTANT_String_infoestructura dedicada (a diferencia de la mayoría de los otros objetos que tienen representaciones más genéricas).

JVMS

JVMS 7 5.1 dice :

Un literal de cadena es una referencia a una instancia de clase String y se deriva de una estructura CONSTANT_String_info (§4.4.3) en la representación binaria de una clase o interfaz. La estructura CONSTANT_String_info proporciona la secuencia de puntos de código Unicode que constituyen el literal de cadena.

El lenguaje de programación Java requiere que los literales de cadena idénticos (es decir, los literales que contienen la misma secuencia de puntos de código) deben referirse a la misma instancia de la clase Cadena (JLS §3.10.5). Además, si se llama al método String.intern en cualquier cadena, el resultado es una referencia a la misma instancia de clase que se devolvería si esa cadena apareciera como un literal. Por lo tanto, la siguiente expresión debe tener el valor verdadero:

("a" + "b" + "c").intern() == "abc"

Para derivar un literal de cadena, la máquina virtual Java examina la secuencia de puntos de código dada por la estructura CONSTANT_String_info.

  • Si el método String.intern se ha llamado previamente en una instancia de clase String que contiene una secuencia de puntos de código Unicode idénticos a los proporcionados por la estructura CONSTANT_String_info, el resultado de la derivación literal de cadena es una referencia a esa misma instancia de clase String.

  • De lo contrario, se crea una nueva instancia de clase String que contiene la secuencia de puntos de código Unicode dada por la estructura CONSTANT_String_info; una referencia a esa instancia de clase es el resultado de la derivación literal de cadena. Finalmente, se invoca el método interno de la nueva instancia de String.

Bytecode

También es instructivo observar la implementación de bytecode en OpenJDK 7.

Si descompilamos:

public class StringPool {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a);
        System.out.println(b);
        System.out.println(a == c);
    }
}

tenemos en el grupo constante:

#2 = String             #32   // abc
[...]
#32 = Utf8               abc

y main:

 0: ldc           #2          // String abc
 2: astore_1
 3: ldc           #2          // String abc
 5: astore_2
 6: new           #3          // class java/lang/String
 9: dup
10: ldc           #2          // String abc
12: invokespecial #4          // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne     42
38: iconst_1
39: goto          43
42: iconst_0
43: invokevirtual #7          // Method java/io/PrintStream.println:(Z)V

Tenga en cuenta cómo:

  • 0y 3: ldc #2se carga la misma constante (los literales)
  • 12: se crea una nueva instancia de cadena (con un #2argumento)
  • 35: ay cse comparan como objetos normales conif_acmpne

La representación de cadenas constantes es bastante mágica en el código de bytes:

  • tiene una estructura dedicada CONSTANT_String_info , a diferencia de los objetos normales (por ejemplo new String)
  • la estructura apunta a una estructura CONSTANT_Utf8_info que contiene los datos. Esos son los únicos datos necesarios para representar la cadena.

y la cita JVMS anterior parece decir que siempre que el Utf8 apuntado es el mismo, se cargan instancias idénticas ldc.

He realizado pruebas similares para campos y:

  • static final String s = "abc"apunta a la tabla constante a través del atributo ConstantValue
  • los campos no finales no tienen ese atributo, pero aún se pueden inicializar con ldc

Bonificación : compárelo con el grupo de enteros , que no tiene soporte directo de bytecode (es decir, no CONSTANT_String_infoanalógico).

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
2

Examinaría intern y == - comparación en lugar de igual solo en el caso de que la comparación igual sea un cuello de botella en múltiples comparaciones de cadenas. Es muy poco probable que esto ayude con un pequeño número de comparaciones, porque intern () no es gratuito. Después de intercalar cadenas de forma agresiva, encontrará llamadas a intern () cada vez más lentas.

Mikko Maunu
fuente
2

Un tipo de pérdida de memoria puede provenir del uso de subString()cuando el resultado es pequeño en comparación con la cadena de origen y el objeto tiene una larga vida.

La solución normal es usar, new String( s.subString(...))pero cuando tiene una clase que almacena el resultado de un potencial / probable subString(...)y no tiene control sobre la persona que llama, puede considerar almacenar los intern()argumentos de String pasados ​​al constructor. Esto libera el gran búfer potencial.

eremmel
fuente
Interesante, pero quizás esto depende de la implementación.
akostadinov
1
La posible fuga de memoria mencionada anteriormente no ocurre en Java 1.8 y 1.7.06 (y posteriores). Consulte Cambios en la representación interna de cadenas realizada en Java 1.7.0_06 .
eremmel
eso confirma que las microoptimizaciones deben aplicarse solo cuando sea necesario después de un perfil de rendimiento y / o memoria. Gracias.
akostadinov
2

El internamiento de cadenas es útil en el caso de que el equals()método se invoque con frecuencia porque el equals()método realiza una comprobación rápida para ver si los objetos son los mismos al comienzo del método.

if (this == anObject) {
    return true;
}

Esto generalmente ocurre cuando se busca a través de Collectionotro código aunque también puede hacer verificaciones de igualdad de cadena.

Sin embargo, el internamiento implica un costo, realicé un microbenchmark de algún código y descubrí que el proceso de internamiento aumenta el tiempo de ejecución en un factor de 10.

El mejor lugar para realizar la internación es generalmente cuando está leyendo claves almacenadas fuera del código, ya que las cadenas del código se internan automáticamente. Esto normalmente sucedería en las etapas de inicialización de su aplicación para evitar la penalización del primer usuario.

Otro lugar donde se puede hacer es al procesar la entrada del usuario que podría usarse para realizar búsquedas clave. Esto normalmente ocurre en su procesador de solicitudes, tenga en cuenta que las cadenas internados deben pasarse.

Aparte de eso, no tiene mucho sentido hacer prácticas en el resto del código, ya que generalmente no dará ningún beneficio.

Arquímedes Trajano
fuente
1

Yo votaría por que no valga la pena el mantenimiento.

La mayoría de las veces, no habrá necesidad ni beneficio de rendimiento, a menos que su código trabaje mucho con las subcadenas. En cuyo caso, la clase String usará la cadena original más un desplazamiento para ahorrar memoria. Si su código usa muchas subcadenas, sospecho que solo hará que exploten sus requisitos de memoria.

wm_eddie
fuente
1

http://kohlerm.blogspot.co.uk/2009/01/is-javalangstringintern-really-evil.html

afirma que String.equals()utiliza "=="para comparar Stringobjetos antes, de acuerdo con

http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html

compara las longitudes de cadenas y luego el contenido.

(Por cierto, las cadenas de códigos de productos en un catálogo de ventas pueden tener la misma longitud: BIC0417 es un casco de seguridad para ciclistas, TIG0003 es un tigre macho adulto vivo; probablemente necesite todo tipo de licencias para pedir uno de esos. Y tal vez sea mejor que pidas un casco de seguridad al mismo tiempo).

Por lo tanto, parece que obtiene un beneficio al reemplazar sus cadenas por su intern()versión, pero obtiene seguridad, legibilidad y cumplimiento estándar, sin usar "==" equals()en su programación. Y la mayor parte de lo que voy a decir depende de que eso sea cierto, si es cierto.

¿Pero String.equals()prueba que le pasó una cadena y no algún otro objeto antes de usar "=="? No estoy calificado para decirlo, pero supongo que no, porque abrumadoramente la mayoría de estas equals()operaciones serán de cadena a cadena, por lo que la prueba casi siempre se pasa. De hecho, priorizar "==" en el interior String.equals()implica la confianza de que con frecuencia está comparando la Cadena con el mismo objeto real.

Espero que nadie se sorprenda de que las siguientes líneas produzcan un resultado de "falso":

    Integer i = 1;
    System.out.println("1".equals(i));

Pero si cambias ia i.toString()en la segunda línea, por supuesto que sí true.

Los lugares donde podría esperar un beneficio de la pasantía incluyen Sety Map, obviamente. Espero que las cadenas internas tengan sus códigos hash en caché ... Creo que eso sería un requisito. Y espero no haber regalado una idea que podría ganarme un millón de dólares. :-)

En cuanto a la memoria, también es obvio que ese es un límite importante si su volumen de cadenas es grande, o si desea que la memoria utilizada por el código de su programa sea muy pequeña. Si su volumen de cadenas -distinct- es muy grande, entonces puede ser el momento de considerar el uso de un código de programa de base de datos dedicado para administrarlos y un servidor de base de datos separado. Del mismo modo, si puede mejorar un programa pequeño (que necesita ejecutarse en 10000 instancias simultáneamente) haciendo que no almacene sus cadenas en absoluto.

Se siente un desperdicio crear una nueva cadena y luego descartarla de inmediato para su intern()sustituto, pero no hay una alternativa clara, excepto para mantener la cadena duplicada. Entonces, realmente el costo de ejecución es buscar su cadena en el grupo interno y luego permitir que el recolector de basura elimine el original. Y si es un literal de cadena, entonces ya viene internado de todos modos.

Me pregunto si el intern()código de programa malintencionado puede abusar de él para detectar si algunas cadenas y sus referencias a objetos ya existen en el intern()grupo y, por lo tanto, existen en otra parte de la sesión de Java, cuando eso no se debe saber. Pero eso solo sería posible cuando el código del programa ya se esté utilizando de manera confiable, supongo. ¡Aún así, es algo a considerar sobre las bibliotecas de terceros que incluye en su programa para almacenar y recordar sus números PIN de cajero automático!

Robert Carnegie
fuente
0

La verdadera razón para usar pasante no es la anterior. Puede usarlo después de recibir un error de falta de memoria. Gran parte de la cadena en un programa típico es String.substring () de otra cadena grande [piense en extraer un nombre de usuario de un archivo xml de 100K. La implementación de Java es que, la subcadena contiene una referencia a la cadena original y el inicio + final en esa cadena enorme. (El pensamiento detrás de esto es una reutilización de la misma cuerda grande)

Después de 1000 archivos grandes, de los cuales solo guarda 1000 nombres cortos, ¡guardará en la memoria los 1000 archivos completos! Solución: en este escenario solo use smallsubstring.intern ()

asaf
fuente
¿Por qué no simplemente crear una nueva cadena de la subcadena si la necesita?
Thorbjørn Ravn Andersen
0

Estoy usando el interno para ahorrar memoria, tengo una gran cantidad de datos de cadena en la memoria y al mover para usar el interno () guardé una gran cantidad de memoria. Desafortunadamente, aunque usa mucha menos memoria, la memoria que usa está almacenada en la memoria PermGen, no en Heap, y es difícil explicar a los clientes cómo aumentar la asignación de este tipo de memoria.

Entonces, ¿hay una alternativa a intern () para reducir el consumo de memoria (el == versus los beneficios de rendimiento iguales no es un problema para mí)

Paul Taylor
fuente
0

Seamos realistas: el escenario de caso de uso principal es cuando lee una secuencia de datos (ya sea a través de una secuencia de entrada o de un conjunto de resultados JDBC) y hay una gran cantidad de pequeñas cadenas que se repiten en todo momento.

Aquí hay un pequeño truco que le da cierto control sobre qué tipo de mecanismo le gustaría utilizar para internalizar cadenas y otros inmutables, y una implementación de ejemplo:

/**
 * Extends the notion of String.intern() to different mechanisms and
 * different types. For example, an implementation can use an
 * LRUCache<T,?>, or a WeakHashMap.
 */
public interface Internalizer<T> {
    public T get(T obj);
}
public static class LRUInternalizer<T> implements Internalizer<T> {
    private final LRUCache<T, T> cache;
    public LRUInternalizer(int size) {
        cache = new LRUCache<T, T>(size) {
            private static final long serialVersionUID = 1L;
            @Override
            protected T retrieve(T key) {
                return key;
            }
        };
    }
    @Override
    public T get(T obj) {
        return cache.get(obj);
    }
}
public class PermGenInternalizer implements Internalizer<String> {
    @Override
    public String get(String obj) {
        return obj.intern();
    }
}

Lo uso a menudo cuando leo campos de secuencias o de ResultSets. Nota: LRUCachees un caché simple basado en LinkedHashMap<K,V>. Llama automáticamente al retrieve()método proporcionado por el usuario para todos los errores de caché.

La forma de usar esto es crear uno LRUInternalizerantes de su lectura (o lecturas), usarlo para internalizar cadenas y otros pequeños objetos inmutables, luego liberarlo. Por ejemplo:

Internalizer<String> internalizer = new LRUInternalizer(2048);
// ... get some object "input" that stream fields
for (String s : input.nextField()) {
    s = internalizer.get(s);
    // store s...
}
Pierre D
fuente
0

Lo estoy usando para almacenar en caché el contenido de aproximadamente 36000 códigos que enlazan con nombres asociados. Interno las cadenas en el caché porque muchos de los códigos apuntan a la misma cadena.

Al internar las cadenas en mi caché, me aseguro de que los códigos que apuntan a la misma cadena realmente apunten a la misma memoria, lo que me ahorra espacio en la RAM.

Si las cadenas internas fueran realmente basura recolectada, no funcionaría para mí en absoluto. Esto básicamente negaría el propósito de la internación. El mío no será basura recolectada porque tengo una referencia a cada cadena en el caché.

Rodney P. Barbati
fuente
No, todas las cadenas iguales internados que están en la memoria en un momento determinado seguirán siendo el mismo objeto. Será un objeto diferente a la cadena igual que estaba en la memoria antes de que se recolectara la basura. Pero esto no es problema porque la cadena anterior ya no está allí.
bdruemen el
0

El costo de internar una cadena es mucho más que el tiempo ahorrado en una sola cadenaA.equals (B) comparación. Solo úselo (por razones de rendimiento) cuando esté usando repetidamente las mismas variables de cadena sin cambios. Por ejemplo, si regularmente itera sobre una lista estable de cadenas para actualizar algunos mapas marcados en el mismo campo de cadena, puede obtener un buen ahorro.

Sugeriría usar internados de cadenas para ajustar el rendimiento cuando esté optimizando partes específicas de su código.

Recuerde también que las cadenas son inmutables y no cometen el error tonto de

String a = SOME_RANDOM_VALUE
a.intern()

recuerda hacer

String a = SOME_RANDOM_VALUE.intern()
abejorro
fuente
0

Si está buscando un reemplazo ilimitado para String.intern, también basura recolectada, lo siguiente me está funcionando bien.

private static WeakHashMap<String, WeakReference<String>> internStrings = new WeakHashMap<>();
public static String internalize(String k) {
    synchronized (internStrings) {
        WeakReference<String> weakReference = internStrings.get(k);
        String v = weakReference != null ? weakReference.get() : null;
        if (v == null) {
            v = k;
            internStrings.put(v, new WeakReference<String>(v));
        }
        return v;
    }
}

Por supuesto, si puede estimar aproximadamente cuántas cadenas diferentes habrá, simplemente use String.intern () con -XX: StringTableSize = highEnoughValue .

bdruemen
fuente
SoftRef haría más sentido.
vach
@vach Al usar WeakReference (en lugar de SoftReference), la memoria se libera antes, por lo que otras asignaciones pueden ir más rápido. Depende de qué más esté haciendo la aplicación, cualquiera de los dos podría tener sentido.
bdruemen