字符内存转成字符串
String类如何存储文本,内部存储和常量池如何工作。  这里要理解的要点是String Java对象与其内容– private value字段下的char[]之间的区别。 String基本上是char[]数组的包装器,将其封装并使其无法修改,因此String可以保持不变。 另外, String类还记住该数组的实际部分(请参阅下文)。 这一切都意味着您可以有两个不同的String对象(相当轻量)指向相同的char[] 。 
 我会告诉你一些例子,连同hashCode()的每个String和hashCode()内部的char[] value字段(我将其称之为文本字符串从区分)。 最后,我将显示javap -c -verbose输出以及测试类的常量池。 请不要将类常量池与字符串文字池混淆。 它们并不完全相同。 另请参见了解javap的常量池输出 。 
先决条件
 为了进行测试,我创建了一个实用程序方法来破坏String封装: 
private int showInternalCharArrayHashCode(String s) {final Field value = String.class.getDeclaredField("value");value.setAccessible(true);return value.get(s).hashCode();
} 它将打印char[] value hashCode() ,有效地帮助我们了解此特定String是否指向相同的char[]文本。 
一个类中的两个字符串文字
让我们从最简单的示例开始。
Java代码
String one = "abc";
String two = "abc"; 顺便说一句,如果您只写"ab" + "c" ,则Java编译器将在编译时执行级联,并且生成的代码将完全相同。 仅当在编译时知道所有字符串时,此方法才有效。 
 类常量池 
 每个类都有自己的常量池 -常量值列表,如果它们在源代码中多次出现,则可以重用。 它包括常见的字符串,数字,方法名称等。 
 这是上面示例中常量池的内容: 
const #2 = String   #38;    //  abc
//...
const #38 = Asciz   abc; 要注意的重要事项是String常量对象( #2 )和字符串指向的Unicode编码文本"abc" ( #38 )之间的区别。 
 字节码 
 这是生成的字节码。 请注意, one引用和two引用都分配有指向"abc"字符串的相同#2常量: 
ldc #2; //String abc
astore_1    //one
ldc #2; //String abc
astore_2    //two 输出量 
 对于每个示例,我将打印以下值: 
System.out.println("one.value: " + showInternalCharArrayHashCode(one));
System.out.println("two.value: " + showInternalCharArrayHashCode(two));
System.out.println("one" + System.identityHashCode(one));
System.out.println("two" + System.identityHashCode(two));这两对相等并不奇怪:
one.value: 23583040
two.value: 23583040
one: 8918249
two: 8918249 这意味着不仅两个对象都指向相同的char[] (下面的相同文本),所以equals()测试将通过。 但更重要的是, one和two是完全相同的引用! 因此, one == two也是正确的。 显然,如果one和two指向同一个对象,则one.value和two.value必须相等。 
 文字和new String()  
 Java代码 
 现在,我们都在等待该示例–一个字符串文字和一个使用相同文字的新String 。 这将如何运作? 
String one = "abc";
String two = new String("abc"); 在源代码中两次使用了"abc"常量这一事实应该给您一些提示…… 
类常量池与上面相同。
字节码
ldc #2; //String abc
astore_1    //onenew #3; //class java/lang/String
dup
ldc #2; //String abc
invokespecial   #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V
astore_2    //two 仔细地看! 第一个对象的创建方法与上面相同,不足为奇。 它只需要从常量池中常量引用已经创建的String ( #2 )。 但是,第二个对象是通过常规构造函数调用创建的。 但! 第一个String作为参数传递。 可以将其反编译为: 
String two = new String(one); 输出量 
 输出有点令人惊讶。 第二对表示对String对象的引用是可以理解的-我们创建了两个String对象-一个在常量池中为我们创建,第二个是为two手动创建的。 但是,为什么第一对建议两个String对象都指向同一个char[] value数组呢? 
one.value: 41771
two.value: 41771
one: 8388097
two: 16585653 当您查看String(String)构造函数的工作原理时,这一点变得很清楚(此处已大大简化了): 
public String(String original) {this.offset = original.offset;this.count = original.count;this.value = original.value;
} 看到? 当基于现有对象创建新的String对象时,它会重用 char[] value 。 String是不可变的,不需要复制已知永远不会修改的数据结构。 而且,由于new String(someString)创建了现有字符串的精确副本,并且字符串是不可变的,因此显然没有理由同时存在两者。 
 我认为这是一些误解的线索:即使您有两个String对象,它们仍可能指向相同的内容。 如您所见, String对象本身很小。 
 运行时修改和intern()  
 Java代码 
 假设您最初使用了两个不同的字符串,但是在进行一些修改之后,它们都是相同的: 
String one = "abc";
String two = "?abc".substring(1);  //also two = "abc"Java编译器(至少是我的)不够聪明,无法在编译时执行此类操作,请看一下:
 类常量池 
 突然我们以指向两个不同常量文本的两个常量字符串结尾: 
const #2 = String   #44;    //  abc
const #3 = String   #45;    //  ?abc
const #44 = Asciz   abc;
const #45 = Asciz   ?abc;字节码
ldc #2; //String abc
astore_1    //oneldc #3; //String ?abc
iconst_1
invokevirtual   #4; //Method String.substring:(I)Ljava/lang/String;
astore_2    //two 拳头弦照常构造。 通过首先加载常量"?abc"字符串,然后在其上调用substring(1)来创建第二个。 
输出量
 这里不足为奇–我们有两个不同的字符串,指向内存中两个不同的char[]文本: 
one.value: 27379847
two.value: 7615385
one: 8388097
two: 16585653 好吧,文本并没有真正的不同 , equals()方法仍然会产生true 。 我们有两个不必要的相同文本副本。 
 现在我们应该进行两次练习。 首先,尝试运行: 
two = two.intern(); 在打印哈希码之前。 one和two不仅指向同一文本,而且它们是相同的参考! 
one.value: 11108810
two.value: 11108810
one: 15184449
two: 15184449 这意味着one.equals(two)和one == two测试都将通过。 我们还节省了一些内存,因为"abc"文本在内存中仅出现一次(第二个副本将被垃圾回收)。 
 第二个练习略有不同,请查看以下内容: 
String one = "abc";
String two = "abc".substring(1); 显然one和two是两个不同的对象,指向两个不同的文本。 但是输出如何表明它们都指向同一个char[]数组?! 
one.value: 11108810
two.value: 8918249
one: 23583040
two: 23583040 我将答案留给你。 它会教您substring()工作原理,这种方法的优点是什么以及何时会导致大麻烦 。 
得到教训
-  String对象本身相当便宜。 它指向的文本占用了大部分内存
-  String只是char[]的薄包装,以保持不变性
-  new String("abc")作为内部文本表示被重用是不是真的那么贵。 但是还是要避免这样的构造。
-  从编译时已知的常量值连接String时,连接由编译器而不是由JVM完成
-  substring()有点棘手,但最重要的是,就使用的内存和运行时间而言,它都很便宜(在两种情况下均保持不变)
参考: Java和社区博客中来自JCG合作伙伴 Tomasz Nurkiewicz的字符串内存内部 。
翻译自: https://www.javacodegeeks.com/2012/07/string-memory-internals.html
字符内存转成字符串