java基础(3)--详解String
其实与八大基本数据类型一样,String也是我们日常中使用非常频繁的对象,但知其然更要知其所以然,现在就去阅读源码深入了解一下String类对象,并解决一些我由来已久的疑问。
为什么String是不可变序列
- String类使用final修饰的,所以不可以通过继承方式来修改String。
- String类字符串存储是使用final修饰的value数组实现的,所以不可以通过修改value数组来修改String。但是数组其实也是一个引用,那能不能通过修改引用来达到修改String的目的?
下面来看个例子:
public class Test03 { public static void main(String[] args) { char[] arr = new char[]{'a', 'b', 'c', 'd'}; String s = new String(arr); arr[0] = 'b'; System.out.println(s); } }
abcd
那为什么不能通过修改引用来修改String呢,看看源码
public String(char value[]) { this.value = Arrays.copyOf(value, value.length); }
String通过拷贝数组保证了本身不会被修改
- String类的方法返回的字符串只要不是原字符串就会new一个对象。
public String substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); }
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); }
还有等等......
String类用"=="进行比较的问题
看下面一段代码
public class Test03 { public static void main(String[] args) { String s1 = "helloWorld"; String s2 = "hello" + "World"; String s3 = "helloWorld"; String s4 = "hello"; String s5 = "World"; final String s6 = "World"; String s7 = s4 + s5; String s8 = "hello" + s6; String str1 = new String("helloWorld"); String str2 = new String("helloWorld"); System.out.println(s1 == s2); System.out.println(s1 == s3); System.out.println(s1 == s7); System.out.println(s1 == s8); System.out.println(str1 == str2); System.out.println(str2 == s1); } }
true true false true false false
一共进行了5次比较,现在一一道来
- 第一次和第二次之所以都为true,是因为两者的引用都指向了字符串常量池中的"helloWorld",虚拟机会在编译期间进行优化,字面常量相”+“会直接进行拼接而不是调用其他方法。
- 当字符串拼接存在不是字符常量而是一个变量的时候,虚拟机在编译期间不会将其进行优化,而是用会在堆上生成一个对象,所以第三个比较是false;但如果String对象使用final修饰,那么他就是不可修改的常量,这时等同于是字符常量拼接,虚拟机才会无顾忌的将其优化,所以第四个比较为true。
- 最后一部分对象的比较就比较容易了,由于是在堆上分配内存的,所以内容是相等的,但是地址引用并不相等,所以都为false。
StringBuilder和StringBuffer
由于String本身不可修改,所以Java提供了StirngBuiler和StringBuffer来弥补这一空缺。现在String之间使用”+“的使用的时候实际上是调用StringBuilder的append方法完成的。
先看一个例子
public class Test03 { public static void main(String[] args) { String s = "hello"; for (int i = 0; i < 100; i++){ s += "hello"; } } }
再看看反编译
Compiled from "Test03.java" public class base.Test03 { public base.Test03(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #2 // String hello 2: astore_1 3: iconst_0 4: istore_2 5: iload_2 6: bipush 100 8: if_icmpge 37 11: new #3 // class java/lang/StringBuilder 14: dup 15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 18: aload_1 19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 22: ldc #2 // String hello 24: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 27: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 30: astore_1 31: iinc 2, 1 34: goto 5 37: return }
可以看出实际上在进行字符串拼接的时候,是调用了append方法进行拼接,再用toString方法返回String对象。
StringBuilder和StringBuffer的不同
StringBuilder是线程不安全的,但是速度快,StringBuffer是线程安全的,他的方法都是用synchronized修饰的,但是速度慢
String,StringBuilder,StringBuffer的效率比较
下面用代码验证一下
String
public class Test03 { public static void main(String[] args) { Long start = System.currentTimeMillis(); String s = "hello"; for (int i = 0; i < 10000; i++) { s += "world"; } System.out.println(System.currentTimeMillis() - start); } }
713
StringBuilder
public class Test03 { public static void main(String[] args) { Long start = System.currentTimeMillis(); StringBuilder s = new StringBuilder("hello"); for (int i = 0; i < 10000; i++) { s = s.append("world"); } System.out.println(System.currentTimeMillis() - start); } }
2
StringBuffer
public class Test03 { public static void main(String[] args) { Long start = System.currentTimeMillis(); StringBuffer s = new StringBuffer("hello"); for (int i = 0; i < 10000; i++) { s = s.append("world"); } System.out.println(System.currentTimeMillis() - start); } }
6
显然StringBuilder > StringBuffer > String;但是在相加字符串较少的时候还是用String效率高点。