JAVA字符串,以及字符串的优化相关

JAVA学习网 2019-01-31 20:42:02
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 = new StringBuffer();
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 = new StringBuilder();
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()方法转换。
 
 
阅读(2714) 评论(0)