String字符串创建对象及拼接
String
String是一个字符串常量,属于引用类型,字符串一旦创建就不可改变,对于String字符串类型一旦发生改变,就会返回新的字符串对象.
源码分析
String字符串底层fina修饰的char[]数组,一个char字符在内存中占两个字节.value[]:用于储存String的内容
常量可以理解成一种特殊的变量,它的值被设定后,在程序运行过程中不允许被改变.常量名习惯使用全大写字符.
格式: final 常量名 = 值;
String类型的声明
创建字符串常量时,JVM会首先检查在字符串常量池,如果在字符串常量池中存在了该字符串 “Hello ming”,此时就会将该字符串对象的地址值赋值给引用 s(s存放在栈中) .如果字符串不在常量池中,就会在常量池中创建字符串,然后将字符串对象的地址值交给s.
值得注意的是:这里只创建了一个字符串对象(不考虑对象已存在的情况)—常量池
String s = "Hello ming";
创建字符串常量时,JVM会首先检查字符串常量池,如果字符串"Hi mary"已经存在常量池中,直接复制堆中这个对象的副本,然后将堆中的地址值赋给引用s,不会在字符串常量池中创建对象.如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,然后在堆中复制该对象的副本,并将对象的地址值交给s.
值得注意的是:这里会创建两个字符串对象,分别在常量池中与堆中(不考虑对象已存在的情况)
String s = new String("Hi Mary");
创建了几个对象
//创建3个对象,1)"123"字符串放入常量池 2)new String()在队中创建,3)n栈中
//s指向new String在堆中的地址,new String指向常量池中"123"的地址
String n = new String("123");
//创建了2个对象,"hello world"(编译器优化)、h栈中
String h = "hello" + " world";
//创建5个对象,"aaa"、"bbb"在常量池中创建,new String()在堆中创建,"aaabbb"在常量池中创建,s栈中
String s = "aaa" + new String("bbb");
注意:这个面试题很爱考,一般它们不算栈的,那就是上面数量-1
代码: 这里说的创建都将栈中的对象加上了的.
//创建2个对象,字符串池中不存在,创建字符串"abc",然后将字符串"abc"对象的地址给s1对象
String s1 = "abc";
//创建1个对象,字符串池中判断依据存在"abc",不再创建,直接把"abc"对象的地址给s2对象
String s2 = "abc";
//既然两个对象地址一样,当然结果就为true
System.out.println("s1 == s2\t\t"+(s1 == s2));
//值相同
System.out.println("s1.equals(s2)\t"+s1.equals(s2));
//创建2个对象,new String开辟地址空间,指向常量池"abc"地址,s3栈中
String s3 = new String("abc");
//创建2个对象,new String开辟地址空间,指向常量池"abc"地址,s4栈中
String s4 = new String ("abc");
System.out.println("s3 == s4\t\t"+(s3 == s4));
//值相同
System.out.println("s3.equals(s4)\t"+s3.equals(s4));
结果:
字符串拼接
从文章中我们知道,String是一个不可变类,一旦初始化之后就不可以修改.那我们常听到的字符串拼接又是怎么回事呢?
所谓字符串拼接就是生成一个新的字符串.任何对字符串的修改都是新建实例下面我们一起来看看字符串拼接的几种方式吧!
‘+’ 操作符
如果是字面值连接,编译器会直接优化代码:Hi jack nice ti meet you.效率会比字符串对象拼接高.
String s = "Hi jack" + "nice to meet you";
字符串对象拼接 频繁的创建对象效率低
String s1 = "Mary";
String s2 = "good morning";
//这里会创建2个对象
String str = s1 + s2;
/** 首先会创建 StringBuilder sbr = new StringBuilder(s1);
然后将s2添加到sbr中 sbr.append(s2);
在转成字符串 String str2 = sbr.toString();
*/
concat()
调用String类的concat()方法拼接字符串,并且两边的值都不能为null
String s = "Hi mary";
String concat = s.concat("Nice to meet you");
"Hello liu".concat("nica to meet you too");
System.out.println(s==concat);
System.out.println(concat);
StringBuilder/StringBuffer
String设计为不可变,实现共享,又通过只读,实现线程安全.在于Stirng的+每次都会创建字符串对象,耗费内存,效率低。所以对于频繁的拼接字符串我们使用StringBuilder/StringBuffer.
StringBuilder/StringBuffer就是用来做高效率的字符串连接.
StringBuilder/StringBuffer连接字符串的使用
//StringBuilder连接字符串
StringBuilder sbr = new StringBuilder();
sbr.append("bbb");
//StringBuffer连接字符串
StringBuffer stb = new StringBuffer();
stb.append("aaa");
StringBuilder/StringBuffer的区别
StringBuffer是线程安全的,StringBuilder是线程非安全。
测试字符串连接效率
我们在前面多次提到了’+'操作符拼接效率低,StringBuilder/StringBuffer效率高.那是不是真的呢?我们现在一起来测试一下.
@Test
public void T3() {
//声明一个字符串
String s = "";
//开始时间
Long start = System.currentTimeMillis();
//循环遍历连接100000次字符串
for (int i = 0; i < 100000; i++) {
s += "abc";
}
//结束时间
Long end = System.currentTimeMillis();
//耗时
Long sj = end - start;
//打印
System.out.println("'+'操作符连接字符串用时:"+sj);
//********************StringBuilder******************************
//声明一个字符串
StringBuilder str = new StringBuilder();
//开始时间
Long start1 = System.currentTimeMillis();
//循环遍历连接100000次字符串
for (int i = 0; i < 100000; i++) {
str.append("abc");
}
//结束时间
Long end1 = System.currentTimeMillis();
//耗时
Long sj1 = end1 - start1;
//打印
System.out.println("StringBuilder连接字符串用时:"+sj1);
//************************StringBuffer******************************
//声明一个字符串
StringBuffer sbf = new StringBuffer();
//开始时间
Long start2 = System.currentTimeMillis();
//循环遍历连接100000次字符串
for (int i = 0; i < 100000; i++) {
sbf.append("abc");
}
//结束时间
Long end2 = System.currentTimeMillis();
//耗时
Long sj2 = end2 - start2;
//打印
System.out.println("StringBUffer连接字符串用时:"+sj2);
}
运行结果
从上面代码的运行结果我们可以看到,’+‘操作符拼接100000次字符串的耗时12951毫秒,StringBuilder耗时3毫秒,StringBuffer耗时3毫秒.明显可以看出StringBuilder和StringBuffer在拼接字符串的效率上要比’+'操作符高,所以我们说StringBuilder和StringBuffer常用来做高效率的字符串连接.
[^1]文章知识仅是个人理解,若有不同可相互讨论.