要在ava中创建字符串对象,请使用String str='droid '; 另一种方法是使用名为new的标准结构对象,如stringstr=newstring('droid )。这两种方式在代码编写时都经常使用。 特别是字面量的方式。 但是,这两种实现实际上存在一些性能和内存消耗的差异。 这一切都是因为JVM保留了特殊的内存,以减少字符串对象的重复创建。 此内存是字符串常量池或字符串文字池。
工作原理
在代码中以文字格式创建字符串对象时,JVM首先检查该文字,如果字符串常量池中有对内容相同的字符串对象的引用,则返回该引用,否则创建新的字符串对象,并将该引用放入字符串常量池中
举例说明
字面的制作形式
字符串str1=' droid ';
JVM检测该字面量。 我们认为这里不存在内容为droid的对象。 如果JVM在字符串常量池中找不到droid字符串对象的存在,它将创建此字符串对象,将对刚创建的对象的引用放入字符串常量池,并将引用返回给变量str1。
如果有以下代码的话
字符串str2=' droid ';
此外,JVM还必须检测此文字。 JVM搜索字符串常量池,发现存在名为" droid "的字符串对象,并将对已存在的字符串对象的引用返回给变量str2。 请注意,这里不会重新创建新的字符串对象。
使用此代码可以确定str1和str2是否指向同一对象
system.out.println(str1==str2;
结果为true。
使用new创建
stringstr3=newstring('droid ' );
使用new创建字符串对象时,无论字符串常量池中是否存在对内容相同的对象的引用,都会创建新的字符串对象。 所以,用下面的代码测试一下吧。
stringstr3=newstring('droid ' );
system.out.println(str1==str3;
如结果所示,为false,表示这两个变量指向不同的对象。
国际刑警组织
对于上面使用new创建的字符串对象,如果要将对此对象的引用添加到字符串常量池中,请使用intern方法。
调用intern时,首先检查字符串常量池中是否存在对该对象的引用,如果有,则将该引用返回给变量,如果没有,则添加引用并返回给变量。
String str4=str3.intern (;
system.out.println(str4==str1;
输出结果为true。
难题
前提条件?
实现字符串池的前提条件是Java字符串对象保持不变。 这确保了多个变量共享同一对象是安全的。 如果Java string对象是可变的,则在一个浏览操作中更改对象的值时,其他变量也会受到影响。 这显然不合理。
浏览or对象
最常见的问题是存储在字符串常量池中的时间引用还是对象。 字符串池存储对象引用,而不是对象。 在Java中,对象是在堆内存中创建的。
更新了验证,很多评论也讨论了这个问题,所以很容易就验证了。 验证环境
2:18:54-Android yue~~/videos $ cat/etc/OS-release
NAME=Fedora
版本=' 17 (beefy miracle ) )。
ID=fedora
VERSION_ID=17
pretty _ name=' fedora 17 (beefy miracle ) '
ANSI_COLOR='0; 34 '
CPE _ name=' CPE :/o : fedora project : fedora 336017 '
2:19:04-Android yue~~/videos $ Java-version
Java版本'1.7.0_ 25 '
openjdkruntimeenvironment (fedora-2.3.12.1.fc17-x86 _ 64 ) ) ) )。
open JDK 64-bitservervm (构建23.7-b01,混合模式) ) ) ) ) ) )。
验证思路:以下Java程序读取大小82M的视频文件,作为字符串进行intern操作。
2:01:17-Android yue~/videos $ ll-LH|grep why _ to _ learn.MP4
- rw-rw-r----1androidyueandroidyue 82 moct 202013 why _ to _ learn.MP4
代码验证
import java.io.BufferedReader;
导入
java.io.FileNotFoundException;import java.io.FileReader;
import java.io.IOException;
public class TestMain {
private static String fileContent;
public static void main(String[] args) {
fileContent = readFileToString(args[0]);
if (null != fileContent) {
fileContent = fileContent.intern();
System.out.println("Not Null");
}
}
private static String readFileToString(String file) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
StringBuffer buff = new StringBuffer();
String line;
while ((line = reader.readLine()) != null) {
buff.append(line);
}
return buff.toString();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != reader) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
由于字符串常量池存在于堆内存中的永久代,适用于Java8之前。我们通过设置永久代一个很小的值来进行验证。如果字符串对象存在字符串常量池中,那么必然抛出java.lang.OutOfMemoryError permgen space错误。
java -XX:PermSize=6m TestMain ~/Videos/why_to_learn.mp4
运行证明程序没有抛出OOM,其实这个不能很好的证明存储的是对象还是引用。
但是这个至少证明了字符串的实际内容对象char[]不存放在字符串常量池中。既然这样的话,其实字符串常量池存储字符串对象还是字符串对象的引用反而不是那么重要。但个人还是倾向于存储的为引用。
优缺点
字符串常量池的好处就是减少相同内容字符串的创建,节省内存空间。
如果硬要说弊端的话,就是牺牲了CPU计算时间来换空间。CPU计算时间主要用于在字符串常量池中查找是否有内容相同对象的引用。不过其内部实现为HashTable,所以计算成本较低。
GC回收?
因为字符串常量池中持有了共享的字符串对象的引用,这就是说是不是会导致这些对象无法回收?
首先问题中共享的对象一般情况下都比较小。据我查证了解,在早期的版本中确实存在这样的问题,但是随着弱引用的引入,目前这个问题应该没有了。
intern使用?
关于使用intern的前提就是你清楚自己确实需要使用。比如,我们这里有一份上百万的记录,其中记录的某个值多次为美国加利福尼亚州,我们不想创建上百万条这样的字符串对象,我们可以使用intern只在内存中保留一份即可。关于intern更深入的了解请参考深入解析String#intern。
总有例外?
你知道下面的代码,会创建几个字符串对象,在字符串常量池中保存几个引用么?
String test = "a" + "b" + "c";
答案是只创建了一个对象,在常量池中也只保存一个引用。我们使用javap反编译看一下即可得知。
17:02 $ javap -c TestInternedPoolGC
Compiled from "TestInternedPoolGC.java"
public class TestInternedPoolGC extends java.lang.Object{
public TestInternedPoolGC();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: ldc #2; //String abc
2: astore_1
3: return
看到了么,实际上在编译期间,已经将这三个字面量合成了一个。这样做实际上是一种优化,避免了创建多余的字符串对象,也没有发生字符串拼接问题。关于字符串拼接,可以查看Java细节:字符串的拼接。