java string详解 - “text”和新的String(“text”)之间有什么区别?




string类 string...什么意思 (9)

String str = new String("hello")

它会检查String常量池是否已经包含String“hello”? 如果存在,则不会在字符串常量池中添加条目。 如果不存在,则会在字符串常量池中添加一个条目。

将在堆内存区域中创建一个对象,并在堆内存位置中创建str对象以指向对象。

如果你想str引用包含在String常量池中的point对象,那么你必须显式调用str.intern();

String str = "world";

它会检查String常量池是否已经包含String“hello”? 如果存在,则不会在字符串常量池中添加条目。 如果不存在,则会在字符串常量池中添加一个条目。

在以上两种情况下,常量池中的str参考指向String "world"

这两个声明有什么区别?

String s = "text";

String s = new String("text");

字符串literals将进入字符串常量池

下面的快照可能会帮助您直观地理解它,以便更长时间记住它。

逐行创建对象:

String str1 = new String("java5");

在构造函数中使用字符串文字“java5”,一个新的字符串值被存储在字符串常量池中。 使用new运算符,将在“java5”作为值的堆中创建一个新的字符串对象。

String str2 = "java5"

参考“str2”指向字符串常量池中已存储的值

String str3 = new String(str2);

在堆中创建一个新的字符串对象,其值与“str2”的引用值相同

String str4 = "java5";

引用“str4”指向字符串常量池中已存储的值

总物件:堆 - 2,池 - 1

进一步阅读Oracle社区


尽管从程序员的角度来看它看起来是一样的,但它对性能有很大的影响。 你会想要几乎总是使用第一种形式。


了解差异的一个简单方法如下: -

String s ="abc";
String s1= "abc";
String s2=new String("abc");

        if(s==s1){
            System.out.println("s==s1 is true");
        }else{
            System.out.println("s==s1 is false");
        }
        if(s==s2){
            System.out.println("s==s2 is true");
        }else{
            System.out.println("s==s2 is false");
        }

输出是

s==s1 is true
s==s2 is false

因此新的String()将总是创建一个新的实例。


"bla"想象成像Strings.createString("bla") (pseudo)这样的魔法工厂。 工厂拥有这样创建的所有字符串池。

如果它被调用,它将检查池中是否已经有字符串。 如果为true,则返回此字符串对象,因此以这种方式获得的字符串的确是同一个对象。

如果没有,它将在内部创建一个新的字符串对象,并将其保存在池中,然后将其返回。 因此,下次查询相同的字符串值时,它将返回相同的实例。

手动创建new String("")会绕过字符串文字池来覆盖此行为。 所以平等应该总是使用equals()来检查,它比较字符序列而不是对象引用的相等性。


JLS

这个概念被JLS称为“实习”。

JLS 7相关文章3.10.5

此外,字符串文字始终指的是String类的同一个实例。 这是因为字符串文字 - 或者更一般地说是常量表达式(§15.28)值的字符串 - 被“实施”,以便使用方法String.intern共享唯一实例。

例3.10.5-1。 字符串文字

该程序由编译单元(第7.3节)组成:

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

和编制单位:

package other;
public class Other { public static String hello = "Hello"; }

产生输出:

true true true true false true

JVMS

JVMS 7 5.1说

字符串文字是对String类实例的引用,并且是从类或接口的二进制表示中的CONSTANT_String_info结构(第4.4.3节)派生而来的。 CONSTANT_String_info结构给出构成字符串文字的Unicode代码点序列。

Java编程语言要求相同的字符串文字(即包含相同的代码点序列的文字)必须引用String类的相同实例(JLS§3.10.5)。 另外,如果在任何字符串上调用String.intern方法,则结果是对同一个类实例的引用,如果该字符串以文字形式出现,则会返回该实例。 因此,以下表达式必须具有真值:

("a" + "b" + "c").intern() == "abc"

为了派生字符串文字,Java虚拟机检查由CONSTANT_String_info结构给出的代码点序列。

  • 如果先前在类String的实例上调用了String.intern方法,该实例包含与CONSTANT_String_info结构相同的Unicode代码点序列,则字符串文字派生的结果是对同一类String实例的引用。

  • 否则,将创建一个包含由CONSTANT_String_info结构给出的Unicode代码点序列的类String的新实例; 对该类实例的引用是字符串文字派生的结果。 最后,调用新的String实例的intern方法。

字节码

查看OpenJDK 7上的字节码实现也很有意义。

如果我们反编译:

public class StringPool {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a);
        System.out.println(b);
        System.out.println(a == c);
    }
}

我们有不断的池:

#2 = String             #32   // abc
[...]
#32 = Utf8               abc

main

 0: ldc           #2          // String abc
 2: astore_1
 3: ldc           #2          // String abc
 5: astore_2
 6: new           #3          // class java/lang/String
 9: dup
10: ldc           #2          // String abc
12: invokespecial #4          // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne     42
38: iconst_1
39: goto          43
42: iconst_0
43: invokevirtual #7          // Method java/io/PrintStream.println:(Z)V

请注意:

  • 03 :加载相同的ldc #2常量(文字)
  • 12 :创建一个新的字符串实例(以#2作为参数)
  • 35acif_acmpne作为常规对象进行if_acmpne

常量字符串的表示在字节码上非常神奇:

而上面的JVMS报价似乎表示,只要Utf8指向的是相同的,则相同的实例由ldc加载。

我已经为领域做了类似的测试,并且:

  • static final String s = "abc"通过ConstantValue属性指向常量表
  • 非final字段没有该属性,但仍可以使用ldc进行初始化

结论 :对字符串池有直接的字节码支持,并且内存表示是有效的。

奖金:将其与整体相比较,该不具有直接的字节码支持(即没有CONSTANT_String_info模拟)。


一个在字符串常量池中创建一个字符串

String s = "text";

另一个在常量池( "text" )中创建一个字符串,在正常堆空间中创建另一个字符串。 两个字符串都具有相同的值,即“文本”。

String s = new String("text");

如果后来未被使用,则s丢失(符合GC的条件)。

另一方面,字符串文字被重用。 如果在类的多个位置使用"text" ,实际上它将是唯一的一个String(即对池中的同一字符串的多个引用)。


@布拉杰:我想你已经提到了另一种方式。 如果我错了,请纠正我

逐行创建对象:

String str1 = new String(“java5”)

   Pool- "java5" (1 Object)

   Heap - str1 => "java5" (1 Object)

String str2 =“java5”

  pool- str2 => "java5" (1 Object)

  heap - str1 => "java5" (1 Object)

String str3 = new String(str2)

  pool- str2 => "java5" (1 Object)

  heap- str1 => "java5", str3 => "java5" (2 Objects)

String str4 =“java5”

  pool - str2 => str4 => "java5" (1 Object)

  heap - str1 => "java5", str3 => "java5" (2 Objects)

这是另一种做法..

void split_string(string text,vector<string>& words)
{
  int i=0;
  char ch;
  string word;

  while(ch=text[i++])
  {
    if (isspace(ch))
    {
      if (!word.empty())
      {
        words.push_back(word);
      }
      word = "";
    }
    else
    {
      word += ch;
    }
  }
  if (!word.empty())
  {
    words.push_back(word);
  }
}






java string