Java中,HashMap使用字符串作为键,那么字符串值是否会被存储两次?

4
如果我有一个看起来像这样的HashMap:
HashMap 其中String键是MyObject中的一个字段,那么这个字符串值会被存储两次吗?
所以当我添加条目时:
_myMap.put(myObj.getName(),myObj);
我是否在内存方面使用了双倍的字符串大小?还是Java在幕后做了一些聪明的事情?
谢谢

请注意,您标记为“已接受”的答案的投票数不到另一个答案的一半。当出现这种情况时,最好再仔细检查一下,确保您真正接受了正确的答案。 - Tyler
3个回答

16

除非你在getName()中实际创建了一个新的字符串值,否则你不会重复使用内存。

以下是一些例子,以澄清这个问题:

 String s1 = "Some really long string!";
 String s2 = s1;
 assert s1.equals(s2);

这里 s1 == s2; 它们引用同一String实例。你的内存使用情况是 2 个引用变量(没什么大不了的),1 个String实例和1 个支持的char[](占用内存的部分)。


 String s1 = "Some really long string!";
 String s2 = new String(s1);
 assert s1.equals(s2);

在这里,s1 != s2;它们指向不同的String实例。然而,由于字符串是不可变的,构造函数知道它们可以共享相同的字符数组。因此,您的内存使用情况是2个引用变量、2个String实例(仍然不是什么大问题,因为……)和1个支持char[]


 String s1 = "Some really long string!";
 String s2 = new String(s1.toCharArray());
 assert s1.equals(s2);

就像以前一样,s1 != s2。 不同的构造函数被使用,但这次是使用了一个char[]。为了确保不可变性,toCharArray() 必须返回其内部数组的防御性副本(这样返回的数组的任何更改都不会改变 String 值)。

[toCharArray() 返回] 一个新分配的字符数组,其长度为此字符串的长度,并且其内容初始化为包含该字符串所表示的字符序列。

更糟糕的是,构造函数还必须防御性地将给定的数组复制到其内部后备数组中,以再次确保不可变性。这意味着可能会有多达 3 个字符数组同时存在于内存中!其中一个最终将被垃圾回收,因此您的内存使用情况是 2 个引用变量、2 个 String 实例和2 个后备 char[]!现在你的内存使用量加倍了!


所以回到你的问题,只要你没有在 getName() 中创建新的 String 值(例如,如果你只是简单地 return this.name;),那么就没有问题。但是,即使是简单的串联(例如return this.firstName + this.lastName;),你的内存使用量也会加倍!

以下代码说明了我的观点:

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

首先,您应该验证上述代码能够正常运行(java -Xmx128m StringTest)而不会抛出任何异常。然后,修改getName()方法为return this.name + "";并再次运行。这次您将会得到一个OutOfMemoryError异常。


你能为这些斜体部分添加引用吗? - JRL
@JRL:我添加了对toCharArray()防御性复制的引用。 - polygenelubricants
不确定是给一个真正全面的答案点赞还是给一个太长没看的负分。最终选择了点赞 :) - clstrfsck
好的,我确实在第一行中提出了我的回答的核心主题 =) - polygenelubricants

4

Java使用引用,因此它只是一个指向存储两次的字符串的指针。 因此,如果您的字符串很大,您不必担心它会占用更多的内存。


1
实际上,这取决于 getName() 的实现方式。请参考我的回答。 - polygenelubricants

1

字符串是不可变的,但是传递引用仍然适用。因此它不会占用两倍的内存。


2
Java 中没有传递引用的方式。引用和其他所有东西一样,都是按值传递的。 - user207421

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接