【问题标题】:How many Strings are getting created with the new operator [duplicate]使用 new 运算符创建了多少个字符串 [重复]
【发布时间】:2021-06-26 04:42:03
【问题描述】:

使用 new 运算符创建了多少个字符串。

假设我正在使用 new 运算符创建一个字符串。

String str = new String("Cat")

它会创建 2 个字符串,一个在堆中,另一个在字符串池中吗?

如果它也在string poll 中创建字符串,那么字符串intern 方法的目的是什么?

【问题讨论】:

  • 其实这并不能回答我的问题。我需要知道新关键字是否会在字符串池中创建条目。
  • 是的,它会的。是的,该线程的答案中对此进行了解释
  • 不,您的new String(..) 永远不会将创建的字符串单独放入字符串池中。您需要显式调用intern(),并且仅当池中没有这样的字符串时才会将其放置在池中。但是由于您在构造函数中使用了 "Cat" 字符串文字(并且文字从池中放置/重用),您可以确定池将包含表示 Cat 序列的字符串。
  • @Stultuske 不,不会的。编译器和intern() 方法将字符串放入字符串池中。不是new 关键字。此代码在执行时会创建一个 String

标签: java string string-interning


【解决方案1】:

有多少个对象?

它会创建 2 个字符串,一个在堆中,另一个在字符串池中吗?

当您编写"Cat" 时,您最终会使用Cat 填充池,并且"Cat" 调用会从池中加载此对象。这通常在编译时就已经发生了。然后new String(...) 将创建一个新的字符串对象,完全忽略池。

所以这个 sn-p 导致了两个对象的创建。为了消除您的困惑,请考虑以下示例:

String first = "Cat";
String second = "Cat";
String third = "Cat";
String fourth = new String("Cat");

在这里,也创建了两个对象。所有的"Cat" 调用都会从池中加载字符串,所以first == second == thirdfourth 将是它自己的对象,因为它使用了new,这总是会导致创建一个新对象,绕过任何类型的缓存机制。

对象是在堆上创建还是在栈上创建并没有真正定义。内存管理完全取决于 JVM。


字符串池详细信息

对于大多数 Java 实现,字符串池已在编译期间创建和填充。当您编写"Cat" 时,编译器会将代表Cat 的字符串对象放入此池中,并且代码中的"Cat" 将通过从池中加载此对象来替换。当你反汇编一个编译的程序时,你可以很容易地看到这一点。例如源码:

public class Test {
    public static void main(String[] args) {
        String foo = "Hello World";
    }
}

反汇编(javap -v):

Classfile /C:/Users/Zabuza/Desktop/Test.class
  Last modified 30.03.2021; size 277 bytes
  SHA-256 checksum 83de8a7326af14fc95fb499af090f9b3377c56f79f2e78b34e447d66b645a285
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 59
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #9                          // Test
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = String             #8             // Hello World
   #8 = Utf8               Hello World
   #9 = Class              #10            // Test
  #10 = Utf8               Test
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               main
  #14 = Utf8               ([Ljava/lang/String;)V
  #15 = Utf8               SourceFile
  #16 = Utf8               Test.java
{
  public Test();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #7                  // String Hello World
         2: astore_1
         3: return
      LineNumberTable:
        line 3: 0
        line 4: 3
}
SourceFile: "Test.java"

如你所见,有

#7 = String             #8             // Hello World
#8 = Utf8               Hello World

并且方法中的部分被替换为

0: ldc           #7

从字符串池中加载Hello World


字符串实习

字符串实习生方法的目的是什么?

嗯,它使您可以将字符串替换为池中的版本。并用您的字符串填充池,以防它以前不存在。例如:

String first = "hello"; // from pool
String second = new String("hello"); // new object
String third = second.intern(); // from pool, same as first

System.out.println(first == second); // false
System.out.println(first == third); // true

用例

不过,我还没有看到此功能的实际应用程序。

但是,我可以想到一个用例,您可以在应用程序中动态创建长字符串,并且您知道它们稍后会再次出现。然后,您可以将字符串放入池中,以便在以后再次出现时减少内存占用。

假设您从 HTTP 响应中接收到一些长字符串,并且您知道响应在大多数情况下是完全相同的,并且您还希望将它们收集到 List

private List<String> responses = new ArrayList<>();

...

public void receiveResponse(String response) {
    ...
    responses.add(response);
}

如果不进行实习,您最终会将每个字符串实例(包括重复项)保留在您的记忆中。但是,如果你实习他们,你就不会在内存中有重复的字符串对象:

public void receiveResponse(String response) {
    ...
    String responseFromPool = response.intern();
    responses.add(responseFromPool);
}

当然,这有点做作,因为您也可以在这里使用Set

【讨论】:

  • "Cat" 什么都不做。具体来说,它会 not '查找字符串池。编译器已经将其放入字符串池中,因为它是字符串文字。当此代码运行时,new 关键字会创建一个字符串。期间。
  • @user207421 对。我添加了一些细节并改写了它
  • 当然,但你一直在谈论“"Cat" 通话”。这没什么意思。而且它不是“对于大多数 Java 实现”。 all 实现的 JLS 要求编译器对常量字符串进行池化。
  • 嗯,无论如何,你必须以一种初学者也能理解的方式来表达事物。我认为“猫叫”足够准确,没有错误或任何东西。 JLS 要求对字符串文字进行实习,但它根本没有解释这应该如何实现。尤其是这是否发生在编译时(正如 Stephen C 在他的评论部分所暗示的那样)。请随意建议对您将如何措辞的问题进行修改,因为我真的不明白您认为究竟是什么错误或不够准确,谢谢。
  • 放下它。这里只讨论两个词:"Cat" 和“呼叫”。第一个是问题,所以它只能是“调用”,即字符串文字做某事的整个概念,我已经说过了。我同意JLS 不仅模糊而且不正确,因为编译器不调用String.intern(),所以在运行时遇到字符串字面量时会发生某些事情。它没有。
【解决方案2】:

new 运算符正在创建一个 String 实例。

在某个时间点创建了第二个 String 实例,以表示传递给 String(String) 构造函数的字符串字面量参数。但是我们不能肯定地说它是在我们执行该语句时创建的。 (它可能已经创建了。)

我们可以肯定地说:

  1. 表示文字的对象是在调用new 运算符之前创建的。
  2. 它在应用程序的生命周期内创建一次1

有趣的是,我们不能绝对肯定地说涉及“字符串池”,或者在任何时候都使用了String.intern。 Java 15 JLS 在section 3.10.5 中声明了这一点:

“此外,字符串字面量总是指代 String 类的同一个实例。这是因为字符串字面量 - 或者更一般地说,作为常量表达式值的字符串(第 15.29 节) - 是“内部的”以便共享唯一的实例,好像通过执行方法 String.intern (§12.5)。"

好像”的用语是说运行时系统必须看起来以特定方式运行......但它不需要实际上 以这种方式实现。如果有一个可行的替代方法可以替代在字符串文字上调用String.intern,从而使String 对象具有正确的唯一性属性,那么这也是一个可以接受的实现。根据 JLS!

(实际上,所有 Hotspot JVMdo 使用字符串池,do 使用String.intern。但这是一个“实现细节”。)


1 - 除非包含该语句的类被多次加载。

【讨论】:

  • 我们可以肯定地说,编译器已经把它放入了字符串池中。
  • 实际上,我们不能。如果您戴上语言律师的帽子(或者是假发?)并仔细阅读 JLS,则不会。 >>在实践中
【解决方案3】:

当我们创建一个String对象String s1=new String("hello");时,字符串文字"hello"存储在String常量池中,引用存储在堆内存中,s1指向该引用。 String s2="hello"; 在这种情况下,字符串文字存储在字符串常量池中,s2 被直接引用到该地址。String s3=s1.intern(); 如果你尝试这个,你正在使 s3 指向存储在中的字符串文字的相同地址字符串常量池。

String s1=new String("hello");  
String s2="hello";  
String s3=s1.intern();//returns string from pool, now it will be same as s2  
System.out.println(s1==s2);//false because reference variables are pointing to different instance  
System.out.println(s2==s3);//true because reference variables are pointing to same instance  

希望对你有帮助,如有其他问题请留言。

【讨论】:

    【解决方案4】:

    我们可以使用 intern() 方法将其放入池中,或者从字符串池中引用另一个具有相同值的 String 对象。

    如上一行,将创建 1 个或 2 个字符串。

    如果池中已经存在字符串字面量“Cat”,则池中只会创建一个字符串“str”。如果池中没有字符串字面量“Cat”,那么会先在池中创建,再在堆空间中创建,所以一共会创建2个字符串对象。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-29
      • 1970-01-01
      • 2012-03-22
      • 1970-01-01
      • 1970-01-01
      • 2016-01-26
      • 1970-01-01
      相关资源
      最近更新 更多