💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
>[success] # 字符串总结 1. `String s = "abc";` 这种直接赋值字符串常量的情况,**此时字符串abc是存在字符串常量池中的**,整个创建过程**先检查字符串常量池中有没有字符串abc,如果有,不会创建新的,而是直接复用。如果没有abc,才会创建一个新的。** 2. 通过 `new` 关键字几种形式创建字符串,一定是在堆里面开辟了一个空间,`new`出来的,在**堆里面的地址** 3. 字符串的比较推荐使用`equals` 方法,因为在 `java` 中`==`号比较 * 如果比较的是**基本数据类型**:比的是具体的数值是否相等 * 如果比较的是**引用数据类型**:比的是地址值是否相等 在 `java` 中 String 是**引用类型**,在使用双等时候比较其实是内存地址,因此要注意的是比较内容使用提供的`equals` 方法 4. 关于字符串的拼接**加号浪费性能** * 如果拼接的全部都为字符串,在拼接时候会触发字符串的优化机制,**在编译转换为class 文件后就已经获取了最终值的结果** ![](https://img.kancloud.cn/31/19/3119ad9bbce8aea59113c19440c12034_897x184.png) ~~~ public class StringTest { public static void main(String[] args) { String str = "a" + "b" + "c"; // 实际在编译后class 就是"abc" } } ~~~ * 如果拼接是变量在**JDK8以前**:系统底层会自动创建一个`StringBuilder`对象,然后再调用其`append`方法完成拼接。拼接后,再调用其`toString`方法转换为`String`类型,而`toString`方法的底层是直接`new String`了一个字符串对象(可看源码实现)。也就是说**一个加号使用变量拼接字符串会创建两个对象**分别是`StringBuilder` 和 `String` ![](https://img.kancloud.cn/d9/62/d9622b3f7dc783028cf5f32a1fdc2668_1528x649.png) ~~~ public class StringTest { public static void main(String[] args) { String s1 = "a"; String s2 = "b" + s1; // 实际做了 new StringBuilder().append(s1).append("b").toString(); } } ~~~ * **JDK8版本**:系统会预估要字符串拼接之后的总大小,把要拼接的内容都放在数组中,此时也是产生一个新的字符串。(?????) ***** * 加号浪费性能案例说明method1 > method2 ,结论**字符串拼接的时候有变量参与:在内存中创建了很多对象浪费空间,时间也非常慢不要直接使用加号创建** ~~~ public static void main(String[] args) { int times = 1_0000; long startTime = System.currentTimeMillis(); method1(times); System.out.println("method1 耗时:" + (System.currentTimeMillis() - startTime)); startTime = System.currentTimeMillis(); method2(times); System.out.println("method2 耗时:" + (System.currentTimeMillis() - startTime)); } public static void method1(int times) { String s = ""; for (int i = 0; i < times; i++) { s += i; } } public static void method2(int times) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < times; i++) { sb.append(i); } String s = sb.toString(); } ~~~ 5. 在字符串的拼接时候更多应该使用`StringBuilder` 这种内容可变的容器,创建后只有一个对象在内存空间 6. [参考链接](https://www.jianshu.com/p/dc8a458d5933),`StringBuilder`这种扩容也是存在一定规则的(**容量和长度的区别,容量表示最多能装多少,长度表示实际内容**) * 默认创建一个长度为16的字节数组 * 添加的内容长度小于16,直接存 * 添加的内容大于16会扩容(原来的容量*2+2) * 如果扩容之后还不够,以实际长度为准 ~~~ public class StringTest { public static void main(String[] args) { StringBuilder sb1 = new StringBuilder(); sb1.append("abc"); System.out.println(sb1.capacity()); // 容量 16 System.out.println(sb1.length()); // 长度 3 // 添加内容大于目前的容量。那么会进行扩容公式 原始容量*2 + 2 sb1.append("defghijklmnopqrstvuwxzy"); System.out.println(sb1.capacity()); // 容量 16*2 +2 = 34 System.out.println(sb1.length()); // 长度 26 // 如果添加内容大于 实际扩容长度应该为16*2 +2 = 34,但添加的内容远远大于34 因此 // 扩容和长度值相等 StringBuilder sb2 = new StringBuilder(); sb2.append("111111111111111111111111111111111111"); System.out.println(sb2.capacity()); // 容量 36 System.out.println(sb2.length()); // 长度 36 } } ~~~ * 如果你默认有构造参数初始容量为**16 + 字符串长度** ~~~ public class StringTest { public static void main(String[] args) { StringBuilder sb1 = new StringBuilder("abc"); sb1.append("abc"); System.out.println(sb1.capacity()); // 容量 19 System.out.println(sb1.length()); // 长度 6 } } ~~~ >[danger] ##### 面试题 * 因为 s3 在拼接后实际是产生新对象在堆中,但`s1` 实际在字符串常量池中,因此比较后地址值不同 ~~~ public class StringTest { public static void main(String[] args) { String s1 = "abc"; String s2 = "ab"; String s3 = s2 + "c"; System.out.println(s1 == s3); // false } } ~~~ * s2 转换为class 文件后实际 "abc",s1 已经在常量池中创建了,因此s2直接复用即可,所以内存地址指向相同 ~~~ public class StringTest { public static void main(String[] args) { String s1 = "abc"; String s2 = "a" + "b" + "c"; // 转换为class 文件后实际 "abc" System.out.println(s1 == s2); // true } } ~~~