>[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
}
}
~~~
- windows -- 环境变量
- Vscode -- 编写java
- 初始java
- java -- 关键字
- 编写第一个java程序
- java -- 注释
- 计算机存储 -- 进制
- java -- 类型
- java -- 变量
- 数字类型
- 布尔类型
- 字符类型
- 类型转换
- 双等比较是什么
- java -- 运算符
- 算数运算符
- 字符串拼接
- 关系/比较运算符
- 自增减运算符
- 逻辑运算符
- 三目运算
- 赋值运算符
- 移位运算符
- 位运算符
- 运算符优先级
- java -- 流程控制语句
- if /else if /if -- 判断
- switch case分支结构
- for -- 循环
- 用双重for循环
- while -- 循环
- do while -- 循环
- 案例练习
- java -- 数组
- 数组的存储
- 数组的增删改查
- 数组的特点
- 数组案例
- 二维数组
- 数组的工具方法
- java -- 方法
- java -- 方法的重载
- java -- 方法的调用流程
- java -- 类方法传参注意事项
- java -- 方法练习案例
- 对比 return break continue
- for each循环
- java -- 基础练习
- java -- 面向对象
- java -- 创建类和对象
- java -- 访问控制符
- java -- 类成员方法
- java -- 构造方法
- java -- this
- java -- 封装
- java -- 对象内存图
- java -- 创建对象案例
- java -- static
- java -- 继承
- super -- 关键字
- java -- 构造块和静态代码块
- java -- 重写
- java -- final
- java -- 多态
- java -- 抽象类
- java -- 接口
- 引用类型数据转换
- 综合案例
- java -- 内部类
- java -- 回调模式
- java -- 枚举类型
- java -- switch 使用枚举
- java -- 枚举方法使用
- java -- 枚举类实现接口
- java -- javaBean
- java -- package 包
- java -- import
- java -- 递归练习
- java -- 设计模式
- 单例模式
- java -- 注解
- java -- 元注解
- Java -- 核心类库
- java -- 处理字符串
- Java -- String
- String -- 常用方法
- String -- 正则
- Java -- StringBuilder 和 StringBuffer
- 知识点
- Java -- StringJoiner 字符串拼接
- 练习题
- 字符串的总结
- Java -- 包装类
- Integer
- Double
- Boolean
- Character
- java -- 集合类
- java -- util.Collection
- Iterator接口
- java -- util.List
- java -- ArrayList
- java -- util.Queue
- java -- util.Set
- java -- util.Map
- java -- util.Collections
- Java -- Math
- Java -- java.lang
- Java -- Object
- Java -- 获取当前时间戳
- Java -- 异常
- Java -- java.util
- java -- Date
- java -- Calender
- Java -- java.text
- Java -- SimpleDateFormat
- Java -- java.time
- Java -- java.io
- java -- io.File
- java -- 泛型
- IDEA -- 用法