一、String类简介
String类位于java.lang包下,是Java语言的核心类,提供了字符串比较、查找、截取、大小写转换等操作,可以用“+”连接其他对象,String类部分源码如下:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
1.String类被final关键字修饰,意味着String类不能被继承,字符串一旦创建就不能再修改。
2.String类实现了Serializable、Comparable和CharSequence接口。
3.String的值是通过字符数组实现字符串存储的。
二、“+”连接符
字符串连接是通过StringBuilder或StringBuffer类及其append方法实现的,对象转换为字符串是通过toString方法实现的,该方法由Object类定义,并可被Java中所有类继承。
使用“+”连接符时,JVM会隐式的创建StringBuilder对象,这种方式在大部分情况下不会造成效率的损失,但是,在循环中进行字符串拼接时就不一样了。
因为会创建大量的StringBuilder对象在堆内存中,这肯定是不允许的,所以这时就建议在循环外创建一个StringBuilder对象,然后循环内调用append方法进行手动拼接。
还有一种特殊情况,如果“+”拼接的是字符串常量中的字符串,编译器会进行优化,直接将两个字符串常量拼接好。
所以,“+”连接符对于直接相加的字符串常量效率很高,因为在编译期间便确定了它的值;但对于间接相加的情况效率就会变低,建议单线程时使用StringBuilder,多线程时使用StringBuffer替代。
三、String、StringBuilder和StringBuffer
1.主要区别
String是不可变的字符序列,StringBuffer和StringBuilder是可变字符序列。
执行速度StringBuilder>StringBuffer>String。
StringBuilder是非线程安全的,StringBuffer是线程安全的。
2.测试
package day01;
/**
* @author qx
* @date 2023/10/19
* @des String、StringBuilder、StringBuffer测试
*/
public class Test1 {
private static void test01() {
long start = System.currentTimeMillis();
String str = "hello";
String result = "";
for (int i = 0; i < 100000; i++) {
result += str;
}
long end = System.currentTimeMillis();
System.out.println("+号拼接10万条数据耗时:" + (end - start) + "ms");
}
private static void test02() {
long start = System.currentTimeMillis();
String str = "hello";
StringBuilder result = new StringBuilder();
for (int i = 0; i < 100000; i++) {
result.append(str);
}
long end = System.currentTimeMillis();
System.out.println("StringBuilder拼接10万条数据耗时:" + (end - start) + "ms");
}
private static void test03() {
long start = System.currentTimeMillis();
String str = "hello";
StringBuffer result = new StringBuffer();
for (int i = 0; i < 100000; i++) {
result.append(str);
}
long end = System.currentTimeMillis();
System.out.println("StringBuffer拼接10万条数据耗时:" + (end - start) + "ms");
}
public static void main(String[] args) {
test01();
test02();
test03();
}
}
执行结果:
+号拼接10万条数据耗时:29009ms
StringBuilder拼接10万条数据耗时:5ms
StringBuffer拼接10万条数据耗时:6ms
总结:不考虑多线程的情况下,StringBuilder效率最高,+拼接效率最低。考虑多线程的情况下,建议使用StringBuffer。
四、字符串常量池
常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。它包括了关于类,方法、接口等中的常量,也包括字符串常量,如String s= "java"这种声明方式,当然也可以扩充,执行器产生的常量也会放入常量池,故认为常量池是JVM的一块特殊的内存空间。
字符串的对象分配:
字符串的对象分配需要消耗大量的时间和空间,而且字符串的使用频率非常高,JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一系列的优化,那就是字符串常量池。JVM会先检查字符串常量池,如果已经存在就直接返回字符串常量池中的实例引用,如果不存在就会实例化该字符串将其放到字符串常量池中。由于String的不可变性,常量池一定不会存在两个相同的字符串。
intern()方法
直接使用双引号声明出来的String对象会直接存储在字符串常量池中,如果不是用双引号声明的String对象,可以使用String提供的intern方法。它是一个native方法,intern方法会从字符串常量池中查询当前字符串是否存在,如果存在就直接返回当前字符串,如果不存在就会将当前字符串放入常量池中,之后再返回。
package day01;
/**
* @author qx
* @date 2023/10/19
* @des String中intern()方法
*/
public class StringTest {
public static void main(String[] args) {
String s1 = new String("CSDN") + new String("qx");
String s2 = new String("CS") + new String("DNqx");
// 执行s1.intern()会在字符串常量池中新建一个引用"CSDNqx",该引用指向s1在堆中的地址,并新建一个引用s3指向字符串常量池中的"CSDNqx"
String s3 = s1.intern();
// 执行s2.intern()不会在字符串常量池中创建新的引用,因为"CSDNqx"已存在,因此只执行了新建一个引用s4指向字符串常量池中"CSDNqx"的操作
String s4 = s2.intern();
// s3和s4指向的都是字符串常量池中的"CSDNqx",而这个"CSDNqx"都指向堆中s1的地址
System.out.println(s1 == s3); // true
System.out.println(s1 == s4); // true
// s3和s4最终关联堆中的地址是对象s1
System.out.println(s2 == s3); // false
System.out.println(s2 == s4); //false
}
}