String类
概念:String是不可变类,即一但一个String对象被创建,包含在这个对象的字符序列是不可改变的,直至该对象被销毁。
1 //String的主要成员变量 2 private final char value[]; 3 /** 4 value指向的是一个字符串数组,字符串中的字符就是用这个value变量存储起来的,并且用final修饰,说明value一旦赋予初始值之后,value指向的地址就不能再改变了,虽然value指向的数组是可以改变的,但是String也没有提供相应的方法让我们去修改value指向的数组的元素,所以说String是一个不可变类,String对象的不可变还有一点就是对String类型的所有改变内部存储结构的操作都会new出一个新的String对象 5 */
String类是被final修饰的,不能有子类,里面的一些方法也加上了final
例子:也就是说创建"abc"字符串后,"abc" 自出生到最终死亡,不可变,不能变成"abcd",也不能变成"ab"
注意:"abc" 这是一个字符串对象,字符串在java中有优待,不需要new也是一个对象,属于String类型。
在java中随便使用双引号括起来的都是String对象。
例如:"abc","def","hello world!",这是3个String对象
在JDK当中双引号括起来的字符串,例如:"abc" "def"都是直接存储在“方法区”的“字符串常量池”当中的。
注解:在JDK1.7时,把字符串常量池移动到了堆内存中
为什么SUN公司把字符串存储在一个“字符串常量池”当中呢。因为字符串在实际的开发中使用太频繁。
为了执行效率,所以把字符串放到了方法区的字符串常量池当中
当使用Sring str = "abc";这种形式创建字符串,那么String就不会在堆内存中创建对象,"abc"存储在字符串常量池中,而str存储的是"abc"在字符串常量池中的地址
当使用String str = new String("abc");这种形式创建字符串,需要在堆内存中创建字符串对象,而在String对象在堆内存中保存的是"abc"在字符串常量池中的内存地址,而str保存的是对象在堆内存中的地址
例子:
-
第一个例子
1 String str1 = "abc"; 2 String str2 = "abc"; 3 System.out.println(str1 == str2);//true 4 //为true原因:在字符串常量池中相同的字符串只会存储一份,不会重复,而str1和2都是同一个地址
-
第二个例子
1 String str1 = "abc"; 2 String str2 = "abc"; 3 String str3 = "ab" + "c"; 4 System.out.println(str1 == str3);//true 5 //为true原因:"ab"和"c"都是字面值常量,在编译时直接编译成"abc",而"abc"在字符串常量池中存在了,就不会再创建,直接把地址赋值给str3
-
第三个例子
1 String str1 = "abc"; 2 final String s1 = "ab"; 3 final String s2 = "c"; 4 String str4 = s1 + s2; 5 System.out.println(str1 == str4);//true 6 //为true原因:s1 和s2都是常量,在编译时直接编译成"abc"
-
第四个例子
1 String str1 = "abc"; 2 String s3 = "ab"; 3 String s4 = "c"; 4 String str5 = s3 + s4; 5 System.out.println(str1 == str5);//false 6 //为false的原因:s3、s4时变量,字符串拼接时,如果有变量参与的情况下,String底层使用的是StringBuilder,所以内存地址不一样了 7 //String str5 = s3 + s4;拼接new StringBuilder(s3).append(s4).toString()
注意:频繁的字符串拼接最好不要使用 +,因为java中的字符串是不可变的,每一次拼接都会new StringBuilder拼接产生新字符串。这样会占用大量的内存。造成内存空间的浪费。
如果以后需要进行大量字符串的拼接操作,建议使用JDK中自带的:
-
java.lang.StringBuffer
-
java.lang.StringBuilder
-
-
第五个例子
1 String s5 = new String("hello"); 2 String s6 = new String("hello"); 3 //上面两行一共创建了3个对象: 4 //字符串常量池中有1个:"hello" 5 //堆内存当中有两个String对象。
通过例子,字符串对象之间的比较不能使用“==”,"=="不保险。应该调用String类的equals方法,String类已经重写了toString()和equals()方法
注解:使用equals()方法进行比较时,把已知道的放在前面,未知的放在equals()方法中,这样可以避免空指针异常
String类的构造方法
1 String s = "abc"; 2 String s = new String("abc"); 3 String s = new String(byte数组);//将整个数组转换为字符串 4 String s = new String(byte数组, 起始下标, 长度);//将数组中的一部分转换为字符串 5 String s = new String(char数组); 6 String s = new String(char数组, 起始下标, 长度); 7 ...
valueOf()方法,参数是一个对象的时候,会自动调用该对象的toString()方法
没有重写toString()方法之前打印输出的是对象内存地址
为什么输出一个引用的时候,会调用toString()方法?
通过源代码可以看出println()这个方法,先调用了String中的valueOf()方法,然后valueOf()方法调用了toString()方法
注解:在输出任何数据的时候println()都是先转换成字符串,再调用toString(),然后输出,当println()里的是对象,也是先转换为字符串,然后调用toString(),只不过调用的是对象的toString()方法,如果对象没有重写,就打印对象内存地址
StringBuffer类
含义:StringBuffer代表可变的字符序列,StringBuffer称为字符串缓冲区,实现了序列化接口
继承了AbstractStringBuilder
工作原理:预先申请一块内存,存放字符序列,如果字符序列满了,会重新改变缓存区的大小,以容纳更多的字符序列。StringBuffer是可变对象,与String最大的不同在StringBuilder中是提供了响应的方法让我们去修改value指向的数组的元素,这也是StringBuffer的字符串序列可变的原因。
JDK1.8,StringBuffer底层是一个char数组,StringBuffer默认的初始化容量是16
StringBuffer是线程安全的,StringBuffer中的每个方法都加上了synchronized锁
StringBuffer拼接字符使用的是append()方法,在字符串的末尾追加,不会新建对象
append()在进行字符串追加的时候,会调用有ensureCapacityInternal()方法判断是否需要进行扩容
而ensureCapacityInternal()方法中扩容调用的是Arrayys数组中copyOf()方法
如何优化StringBuffer的性能?
-
在创建StringBuffer的时候尽可能给定一个初始化容量。
-
最好减少底层数组的扩容次数。预估计一下,给一个大一些初始化容量。
-
关键点:给一个合适的初始化容量。可以提高程序的执行效率。
StringBuilder类
继承了AbstractStringBuilder
工作原理:和StringBuffer相同
StringBuilder不是线程安全的
JDK1.8,StringBuilder底层是一个char数组,StringBuilder默认的初始化容量是16