Java中字符串常量和字符串变量的区别
ava中字符串对象创建有两种形式,⼀种为字⾯量形式,如String str = "droid";,另⼀种就是使⽤new这种标准的构造对象的⽅法,如String str = new String("droid");,这两种⽅式我们在代码编写时都经常使⽤,尤其是字⾯量的⽅式。然⽽这两种实现其实存在着⼀些性能和内存占⽤的差别。这⼀切都是源于JVM为了减少字符串对象的重复创建,其维护了⼀个特殊的内存,这段内存被成为字符串常量池或者字符串字⾯量池。
⼯作原理
当代码中出现字⾯量形式创建字符串对象时,JVM⾸先会对这个字⾯量进⾏检查,如果字符串常量池中存在相同内容的字符串对象的引⽤,则将这个引⽤返回,否则新的字符串对象被创建,然后将这个引⽤放⼊字符串常量池,并返回该引⽤。
举例说明
字⾯量创建形式
复制代码代码如下:
String str1 = "droid";
JVM检测这个字⾯量,这⾥我们认为没有内容为droid的对象存在。JVM通过字符串常量池查不到内容为droid的字符串对象存在,那么会创建这个字符串对象,然后将刚创建的对象的引⽤放⼊到字符串常量池中,并且将引⽤返回给变量str1。
如果接下来有这样⼀段代码
复制代码代码如下:
String str2 = "droid";
同样JVM还是要检测这个字⾯量,JVM通过查字符串常量池,发现内容为”droid”字符串对象存在,于是将已经存在的字符串对象的引⽤返回给变量str2。注意这⾥不会重新创建新的字符串对象。
验证是否为str1和str2是否指向同⼀对象,我们可以通过这段代码
复制代码代码如下:
System.out.println(str1 == str2);
结果为true。
使⽤new创建
复制代码代码如下:
String str3 = new String("droid");
当我们使⽤了new来构造字符串对象的时候,不管字符串常量池中有没有相同内容的对象的引⽤,新的字符串对象都会创建。因此我们使⽤下⾯代码测试⼀下,
复制代码代码如下:
String str3 = new String("droid");
System.out.println(str1 == str3);
结果如我们所想,为false,表明这两个变量指向的为不同的对象。
intern
对于上⾯使⽤new创建的字符串对象,如果想将这个对象的引⽤加⼊到字符串常量池,可以使⽤intern⽅法。
调⽤intern后,⾸先检查字符串常量池中是否有该对象的引⽤,如果存在,则将这个引⽤返回给变量,否则将引⽤加⼊并返回给变量。
复制代码代码如下:
String str4 = str3.intern();
System.out.println(str4 == str1);
输出的结果为true。
疑难问题
前提条件?
字符串常量池实现的前提条件就是Java中String对象是不可变的,这样可以安全保证多个变量共享同⼀个对象。如果Java中的String对象可变的话,⼀个引⽤操作改变了对象的值,那么其他的变量也会受到影响,显然这样是不合理的。
引⽤ or 对象
字符串常量池中存放的时引⽤还是对象,这个问题是最常见的。字符串常量池存放的是对象引⽤,不是对象。在Java中,对象都创建在堆内存中。
更新验证,收到的很多评论也在讨论这个问题,我简单的进⾏了验证。验证环境:
复制代码代码如下:
22:18:54-androidyue~/Videos$ cat /etc/os-release
NAME=Fedora
VERSION="17 (Beefy Miracle)"
ID=fedora
VERSION_ID=17
PRETTY_NAME="Fedora 17 (Beefy Miracle)"
ANSI_COLOR="0;34"
CPE_NAME="cpe:/o:fedoraproject:fedora:17"
22:19:04-androidyue~/Videos$ java -version
java version "1.7.0_25"
OpenJDK Runtime Environment (fedora-2.3.12.1.fc17-x86_64)
OpenJDK 64-Bit Server VM (build 23.7-b01, mixed mode)
验证思路:以下的Java程序读取⼀个⼤⼩为82M的视频⽂件,以字符串形式进⾏intern操作。
复制代码代码如下:
22:01:17-androidyue~/Videos$ ll -lh | grep why_to_learn.mp4
-rw-rw-r--. 1 androidyue androidyue  82M Oct 20  2013 why_to_learn.mp4
验证代码
复制代码代码如下:
import java.io.BufferedReader;
import 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 = adLine()) != null) {
buff.append(line);
}
String();
} 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 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."<init>":()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
看到了么,实际上在编译期间,已经将这三个字⾯量合成了⼀个。这样做实际上是⼀种优化,避免了创建多余的字符串对象,也没有发⽣字符串拼接问题。关于字符串拼接,可以查看。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。