Java、HashMap 和使用字符串作为键 - 字符串值是否会存储两次?

发布于 2024-08-24 19:31:31 字数 304 浏览 6 评论 0原文

如果我有一个如下所示的 HashMap:

HashMap

其中 String 键是 MyObject 中的字段,则此字符串是否值被存储两次?

因此,当我添加条目时:

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

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

谢谢

If I have a HashMap that looks like this:

HashMap<String, MyObject>

where the String key is a field in MyObject, does this string value get stored twice?

So when I add entries:

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

Am I using double the String size in terms of memory? Or does Java do something clever behind the scenes?

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

池予 2024-08-31 19:31:31

除非您实际上在 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 个字符数组的副本!其中 1 个最终将被垃圾收集,因此您的内存使用量是 2 个引用变量、2 个 String 实例和 2 个支持 char[] ! 现在您的内存使用量增加了一倍!


因此,回到您的问题,只要您没有在 getName() 中创建新的字符串值(即,如果您只是简单地返回 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

Unless you're actually creating a new String value in getName(), you're not duplicating your memory usage.

Here are a few examples to clarify things:

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

Here, s1 == s2; they refer to the same String instance. Your memory usage is 2 reference variables (no big deal), 1 String instance, and 1 backing char[] (the part that takes up memory).


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

Here, s1 != s2; they refer to different String instances. However, since strings are immutable, the constructor knows that they can share the same character array. Your memory usage is 2 reference variables, 2 String instances (still no big deal, because...), and 1 backing char[].


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

Here, just like before, s1 != s2. A different constructor is used, this time, however, that takes a char[] instead. To ensure immutability, toCharArray() must return a defensive copy of its internal array (that way any changes to the returned array would not mutate the String value).

[toCharArray() returns] a newly allocated character array whose length is the length of this string and whose contents are initialized to contain the character sequence represented by this string.

To make matters worse, the constructor must also defensively copy the given array to its internal backing array, again to ensure immutability. This means that as many as 3 copies of the character array may live in the memory at the same time! 1 of those will be garbage-collected eventually, so your memory usage is 2 reference variables, 2 String instances, and 2 backing char[]! NOW your memory usage is doubled!


So going back to your question, as long as you're not creating a new String value in getName() (i.e. if you just simply return this.name;), then you're fine. If you are doing even a simple concatenation, however (e.g. return this.firstName + this.lastName;), then you will double your memory usage!

The following code illustrates my point:

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

You should first verify that the above code runs (java -Xmx128m StringTest) without throwing any exception. Then, modify getName() to return this.name + ""; and run it again. This time you will get an OutOfMemoryError.

阪姬 2024-08-31 19:31:31

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

Java uses the reference, so it is just a pointer to the string that it stores twice. So you don't have to worry if your string is huge, it will still be the same amount of memory that is used.

小忆控 2024-08-31 19:31:31

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

String are immutable, but pass-by-reference still apply. So it won't take twice as much memory.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文