Java中的String类绝对是使用最广泛的类,本文就带着大家细读一下JDK8下的String源码,主要包含以下几个方面的内容:
一.String不可变特性
二.String核心字段分析
三.String常见方法分析
四.String在Jdk1.6和1.8版本的差异
编辑
一.String的不可变特性
1.什么是不可变对象?
如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。
2.String设计成不可变的好处
A.只有当字符串是不可变的,字符串常量池才有可能实现,才能把字面值相同的字符串都指向常量池中的同一地址空间,达到节约堆内存的目的;
B.字符串是不可变的,必然是线程安全的,这样同一个字符串实例就可以被多个线程安全地共享,而且不需要任何同步操作;
C.字符串是不可变的,才能缓存hash的值,避免重复计算,使得String作为HashMap的Key,具有很好的性能。
3.String类如何实现不可变
1.String类定义使用final修饰,不能被继承,这样String的所有方法就不能被重写;
2.存储字符串数据的字段声明为private final char value[],使用private final修饰,value自身的值初始化后不能被修改,也不能被外部访问;
3.String的构造函数,通过拷贝等方式,保证String内部value字段不能被外部访问;
4.任何需要改变String内容的方法,都创建了一个新的String对象,而原对象保持不变,如substring,trim,toUpperCase等;
5.获取value的方法,都返回了value的一个拷贝,如toCharArray。
4.String类不可变性破坏
虽然value字段被private修饰,但是还是能通过反射的方式获取并进行修改,代码如下:
代码语言:javascript复制private static void main() throws IllegalAccessException, NoSuchFieldException { String a = "1"; Field valueField = String.class.getDeclaredField("value"); System.out.println(a); valueField.setAccessible(true); char[] value = (char[]) valueField.get(a); value[0] = '0'; System.out.println(a);}
通过上面代码,就把变量a的值由1变成0。
二.String的核心字段
1.value
value的定义为private final char value[],存储字符串中的每一个字符,并保证不被外部访问。
2.hash
hash的定义为private int hash,存储的是字符串的hashCode的值,与hash关联的有两个问题:
A.为什么要存储hash值?
因为字符串的hashCode方法计算非常耗时,时间复杂度是O(N),如果每一次用到,都需要计算,严重影响性能,所以有必要缓存hash的值;
2.为什么hash字段没有使用final修饰?
如果使用final修饰,hash的值必须在对象初始化完成之前完成赋值,而hash的值的计算又很耗时,对于那些不需要使用hash的字符串,计算好了又不使用,会造成CPU的浪费。所以这里就采用延迟计算的策略,默认值为0,需要使用的时候再计算,要达到这样目的hash字段就不能被final修饰。
三.String常见方法分析
1.String加法
Java中不容许操作符重载,所以String的加法是通过语法糖(编译手段)来实现的,具体分为以下两种情况:
A. 执行相加操作时,结果值在编译期确定,就直接使用相加后的结果进行替换,这样就消除加法操作。举例说明:
例1:
代码语言:javascript复制public static void main(String[] args) { String value = "a" "b"; System.out.println(value);}
因为字符串"a"和"b"都是字面常量,value的值在编译的时候就已经确定,值为"ab",所以直接把"ab"赋值给了value。我们反编译上面代码生成的class文件,得到的等价代码如下:
代码语言:javascript复制public static void main(String[] args) { String value = "ab"; System.out.println(value);}
例2:
代码语言:javascript复制public static void main(String[] args) { final String a = "a"; final String b = "b"; String value = a b; System.out.println(value);}
因为变量a和b都使用final修饰,值为字面常量,编译期确定,所有在编译期确定a b的值就是就是"ab",就直接把"ab"赋值给了value,我们反编译上面代码生成的class文件,等价代码如下:
代码语言:javascript复制public static void main(String[] args) { String a = "a"; String b = "b"; String value = "ab"; System.out.println(value);}
B. 执行相加操作时,结果值在编译期不能确定,则使用StringBuilder进行拼接,举例说明:
例3:
代码语言:javascript复制public static void main(String[] args) { String a = "a"; String b = "b"; String c = "c"; String value = a b c; System.out.println(value);}
因为a,b,c三个变量,没有final修饰,值可能在运行的过程中被修改,所以value的值就是编译期不定,编译的时候转换为使用StringBuilder进行拼接,反编译后等价代码如下:
代码语言:javascript复制public static void main(String[] args) { String a = "a"; String b = "b"; String c = "c"; String value = new StringBuilder().append(a).append(b).append(c).toString(); System.out.println(value);}
例4:
代码语言:javascript复制public static void main(String[] args) { final String a = String.valueOf(System.currentTimeMillis()); final String b = String.valueOf(System.currentTimeMillis()); String value = a b; System.out.println(value);}
虽然变量a和b使用了final修饰,但是a和b的值在编译期不确定,所以value的值也是不定的,最后只能使用StringBuilder进行拼接,反编译后等价代码如下:
代码语言:javascript复制public static void main6(String[] args) { final String a = String.valueOf(System.currentTimeMillis()); final String b = String.valueOf(System.currentTimeMillis()); String value = new StringBuilder().append(a).append(b).toString(); System.out.println(value);}
说明一下,String加法在编译的时候,究竟是采用用结果值直接替换还是使用StringBuilder进行拼接,跟进行相加的变量是否使用final修饰没有直接关系,唯一的判断依据就是编译期加法结果的值是否确定,确定就替换,不确定就拼接。
例5:
代码语言:javascript复制public static void main(String[] args) { String a = ""; for (int i = 0; i < 26; i ) { a = (char) ('A' i); } System.out.println(a);}
这个例子用来说明低效使用String加法运算,此过程会频繁的创建String和StringBuilder对象,所以一定避免在循环中使用加号对字符串进行拼接,上面代码反编译后等价代码如下:
代码语言:javascript复制public static void main(String[] args) { String a = ""; for (int i = 0; i < 26; i ) { a = new StringBuilder().append(a).append((char) ('A' i)).toString(); } System.out.println(a);}
效果更高的写法弃用加法,直接使用StringBuilder进行字符串拼接,只创建一个StringBuilder对象,只调用一次builder.toString()方法,代码如下:
代码语言:javascript复制public static void main(String[] args) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < 26; i ) { builder.append((char) ('A' i)); } String value = builder.toString(); System.out.println(value);}