Por que acrescentar “” a uma String economiza memória?

193

Eu usei uma variável com muitos dados, digamos String data. Eu queria usar uma pequena parte dessa string da seguinte maneira:

this.smallpart = data.substring(12,18);

Após algumas horas de depuração (com um visualizador de memória), descobri que o campo de objetos se smallpartlembrava de todos os dados data, embora contivesse apenas a substring.

Quando mudei o código para:

this.smallpart = data.substring(12,18)+""; 

..o problema foi resolvido! Agora meu aplicativo usa muito pouca memória agora!

Como isso é possível? Alguém pode explicar isso? Eu acho que essa parte pequena continuou fazendo referência a dados, mas por quê?

ATUALIZAÇÃO: Como posso limpar a grande String então? Data = new String (data.substring (0,100)) fará a coisa?

hsmit
fonte
Leia mais sobre sua intenção final abaixo: De onde vem a corda grande em primeiro lugar? Se ler de um CLOB de arquivo ou banco de dados ou algo assim, apenas ler o que você precisa durante a análise será ideal o tempo todo.
precisa saber é o seguinte
4
Incrível ... Estou trabalhando em java há mais de 4 a 5 anos, ainda assim isso é novo para mim :). obrigado pela informação mano.
Parth
1
Há uma sutileza no uso new String(String); consulte stackoverflow.com/a/390854/8946 .
Lawrence Dol

Respostas:

159

Fazendo o seguinte:

data.substring(x, y) + ""

cria um novo objeto String (menor) e joga fora a referência à String criada por substring (), permitindo assim a coleta de lixo.

O importante é perceber que isso substring()fornece uma janela para uma String existente - ou melhor, a matriz de caracteres subjacente à String original. Portanto, ele consumirá a mesma memória que a String original. Isso pode ser vantajoso em algumas circunstâncias, mas problemático se você deseja obter uma subcadeia e descartar a String original (como você descobriu).

Dê uma olhada no método substring () na fonte JDK String para obter mais informações.

EDIT: Para responder a sua pergunta complementar, a construção de uma nova String a partir da substring reduzirá o consumo de memória, desde que você faça uma referência à String original.

NOTA (janeiro de 2013). O comportamento acima foi alterado no Java 7u6 . O padrão flyweight não é mais usado e substring()funcionará como você esperaria.

Brian Agnew
fonte
89
Esse é um dos poucos casos em que o String(String)construtor (ou seja, o construtor String que recebe uma String como entrada) é útil: new String(data.substring(x, y))efetivamente faz a mesma coisa que anexar "", mas torna a intenção um pouco mais clara.
Joachim Sauer
3
apenas para precisão, a substring usa o valueatributo da string original. Eu acho que é por isso que a referência é mantida.
Valentin Rocher
@Bishiboosh - sim, está certo. Não queria expor as particularidades da implementação, mas é exatamente isso que está acontecendo.
precisa
5
Tecnicamente, é um detalhe de implementação. Mas é frustrante, no entanto, e alcança muitas pessoas.
Brian Agnew
1
Gostaria de saber se é possível otimizar isso no JDK usando referências fracas ou algo assim. Se eu sou a última pessoa que precisa desse caractere [], e só preciso dele um pouco, crie uma nova matriz para usar internamente.
WW.
28

Se você olhar a fonte de substring(int, int), verá que ela retorna:

new String(offset + beginIndex, endIndex - beginIndex, value);

onde valueé o original char[]. Então você obtém uma nova String, mas com o mesmo subjacentechar[] .

Ao fazê-lo data.substring() + "", você obtém uma nova String com uma nova subjacentechar[] .

Na verdade, seu caso de uso é a única situação em que você deve usar o String(String)construtor:

String tiny = new String(huge.substring(12,18));
Pascal Thivent
fonte
1
Há uma sutileza no uso new String(String); consulte stackoverflow.com/a/390854/8946 .
Lawrence Dol
17

Quando você usa substring, na verdade, ele não cria uma nova string. Ainda se refere à sua sequência original, com uma restrição de tamanho e deslocamento.

Portanto, para permitir que sua string original seja coletada, é necessário criar uma nova string (usando new Stringou o que você possui).

Chris Jester-Young
fonte
5

Eu acho que essa parte pequena continuou fazendo referência a dados, mas por quê?

Como as seqüências Java consistem em uma matriz de caracteres, um deslocamento inicial e um comprimento (e um hashCode em cache). Algumas operações String, como substring()criar um novo objeto String, que compartilhe a matriz de caracteres do original e simplesmente possua diferentes campos de deslocamento e / ou comprimento. Isso funciona porque a matriz de caracteres de uma String nunca é modificada depois de criada.

Isso pode economizar memória quando muitas substrings se referem à mesma sequência básica sem replicar partes sobrepostas. Como você notou, em algumas situações, ele pode impedir que os dados que não são mais necessários sejam coletados como lixo.

A maneira "correta" de corrigir isso é o new String(String)construtor, ou seja,

this.smallpart = new String(data.substring(12,18));

BTW, a melhor solução geral seria evitar ter seqüências muito grandes em primeiro lugar e processar qualquer entrada em partes menores, com alguns KB de cada vez.

Michael Borgwardt
fonte
Há uma sutileza no uso new String(String); consulte stackoverflow.com/a/390854/8946 .
Lawrence Dol
5

Em Java, seqüências de caracteres são objetos imutáveis ​​e, uma vez criada, ela permanece na memória até ser limpa pelo coletor de lixo (e essa limpeza não é algo que você pode dar como garantido).

Quando você chama o método de substring, o Java não cria uma nova sequência de fato, mas apenas armazena um intervalo de caracteres dentro da sequência original.

Então, quando você criou uma nova string com este código:

this.smallpart = data.substring(12, 18) + ""; 

você realmente criou uma nova sequência quando concatenou o resultado com a sequência vazia. É por isso.

Kico Lobo
fonte
3

Conforme documentado por jwz em 1997 :

Se você tiver uma cadeia enorme, retire uma subcadeia (), segure a subcadeia e permita que a cadeia mais longa se torne lixo (em outras palavras, a cadeia de caracteres tem uma vida útil mais longa), os bytes subjacentes da cadeia enorme nunca desaparecem longe.

Ken
fonte
2

Apenas para resumir, se você criar muitas substrings a partir de um pequeno número de grandes strings, use

   String subtring = string.substring(5,23)

Como você usa apenas o espaço para armazenar as cordas grandes, mas se estiver extraindo apenas um punhado de cordas pequenas, a partir de perdas de cordas grandes,

   String substring = new String(string.substring(5,23));

Manterá o uso de memória baixo, pois as grandes cadeias de caracteres podem ser recuperadas quando não forem mais necessárias.

Essa chamada new Stringé um lembrete útil de que você realmente está recebendo uma nova sequência, em vez de uma referência à sequência original.

mdma
fonte
Há uma sutileza no uso new String(String); consulte stackoverflow.com/a/390854/8946 .
Lawrence Dol
2

Em primeiro lugar, a chamada java.lang.String.substringcria uma nova janela no originalString com o uso do deslocamento e do comprimento, em vez de copiar a parte significativa da matriz subjacente.

Se dermos uma olhada mais de perto no substringmétodo, notamos uma chamada de construtor de stringString(int, int, char[]) e a transmitimos por inteiro char[]que representa a string . Isso significa que a substring ocupará tanta quantidade de memória quanto a string original .

Ok, mas por que + ""resulta em demanda por menos memória do que sem ela?

Fazer um +on stringsé implementado via StringBuilder.appendchamada de método. Olhar para a implementação deste método em AbstractStringBuilderclasse nos dirá que finalmente faz arraycopyparte da parte que realmente precisamos (o substring).

Alguma outra solução alternativa ??

this.smallpart = new String(data.substring(12,18));
this.smallpart = data.substring(12,18).intern();
laika
fonte
0

Às vezes, anexar "" a uma string economiza memória.

Digamos que eu tenha uma enorme sequência contendo um livro inteiro, um milhão de caracteres.

Então eu crio 20 strings contendo os capítulos do livro como substrings.

Então eu crio 1000 strings contendo todos os parágrafos.

Então eu crio 10.000 strings contendo todas as frases.

Então eu crio 100.000 strings contendo todas as palavras.

Eu ainda uso apenas 1.000.000 caracteres. Se você adicionar "" a cada capítulo, parágrafo, sentença e palavra, use 5.000.000 de caracteres.

É claro que é totalmente diferente se você extrair apenas uma palavra do livro inteiro e o livro inteiro puder ser coletado como lixo, mas não é porque essa palavra tenha uma referência a ele.

E é novamente diferente se você tiver uma sequência de um milhão de caracteres e remover tabulações e espaços nas duas extremidades, fazendo 10 chamadas para criar uma substring. A maneira como o Java funciona ou trabalha evita copiar um milhão de caracteres de cada vez. Há compromisso, e é bom se você souber quais são os compromissos.

gnasher729
fonte