String类型,是Java对char数组的进一步封装,占8个字节
String类的实现主要由三部分组成:char数组,offset偏移量,String的长度。
String类型有三个基本特点:
1、不可变性
不变性是指String对象一旦生成,则不能再对它进行改变。 在一个对象被多线程共享,而且被频繁的访问时,可以省略同步和锁的时间,从而提高性能。而String的不变性,可泛化为不变模式。
不变性的作用在于当一个对象需要被多线程共享,并且频繁访问时,可以省略同步和锁等待的时间,从而大幅提高系统性能。
2、针对常量池的优化
当两个String对象拥有相同的值时,它们只引用常量池中的同一个拷贝。
例如:
1 String s1 = "123";2 String s2 = "123";3 String s3 = new String("123");4 System.out.println(s1 == s2); //true
5 System.out.println(s1 == s3); //false
6 System.out.println(s1 == s3.intern()); //true
以上代码中,s1和s2引用的是相同的地址,故而第s1==s2是true;
而s3虽然与s1,s2相等,但是s3时通过new String(“123”)创建的,重新开辟了内存空间,因引用的地址不同,
所以第s1==s3是false;intern方法返回的是String对象在常量池中的引用,所以s1 == s3.intern()是true。
3、被final修饰过
作为final类的String对象在系统中不能有任何子类,正是因为这个,才保证了不可变。
如下图,给一个已有字符串"abcd"第二次赋值成"abcedl",不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。
为什么要被final修饰?
1、不可变性支持线程安全。
在并发场景下,多个线程同时读一个资源,是不会引发竟态条件的。只有对资源做写操作才有危险。不可变对象不能被写,所以线程安全。
2、不可变性支持字符串常量池
这样在大量使用字符串的情况下,可以节省内存空间,提高效率。但之所以能实现这个特性,String的不可变性是最基本的一个必要条件。要是内存里字符串内容能改来改去,这么做就完全没有意义。
1 String one = "someString";2 String two = "someString";
由于内存里面指向的是同一个地址,所以one==two 是true。
2、字符串操作中的常见优化方法
2.1 split()方法优化
通常情况下,split()方法带给我们很大的方便,但是其性能不是很好。建议结合使用
indexOf()和subString()方法进行自定义拆分,这样性能会有显著的提高。
2.2 String常亮的累加操作优化方法
示例代码:
1 String str = "";2 long strBeginTime =System.currentTimeMillis();3 for (int i = 0; i < 100000; i++) {4 str += "s";5 }6 long strEndTime =System.currentTimeMillis();7 System.out.println("str拼接100000遍s耗时: " + (strEndTime - strBeginTime) + "ms");8
9 StringBuffer str1 = newStringBuffer();10 long str1BeginTime =System.currentTimeMillis();11 for (int i = 0; i < 100000; i++) {12 str1.append("s");13 }14 long str1EndTime =System.currentTimeMillis();15 System.out.println("str1拼接100000遍s耗时: " + (str1EndTime - str1BeginTime) + "ms");16
17 StringBuilder str2 = newStringBuilder();18 long str2BeginTime =System.currentTimeMillis();19 for (int i = 0; i < 100000; i++) {20 str2.append("s");21 }22 long str2EndTime =System.currentTimeMillis();23 System.out.println("str2拼接100000遍s耗时: " + (str2EndTime - str2BeginTime) + "ms");
结果:
str拼接100000遍s耗时: 3465ms
str1拼接100000遍s耗时: 7ms
str2拼接100000遍s耗时: 4ms
所以,使用+号拼接字符串,其效率明显较低,而使用StringBuffer和StringBuilder的
append()方法进行拼接,效率是使用+号拼接方式的百倍甚至千倍,而StringBuffer的效率
比StringBuilder低些,这是由于StringBuffer实现了线程安全,效率较低也是不可避免的。
所以在字符串的累加操作中,建议结合线程问题选择,应避免使用+号拼接字符串。
2.3 StringBuffer和StringBuilder的选择
上例中也使用过StringBuffer和StringBuilder了,两者只有线程安全方面的差别,所以呢,在无需考虑线程安全的情况下,建议使用性能相对较高的StringBuilder类,若系统要求线程安全,就选择StringBuffer类。
2.4 基本数据类型转化为String类型的优化方案
示例代码:
1 Integer num = 0;2 int count = 100000;3 long beginTime =System.currentTimeMillis();4 for (int i = 0; i < count; i++) {5 String s = num + "";6 }7 long endTime =System.currentTimeMillis();8 System.out.println("+号拼接的方式耗时: " + (endTime - beginTime) + "ms");9
10 beginTime =System.currentTimeMillis();11 for (int i = 0; i < count; i++) {12 String s =String.valueOf(num);13 }14 endTime =System.currentTimeMillis();15 System.out.println("String.valueOf()的方式耗时: " + (endTime - beginTime) + "ms");16
17 beginTime =System.currentTimeMillis();18 for (int i = 0; i < count; i++) {19 String s =num.toString();20 }21 endTime =System.currentTimeMillis();22 System.out.println("toString()的方式耗时: " + (endTime - beginTime) + "ms");
结果:
+号拼接的方式耗时: 30ms
String.valueOf()的方式耗时: 6ms
toString()的方式耗时: 5ms
以上示例中
String.valueOf()直接调用了底层的Integer.toString()方法,会先判空;
+号拼接由StringBuilder实现,先调用了append()方法,然后调用了toString()方法获取字符串;
num.toString()直接调用了Integer.toString()方法,
所以效率是:
num.toString()方法最快,
其次是String.valueOf(num),最后是加号拼接的方式。
避免使用加号拼接的方式转换,最好是使用基本数据类型自带的toString()方法转换。