O que é a Java String interning?


234

O que é String Interning em Java, quando devo usá-lo e por quê ?



2
se String a = new String("abc"); String b = new String("abc"); entãoa.intern() == b.intern()
Asanka Siriwardena 01/04


Será que String.intern()dependem ClassLoader, ou seja, fazer diferente carregador de classe de criação de "diferentes" Strings, causando diferentes interns?
AlikElzin-Kilaka

1
@ AlikElzin-kilaka não, os carregadores de classes são totalmente irrelevantes para a internação de strings. Na próxima vez que você tiver uma pergunta, abra uma nova pergunta em vez de publicá-la como um comentário em uma pergunta diferente.
Holger

Respostas:


233

http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#intern ()

Basicamente, fazer String.intern () em uma série de strings garantirá que todas as strings com o mesmo conteúdo compartilhem a mesma memória. Portanto, se você tiver uma lista de nomes onde 'john' aparece 1000 vezes, internando você garante que apenas um 'john' seja realmente alocado de memória.

Isso pode ser útil para reduzir os requisitos de memória do seu programa. Mas lembre-se de que o cache é mantido pela JVM no conjunto de memórias permanentes, que geralmente é limitado em tamanho comparado ao heap, portanto, você não deve usar intern se não tiver muitos valores duplicados.


Mais sobre restrições de memória do uso de intern ()

Por um lado, é verdade que você pode remover duplicatas de String internalizando-as. O problema é que as seqüências internalizadas vão para a Geração Permanente, que é uma área da JVM reservada para objetos não-usuário, como Classes, Métodos e outros objetos internos da JVM. O tamanho dessa área é limitado e geralmente é muito menor que o monte. Chamar intern () em uma String tem o efeito de movê-lo para fora da pilha para a geração permanente, e você corre o risco de ficar sem espaço no PermGen.

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


Do JDK 7 (quero dizer no HotSpot), algo mudou.

No JDK 7, cadeias internas não são mais alocadas na geração permanente do heap Java, mas são alocadas na parte principal do heap Java (conhecida como gerações novas e antigas), juntamente com os outros objetos criados pelo aplicativo . Essa alteração resultará em mais dados residindo no heap Java principal e menos dados na geração permanente e, portanto, pode exigir que os tamanhos de heap sejam ajustados. A maioria dos aplicativos verá apenas diferenças relativamente pequenas no uso de heap devido a essa alteração, mas aplicativos maiores que carregam muitas classes ou fazem uso pesado do método String.intern () verão diferenças mais significativas.

- Do Java SE 7 Recursos e Aprimoramentos

Atualização: cadeias internas são armazenadas no heap principal a partir do Java 7 em diante. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes


1
"Mas lembre-se de que o cache é mantido pela JVM no pool de memória permanente, que geralmente possui tamanho limitado ......" Você pode explicar isso? Eu não entendi
saplingPro

2
as cadeias "internadas" são armazenadas em uma região de memória especial na JVM. Essa região de memória geralmente possui um tamanho fixo e não faz parte do Java Heap comum em que outros dados são armazenados. Devido ao tamanho fixo, pode acontecer que essa região de memória permanente seja preenchida com todas as suas strings, causando problemas feios (as classes não podem ser carregadas e outras coisas).
violoncelo

@ violoncelo então, é semelhante ao cache?
21412 # 07

8
@grassPro: Sim, é um tipo de cache, fornecido originalmente pela JVM. Como observação, devido à mesclagem da Sun / Oracle JVM e JRockit, os engenheiros da JVM tentam se livrar da região de memória permanente no JDK 8 ( openjdk.java.net/jeps/122 ), portanto não haverá qualquer limitação de tamanho no futuro.
violoncelo

9
Os programadores também devem estar cientes de que o internamento de strings pode ter implicações de segurança. Se você tiver texto confidencial, como senhas, como seqüências de caracteres na memória, ele poderá permanecer na memória por um período muito longo, mesmo que os objetos de sequência reais tenham sido submetidos à GC por muito tempo. Isso pode ser problemático se os bandidos tiverem acesso a um despejo de memória. Esse problema existe mesmo sem internação (já que o GC não é determinístico para começar, etc.), mas o torna um pouco pior. É sempre uma boa ideia usar em char[]vez de Stringtexto confidencial e zerá-lo assim que não for mais necessário.
chris

71

Existem algumas perguntas de "entrevista cativante", como por que você é igual! se você executar o trecho de código abaixo.

String s1 = "testString";
String s2 = "testString";
if(s1 == s2) System.out.println("equals!");

Se você deseja comparar Strings, você deve usar equals(). O texto acima será impresso igual porque o testStringjá está internado para você pelo compilador. Você pode internar as seqüências de caracteres usando o método intern, como é mostrado nas respostas anteriores ....


5
Seu exemplo é complicado, pois resultará na mesma impressão, mesmo se você usar o equalsmétodo Você pode adicionar uma new String()comparação para mostrar a distinção mais claramente.
giannis christofakis 30/03/19

@giannischristofakis mas se usarmos o novo String (), o == falhará? O java também internaliza automaticamente novas strings?
Deepak Selvakumar 16/04

@giannischristofakis, é claro, se você usar o novo String (), ele falhará em ==. mas a nova String (...). intern () não falhará em == porque intern retornará a mesma string. Simples assumir compilador está fazendo novo String ().
Intern

42

JLS

O JLS 7 3.10.5 o define e fornece um exemplo prático:

Além disso, um literal de string sempre se refere à mesma instância da classe String. Isso ocorre porque literais de string - ou, de maneira mais geral, strings que são os valores de expressões constantes (§15.28) - são "internados" para compartilhar instâncias exclusivas, usando o método String.intern.

Exemplo 3.10.5-1. Literais de cordas

O programa que consiste na unidade de compilação (§7.3):

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

e a unidade de compilação:

package other;
public class Other { public static String hello = "Hello"; }

produz a saída:

true true true true false true

JVMS

O JVMS 7 5.1 diz que a internação é implementada de maneira mágica e eficiente com uma CONSTANT_String_infoestrutura dedicada (ao contrário da maioria dos outros objetos que possuem representações mais genéricas):

Um literal de string é uma referência a uma instância da classe String e é derivado de uma estrutura CONSTANT_String_info (§4.4.3) na representação binária de uma classe ou interface. A estrutura CONSTANT_String_info fornece a sequência de pontos de código Unicode que constituem a cadeia literal.

A linguagem de programação Java requer que literais de string idênticos (ou seja, literais que contenham a mesma sequência de pontos de código) se refiram à mesma instância da classe String (JLS §3.10.5). Além disso, se o método String.intern for chamado em qualquer string, o resultado será uma referência à mesma instância de classe que seria retornada se essa string aparecesse como um literal. Portanto, a seguinte expressão deve ter o valor true:

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

Para derivar um literal de cadeia, a Java Virtual Machine examina a sequência de pontos de código fornecidos pela estrutura CONSTANT_String_info.

  • Se o método String.intern já foi chamado em uma instância da classe String que contém uma sequência de pontos de código Unicode idênticos àqueles fornecidos pela estrutura CONSTANT_String_info, o resultado da derivação literal da string é uma referência à mesma instância da classe String.

  • Caso contrário, uma nova instância da classe String será criada contendo a sequência de pontos de código Unicode fornecidos pela estrutura CONSTANT_String_info; uma referência a essa instância de classe é o resultado da derivação literal de cadeia de caracteres. Por fim, o método interno da nova instância String é chamado.

Bytecode

Vamos descompilar alguns bytecode do OpenJDK 7 para ver a internação em ação.

Se decompilarmos:

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);
    }
}

temos na piscina constante:

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

e 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

Observe como:

  • 0e 3: a mesma ldc #2constante é carregada (os literais)
  • 12: uma nova instância de string é criada (com #2como argumento)
  • 35: ae csão comparados como objetos regulares comif_acmpne

A representação de strings constantes é bastante mágica no bytecode:

  • possui uma estrutura CONSTANT_String_info dedicada , diferentemente dos objetos regulares (por exemplo new String)
  • o struct aponta para uma estrutura CONSTANT_Utf8_info que contém os dados. Esses são os únicos dados necessários para representar a sequência.

e a citação da JVMS acima parece dizer que sempre que o Utf8 apontado é o mesmo, instâncias idênticas são carregadas ldc.

Eu fiz testes semelhantes para campos e:

  • static final String s = "abc"aponta para a tabela constante por meio do atributo ConstantValue
  • campos não finais não possuem esse atributo, mas ainda podem ser inicializados com ldc

Conclusão : existe suporte direto de bytecode para o conjunto de strings e a representação de memória é eficiente.

Bônus: compare isso ao pool Inteiro , que não possui suporte direto ao bytecode (ou seja, sem CONSTANT_String_infoanalógico).


19

Atualização para Java 8 ou mais . No Java 8, o espaço PermGen (Geração Permanente) é removido e substituído pelo Meta Space. A memória do conjunto de cadeias é movida para o heap da JVM.

Comparado com o Java 7, o tamanho do pool de String é aumentado no heap. Portanto, você tem mais espaço para seqüências de caracteres internalizadas, mas possui menos memória para todo o aplicativo.

Mais uma coisa, você já sabia que, ao comparar 2 (referências de) objetos em Java, ' ==' é usado para comparar a referência de objeto ' equals', é usado para comparar o conteúdo do objeto.

Vamos verificar este código:

String value1 = "70";
String value2 = "70";
String value3 = new Integer(70).toString();

Resultado:

value1 == value2 ---> true

value1 == value3 ---> false

value1.equals(value3) ---> true

value1 == value3.intern() ---> true

É por isso que você deve usar ' equals' para comparar objetos 2 String. E é assim que intern()é útil.


2

A internação de strings é uma técnica de otimização do compilador. Se você tiver dois literais de seqüência de caracteres idênticos em uma unidade de compilação, o código gerado garantirá que haja apenas um objeto de seqüência de caracteres criado para toda a instância desse literal (caracteres entre aspas duplas) dentro do assembly.

Eu sou de fundo C #, então eu posso explicar, dando um exemplo disso:

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;

saída das seguintes comparações:

Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true    
Console.WriteLine(obj == str2); // false !?

Nota1 : Os objetos são comparados por referência.

Nota2 : typeof (int) .Name é avaliado pelo método de reflexão para que não seja avaliado em tempo de compilação. Aqui essas comparações são feitas em tempo de compilação.

Análise dos resultados: 1) verdadeiro porque ambos contêm o mesmo literal e, portanto, o código gerado terá apenas um objeto que faz referência a "Int32". Ver nota 1 .

2) true porque o conteúdo de ambos os valores é verificado, o mesmo.

3) FALSE porque str2 e obj não possuem o mesmo literal. Ver nota 2 .


3
É mais forte que isso. Qualquer literal de String carregado pelo mesmo carregador de classes se referirá à mesma String. Veja a especificação JLS e JVM.
Marquês de Lorne

1
@ user207421 de fato, é ainda irrelevante a qual carregador de classes a literal da string pertence.
Holger

1
Java interning() method basically makes sure that if String object is present in SCP, If yes then it returns that object and if not then creates that objects in SCP and return its references

for eg: String s1=new String("abc");
        String s2="abc";
        String s3="abc";

s1==s2// false, because 1 object of s1 is stored in heap and other in scp(but this objects doesn't have explicit reference) and s2 in scp
s2==s3// true

now if we do intern on s1
s1=s1.intern() 

//JVM checks if there is any string in the pool with value “abc” is present? Since there is a string object in the pool with value “abc”, its reference is returned.
Notice that we are calling s1 = s1.intern(), so the s1 is now referring to the string pool object having value abc”.
At this point, all the three string objects are referring to the same object in the string pool. Hence s1==s2 is returning true now.

0

No livro Deshmukh do OCP Java SE 11 Programmer, encontrei a explicação mais fácil para Interning, que foi a seguinte: Como as strings são objetos e como todos os objetos em Java sempre são armazenados apenas no espaço de heap, todas as strings são armazenadas no espaço de heap. No entanto, o Java mantém as strings criadas sem usar a nova palavra-chave em uma área especial do espaço de heap, chamada "pool de strings". Java mantém as seqüências criadas usando a nova palavra-chave no espaço de heap regular.

O objetivo do conjunto de strings é manter um conjunto de strings exclusivos. Sempre que você cria uma nova sequência sem usar a nova palavra-chave, o Java verifica se a mesma sequência já existe no conjunto de sequências. Se isso acontecer, Java retornará uma referência ao mesmo objeto String e, se não, Java criará um novo objeto String no conjunto de strings e retornará sua referência. Portanto, por exemplo, se você usar a sequência "olá" duas vezes no seu código, como mostrado abaixo, obterá uma referência para a mesma sequência. Podemos realmente testar essa teoria comparando duas variáveis ​​de referência diferentes usando o operador == , como mostrado no código a seguir:

String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2); //prints true

String str3 = new String("hello");
String str4 = new String("hello");

System.out.println(str1 == str3); //prints false
System.out.println(str3 == str4); //prints false 

== O operador simplesmente verifica se duas referências apontam para o mesmo objeto ou não e retorna true se o fizerem. No código acima, str2 obtém a referência ao mesmo objeto String que foi criado anteriormente. No entanto, str3 e str4 obtêm referências a dois objetos String totalmente diferentes. É por isso que str1 == str2 retorna true, mas str1 == str3 e str3 == str4 retornam false. De fato, quando você faz uma nova String ("olá"); dois objetos String são criados em vez de apenas um, se for a primeira vez que a string "hello" é usada em qualquer lugar do programa - um no pool de strings por causa do uso de uma string entre aspas e um no espaço de heap normal porque do uso de nova palavra-chave.

O pool de strings é a maneira do Java de economizar memória de programa, evitando a criação de vários objetos String que contêm o mesmo valor. É possível obter uma string do pool de strings criada com a nova palavra-chave usando o método interno de String. É chamado "interning" de objetos de string. Por exemplo,

String str1 = "hello";
String str2 = new String("hello");
String str3 = str2.intern(); //get an interned string obj

System.out.println(str1 == str2); //prints false
System.out.println(str1 == str3); //prints true
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.