Java, HashMaps e usando Strings como chaves - o valor da string é armazenado duas vezes?
-
19-09-2019 - |
Pergunta
Se eu tiver um HashMap parecido com este:
HashMap<String, MyObject>
onde o String
key é um campo em MyObject
, esse valor de string é armazenado duas vezes?
Então, quando adiciono entradas:
_myMap.put(myObj.getName(), myObj);
Estou usando o dobro do tamanho da String em termos de memória?Ou o Java faz algo inteligente nos bastidores?
Obrigado
Solução
O Java usa a referência, por isso é apenas um ponteiro para a string que armazena duas vezes. Portanto, você não precisa se preocupar se sua corda for enorme, ela ainda será a mesma quantidade de memória usada.
Outras dicas
A menos que você esteja realmente criando um novo valor String em getName()
, você não está duplicando o uso de memória.
Aqui estão alguns exemplos para esclarecer as coisas:
String s1 = "Some really long string!";
String s2 = s1;
assert s1.equals(s2);
Aqui, s1 == s2
;eles se referem ao mesmo String
instância.Seu uso de memória é de 2 variáveis de referência (não é grande coisa), 1 String
exemplo, e 1 apoio char[]
(a parte que ocupa memória).
String s1 = "Some really long string!";
String s2 = new String(s1);
assert s1.equals(s2);
Aqui, s1 != s2
;eles se referem a diferentes String
instâncias.Entretanto, como as strings são imutáveis, o construtor sabe que elas podem compartilhar o mesmo array de caracteres.Seu uso de memória é de 2 variáveis de referência, 2 String
instâncias (ainda não é grande coisa, porque...), e 1 apoio char[]
.
String s1 = "Some really long string!";
String s2 = new String(s1.toCharArray());
assert s1.equals(s2);
Aqui, como antes, s1 != s2
.Um construtor diferente é usado, desta vez, porém, que leva um tempo char[]
em vez de.Para garantir a imutabilidade, toCharArray()
deve devolver uma cópia defensiva de sua matriz interna (dessa forma, quaisquer alterações na matriz retornada não alterariam o valor da String).
[toCharArray() retorna] uma matriz de caracteres recém-alocada cujo comprimento é o comprimento desta string e cujo conteúdo é inicializado para conter a sequência de caracteres representada por esta string.
Para piorar a situação, o construtor também deve copiar defensivamente o array fornecido ao seu array de apoio interno, novamente para garantir a imutabilidade.Isso significa que até 3 cópias da matriz de caracteres pode residir na memória ao mesmo tempo!1 delas será coletada como lixo eventualmente, então seu uso de memória é de 2 variáveis de referência, 2 String
instâncias, e 2 apoio char[]
! AGORA seu uso de memória é duplicado!
Então, voltando à sua pergunta, contanto que você não esteja criando um novo valor String em getName()
(ou seja,se você simplesmente return this.name;
), então você está bem.Porém, se você estiver fazendo uma concatenação simples (por exemplo, return this.firstName + this.lastName;
), então você dobrará o uso de memória!
O código a seguir ilustra meu ponto:
public class StringTest {
final String name;
StringTest(String name) {
this.name = name;
}
String getName() {
return this.name; // this one is fine!
// return this.name + ""; // this one causes OutOfMemoryError!
}
public static void main(String args[]) {
int N = 10000000;
String longString = new String(new char[N]);
StringTest test = new StringTest(longString);
String[] arr = new String[N];
for (int i = 0; i < N; i++) {
arr[i] = test.getName();
}
}
}
Você deve primeiro verificar se o código acima é executado (java -Xmx128m StringTest
) sem lançar nenhuma exceção.Então, modifique getName()
para return this.name + "";
e execute-o novamente.Desta vez você receberá um OutOfMemoryError
.
String são imutáveis, mas ainda se aplicam a referência. Portanto, não será preciso duas vezes mais memória.