Java String Immutability和使用相同的字符串值来创建新字符串

我知道问题的标题不是很清楚,对不起,不知道如何提出。 我有一个非常基本的java实现问题,我想关注应用程序性能,但它也涉及java中的String创建模式。

我理解Java中字符串的不变性概念。 我不确定的是,我在某处读到以下内容不会产生两个不同的String对象:

String name = "Sambhav"; String myName= "Sambhav"; 

我想知道Java是如何做到的? 它是否实际在程序存储器中查找String值并检查它是否存在,如果它不存在则创建一个新的String对象? 在这种情况下,显然它节省了内存,但存在性能问题。

还可以说我有这样的代码:

  public void some_method(){ String name = "Sambhav"; System.out.println(name); // or any random stufff } 

现在每次调用此函数时,是否有新的String被添加到内存中,或者我是否使用相同的String对象? 我很想知道这一切是如何发生的见解的?

如果我们这样说的话

 String name = "Sambhav"; String myName= "Sambhav"; 

因为引用不会创建新对象,怎么样

 String name = new String("Sambhav"); String myName= new String("Sambhav"); 

Java仍然能够捕获字符串是否相同,只是将myName指向与前一个语句中创建的对象相同的对象?

字符串是内部字符串数组,具有一些固有的function,可以使用底层字符数组。 例如。 subString(int),split(String)方法。

字符串是不可变的,这意味着为更改String引用所做的任何努力都会创建一个新的String并为其分配内存。 如下

 line 1. String a = new String("SomeString"); line 2. a = "SomeStringChanged"; 

第1行使用变量a引用的“SomeString”分配内存,并将“ SomeString ”添加到String Pool

第2行在字符串池中使用“SomeStringChanged”分配内存并由aie a引用,现在不指向“SomeString”,“SomeString”占用的内存现在可用于gc

这里没有重用

 line 3. String b = "SomeStringChanged"; 

现在,变量ab 重用了文字“ SomeStringChanged ”。 即它们指的是相同的内存位置 ,实际上是指一个称为“ 字符串池 ”的位置。

 line 4. a = new String("SomeStringChanged"); 

现在完成一个新的分配,其中包含“ SomeStringChanged ”并由a引用

现在没有重复使用 。 (char数组SomeStringChanged已存在于String Pool中。因此不会发生字符串池分配)

 line 5. a = new String("SomeStringChanged").intern(); 

现在,第4行中创建的分配被丢弃,变量ab指的是字符串池中包含“SomeStringChanged”的相同位置。 这里有重复使用相同的char数组。 功劳归于intern()方法

 line 6. String x = new String("SomeX"); line 7. String y = "SomeX"; 

第6行将为堆和字符串池中的SomeX创建分配。 char数组是重复的。

第7行不会为SomeX分配任何内存,因为它已存在于字符串池中

 Line 8 String s = new String(someStringVariable); 

第8行只会在堆中分配单个内存位置而不在String Pool中。

总之,只有将String引用声明为文字或者String对象被实现时,才能重用char字符串数组。即只有这两个才能使用String池(实际上是char数组重用背后的想法) )。

您在源文件中放置引号的字符串“就像那样”编译时常量 ,如果它们的内容匹配,它们由类的字节码表示中的常量池中的单个条目表示,因此表示单个String对象在运行时。

 String name = new String("Sambhav"); String myName= new String("Sambhav"); 

这些是显式的不同对象,将为每个调用创建一个新的String对象,尽管它可以重用底层字符串的char数组(在构造函数中提供的字符串)。 这是因为新的关键字设想Java创建一个新对象。 这就是为什么name!= myName ,即使name.equals(myName)

String name = new String("Sambhav");

String myName = new String("Sambhav");

Java仍然能够捕获字符串是否相同,只是将myName指向与前一个语句中创建的对象相同的对象?

JVM通过计算hash来设法仅保留一个相等String对象的引用。

这些String对象保存在String pool

字符串池

字符串池(有时也称为字符串规范化)是使用单个共享String对象替换具有相同值但不同标识的多个String对象的过程。

您可以通过保留自己的Map (根据您的要求可能有软或弱引用)并使用map值作为规范化值来实现此目标。

或者您可以使用JDK提供给您的String.intern()方法。

JVM版本的快速字符串池差异

在Java 6中,此String pool位于Perma Gen内存中。 这种记忆通常很小且有限。 此外,不应使用String.intern()因为您可能会耗尽内存。

在Java 7和8中,它被取出到heap内存并使用类似数据结构的hash-table实现。

由于类似hash-table结构( HashMapWeakHashMap )使用计算的hash来以恒定的复杂度访问条目,因此整个过程非常快。

如本文所述:

  • 由于用于JVM字符串池存储的固定大小的内存区域(PermGen),因此远离Java 6上的String.intern()方法。

  • Java 7和8在堆内存中实现字符串池。 这意味着您受到Java 7和8中字符串池的整个应用程序内存的限制。

  • 在Java 7和8中使用-XX:StringTableSize JVM参数来设置字符串池映射大小。 它是固定的,因为它实现为带有列表中的列表的哈希映射。 近似应用程序中不同字符串的数量(您打算实习)并将池大小设置为等于接近此值的某个素数。 它将允许String.intern()在常量时间内运行,并且每个实现字符串需要相当小的内存消耗(显式使用的Java WeakHashMap将为同一任务消耗4-5倍的内存)。

  • -XX:StringTableSize参数的默认值在Java 7中为1009 ,在Java 8中为25-50K左右。

您实际上显示了为什么字符串可以在内部使用相同的缓冲区的3个不同原因。 请注意,共享缓冲区仅适用于单独的实例,因为它们是不可变的; 否则缓冲区中的更改也会反映在其他变量值中。

  1. 编译器检测到相同的字符串文字; 如果重复字符串文字,编译器可能只是指向同一个对象实例;

  2. 对String的引用指向同一个对象实例,因此按定义相同;

  3. 缓冲共享可能有助于在构建new 。 如果运行时系统发现可以共享String内容,那么它可以选择这样做; 但是这种行为并不能保证 – 它是特定于实现的。 对象实例应该是不同的(但是将它们用作单独的实例仍然不是明智的)。

作为#3的示例,Java 6 OpenJDK源将简单地指向相同的缓冲区。 如果缓冲区大于新的String实例,则将创建一个副本。 这些是显式的不同对象,将为每个调用创建一个新的String对象,尽管它可以重用底层字符串的char数组(在构造函数中提供的那个),以便垃圾收集器可以清除更大的字符串(否则更大的字符缓冲区)可以无限期地保存在记忆中)。

除非你粗心大意并开始使用==进行相等(或其他将==equals混淆的结构),否则对你来说这一切都无关紧要。