1.重要特征:
String 字符串常量---》是不可改变的量,在字符串不经常变化的场景中可以使用 String 类,例如常量的声明、少量的变量运算。
StringBuffer 字符串变量(线程安全)---》运行在多线程环境,且频繁进行字符串运算(如拼接、替换、删除等),则可以考虑使用 StringBuffer,
例如 XML 解析、HTTP 参数解析和封装。
StringBuilder 字符串变量(非线程安全)---》运行在单线程的环境中,且频繁进行字符串运算(如拼接、替换、和删除等),则可以考虑使用 StringBuilder,
如 SQL 语句的拼装、JSON 封装等。
1.2创建字符串的三种方式:
使用new关键字创建字符串,比如String s1 = new String("abc");
直接指定。比如String s2 = "abc";-----》使用直接指定或者使用纯字符串串联来创建String对象,则仅仅会检查维护String池中的字符串,
池中没有就在池中创建一个,有则罢了!但绝不会在堆栈区再去创建该String对象。
使用串联生成新的字符串。比如String s3 = "ab" + "c";
原理1:当使用任何方式来创建一个字符串对象s=X时,Java运行时(运行中JVM)会拿着这个X在String池中找是否存在内容相同的字符串对象,如果不存在,则在池中创建一个字符串s,否则,不在池中添加。
原理2:Java中,只要使用new关键字来创建对象,则一定会(在堆区或栈区)创建一个新的对象。
原理3:使用直接指定或者使用纯字符串串联来创建String对象,则仅仅会检查维护String池中的字符串,池中没有就在池中创建一个,有则罢了!但绝不会在堆栈区再去创建该String对象。
原理4:使用包含变量的表达式来创建String对象,则不仅会检查维护String池,而且还会在堆栈区创建一个String对象。
2.String特性【不能被继承】:
String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法
String类其实是通过【本质】char数组来保存字符串的【实现了charsequence接口】
对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象【如concat sub replace】
2.2JVM常量池:
常量池中的数据是那些在编译期间被确定,并被保存在已编译的.class文件中的一些数据。除了包含所有的8种基本数据类型
(char、byte、short、int、long、float、double、boolean)外,还有String及其数组的常量值,另外还有一些以文本形式出现的符号引用。
2.3java栈【数据共享】:
栈的特点是存取速度快(比堆块),但是空间小,数据生命周期固定,只能生存到方法结束。
【例子】:如boolean b = true、char c = ‘c’、String str = “123”
true、c、123,这些等号右边的指的是编译期间可以被确定的内容,都被维护在常量池中
b、c、str这些等号左边第一个出现的指的是一个引用,引用的内容是等号右边数据在常量池中的地址
boolean、char、String这些是引用的类型
2.4String【intern()】及其他基本数据类型也都是一样的:先看常量池中有没有要创建的数据,有就返回数据的地址,没有就创建一个。
2.5String += “hello”或者字符串连接符 +
【会生成多个builder对象】 编译器每次碰到”+”的时候,会new一个StringBuilder出来,接着调用append方法,在调用toString方法,生成新字符串。
---》会自动被JVM优化成:StringBuilder str = new StringBuilder(string);----》多个”+“将会创建很多的StringBuilder对象
str.append(“hello”);
str.toString();
2.6String的concat()--》
通过两次字符串的拷贝,产生一个新的字符数组buf[],再根据字符数组buf[],new一个新的String对象出来
concat方法调用N次,将发生N*2次数组拷贝以及new出N个String对象,无论对于时间还是空间都是一种浪费。
2.7String重写了equals()----》==比较的是引用类型变量的地址,基本类型变量的值
equals()比较的不在是地址而是值
3.StringBuffer特性【线程安全】:
StringBuffer和StringBuilder原理一样,无非是在底层维护了一个char数组,每次append的时候就往char数组里面放字符而已,在最终sb.toString()的时候,
用一个new String()方法把char数组里面的内容都转成String。整个过程就new了一个StringBuilder对象一个String对象
3.1同StringBuilder,在append()追加,将只会创建一次对象,在原来的基础上追加
3.2都用在频繁进行字符串运算的环境中
3.3对于间接相加(即包含字符串引用),形如s1+s2+s3; 效率要比直接相( String str = "111" + "222" + "333")加低,因为在编译器不会对引用变量进行优化。
编译时优化:
【例】String a = “hello2″; String b = “hello” + 2; ------》 a==b在编译期间被优化为hello2,故运行期间指向相同的常量池地址
String a = “hello2″; String b = “hello”; String c = b + 2;----》!a==c存在引用,未能在编译期优化,生成的对象保存在堆上,地址不一致
String a = “hello2″; final String b = “hello”; String c = b + 2;-----》a==c final定义,b在编译期直接被真实值替换,地址同
String a = "hello2"; final String b = getHello(); String c = b + 2;-----》赋值通过方法调用返回,b的值在运行期间才能知道,故地址不一致
String str1 = "I"; str1 += "love"+"java";(1) str1 = str1+"love"+"java";(2)(1会进行优化,而2不会,故1的效率高)
4.StringBuilder特性【线程不安全】:
4.1StringBuilder唯一的性能损耗点在于char数组不够的时候需要进行扩容,扩容需要进行数组拷贝,一定程度上降低了效率。
优化:如果可以估计到要拼接的字符串的长度的话,尽量利用构造函数指定他们的长度。
5.String str = new String(“abc”)创建了多少个对象
5.1代码在运行期间确实只创建了一个对象,即在堆上创建了”abc”对象
5.2运行时常量池中创建了一个”abc”对象,而在代码执行过程中确实只创建了一个String对象。
5.3涉及到2个String对象
6.toString() 每个java类都会从Object类继承toString()方法。如:println()编译器会自动添加toString(),不用手动显示添加
7.null与“”的区别
13.1String s="";【分配了一个内存空间,存了一个字符串对象】
声明了一个对象实例,这个引用已经指向了一块是空字符串的内存空间,是一个实际的东西了,所以你可以对它进行任何操作
13.2String a=null;【null是未分配堆内存空间,引用未指向任何内存空间】
声明了一个空对象,对空对象做任何操作都不行的(报NullPointException),除了=和==
13.3String b;【声明一个字符串对象,但并没有分配内存】
在成员变量的定义中,String s;等同于String s=null;-----》初始化为null
而在本地变量(方法变量)的定义中,String s;不等同于String s=null;,这时要使用s必须显式地赋值【否则报异常】。
在方法中定义变量都要显示赋初值,main()方法也不例外,而在方法之外编译器会自动赋初值。
解决空字符串问题:
对于字符串空值的判定,建议使用 apache-commons-lang http://commons.apache.org/lang/api-2.5/org/apache/commons/lang/StringUtils.html
有 isBlank/isNotBlank【包含“ ”】 和 isEmpty/isNotEmpty【不包含“ ”做非空处理】 方法【对于null “”的判定相同】