不管是Java语言还是C/C++语言,String可以说是我们用的最多的类型吧!但是你是否真的了解它呢?那下面咱们一起聊聊Java String吧!
String常量
JDK版本不同,字符串常量所处的JVM位置不同,关于虚拟机运行时数据区,后续专门找一个机会来分享;
- 版本<=JDK 1.6:字符串常量池--永久代;
- 版本>=JDK 1.7:字符串常量池--堆;
备注:本文示例JDK环境:JDK 1.8示例代码
String.intern
返回标准表示的字符串对象。String类维护私有字符串池。 调用此方法时,如果字符串池已经包含等于此字符串对象的字符串(通过equals方法确定), 则返回池中的字符串。 否则,将此String对象添加到池中,并返回对此String对象的引用。 这个功能为String提供了字符串池,我们可以使用它来优化内存。但是,这有一个缺点:在OpenJDK中,String.intern()是本地方法,它实际上调用了JVM的相关方法来实现该功能。
public static void main(String args[]) {
String s1 = new String("hello") + new String(" java");
s1.intern();
String s2 = "hello java";
System.out.println("s1.intern()==s2: " + (s1.intern()==s2));
System.out.println("s1==s2: " + (s1==s2));
String s3 = new String("hello");
s3.intern();
String s4 = "hello";
System.out.println("s3.intern()==s4: " + (s3.intern()==s4));
System.out.println("s3==s4: " + (s3==s4));
}
复制代码
输出结果:
s1.intern()==s2: true
s1==s2: true
s3.intern()==s4: true
s3==s4: false
复制代码
说明:
第一行代码:共计创建五个对象:2个字符串常量对象,3个堆对象,其中"hello java"堆对象是最终的需要的对象;
第二行代码:执行intern,首先去常量池查找,如果没有找到,则原则上需要在常量池创建一份s1数据的拷贝,但是由于JDK8常量池并入了Heap上,因此,不会再创建一份s1数据的拷贝,直接创建一份引用指向s1,保存到常量池中。
第三行代码:显示声明一个字符串常量对象,由于常量池中已经存在,则直接和常量池一样,指向s1;
所以,无论s1,s1.intern()和s2都指向同一个对象,因此,s1=s2=s1.intern();
第六行代码:执行完,创建两个对象,一个在heap,一个在常量池;
第七行代码:执行s3.intern()时,由于常量池中已经创建了此对象(在执行第六行的时候创建的),因此执行s3.intern(),则不做任何处理;
第八行代码:显示声明一个字符串常量对象,由于常量池中已经存在,则直接指向常量池中的对象;
所以:s3.intern()指向常量池中的对象,s4则也指向常量池,s3.intern() = s4;s3指向堆中的对象,则s3 != s4;
具体的对象关系如下图:
)String, StringBuffer, StringBuilder
String | StringBuffer | StringBuilder |
---|---|---|
String的值是不可变的,这就导致每次 对String的操作都会生成新的String对象, 不仅效率低下,而且浪费大量优先的内存空间 | StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量 | 可变类,速度更快 |
线程安全 | 线程不安全 | |
多线程操作字符串 | 单线程操作字符串 |
String用到的“+”号
在Java中的String类已经重载的"+",因此String支持“+”,通过反编译的字节码来看,其实是通过创建StringBuilder来完成字符串拼接;
这里具体说明:如果在for循环内部,不建议使用“+”的方式来进行字符串的拼接,会增加系统的开销。此处顺便回答了颠倒语序如何实现?中的一个遗留问题!
public static void test_string_builder()
{
long start = System.currentTimeMillis();
Random rand = new Random();
StringBuilder res = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
res.append(rand.nextInt(10));
res.append(" ");
}
System.out.println(res.toString());
long end = System.currentTimeMillis();
System.out.println("test_string_builder cost: " + (end - start));
}
复制代码
时间成本: test_string_builder cost: 1
复制代码
public static void test_string_plus()
{
long start = System.currentTimeMillis();
Random rand = new Random();
String res = "";
for (int i = 0; i < 1000; i++)
{
res += rand.nextInt(10);
res += " ";
}
System.out.println(res.toString());
long end = System.currentTimeMillis();
System.out.println("test_string_plus cost: " + (end - start));
}
复制代码
时间成本:test_string_plus cost: 6
复制代码