Java、HashMaps 和使用字符串作为键 - 字符串值是不是被存储两次?

Posted

技术标签:

【中文标题】Java、HashMaps 和使用字符串作为键 - 字符串值是不是被存储两次?【英文标题】:Java, HashMaps and using Strings as the keys - does the string value get stored twice?Java、HashMaps 和使用字符串作为键 - 字符串值是否被存储两次? 【发布时间】:2011-01-27 03:07:32 【问题描述】:

如果我有一个看起来像这样的 HashMap:

HashMap<String, MyObject>

其中String 键是MyObject 中的一个字段,这个字符串值会被存储两次吗?

所以当我添加条目时:

_myMap.put(myObj.getName(), myObj);

就内存而言,我是否使用双倍的字符串大小?还是 Java 在幕后做了一些聪明的事情?

谢谢

【问题讨论】:

请注意,您标记为“已接受”的答案的投票数不到另一个答案的一半。发生这种情况时,最好仔细检查并确保您确实接受了正确的选择。 【参考方案1】:

除非您实际上是在 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() 必须返回其内部数组的防御性副本(这样对返回数组的任何更改都不会改变字符串值)。

[toCharArray()返回]一个新分配的字符数组,它的长度就是这个字符串的长度,其内容被初始化为包含这个字符串所代表的字符序列。

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


所以回到你的问题,只要你没有在getName() 中创建一个新的字符串值(即,如果你只是简单地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:我添加了对toCharArray()防御性复制的引用。 不确定是 +1 以获得真正全面的答案,还是 -1 获得 TL;DR。选择了 +1 选项 :) 嗯,我确实把我答案的中心主题放在了第一行 =)【参考方案2】:

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

【讨论】:

Java 中没有引用传递。 引用与其他所有内容一起按值传递。【参考方案3】:

Java 使用引用,因此它只是一个指向它存储两次的字符串的指针。所以你不必担心你的字符串是否很大,它仍然会使用相同数量的内存。

【讨论】:

其实要看getName()是怎么实现的。看我的回答。

以上是关于Java、HashMaps 和使用字符串作为键 - 字符串值是不是被存储两次?的主要内容,如果未能解决你的问题,请参考以下文章

java 使用Hashmaps检查Anagrams

使用ArrayLists和HashMaps在Java中进行紧凑拆分和新行操作

如何在Java中访问嵌套的HashMaps?

修改HashMaps的HashMap中的元素

SparseArray 与 HashMap

在firebase(JAVA)中插入一个字符串变量作为键(子)