1. 字符串概述
- 概述
- String 类定义的变量可以用于存储字符串,同时还提供了很多操作字符串的功能。
- String 类属于
java.lang
包。
- 特点
String 常被称为不可变字符串类型,它的对象在创建后不能被更改。
2. 字符串对象的创建
2.1. 双引号创建
- 说明
直接使用""
定义,在字符串常量池中存储,而且相同内容只会在其中存储一份。
- 快速入门
public class Test {
public static void main(String[] args) {
String str1 = "Black";
String str2 = "Black";
// 加载 str2 变量时,去字符串常量池创建是发现 "Black" 字符串存在,则不创建,直接赋地址。
System.out.println(System.identityHashCode(str1));
System.out.println(System.identityHashCode(str2));
System.out.println(str1 == str2);
str1 += "Pink";
// 字符串加运算验证
System.out.println(System.identityHashCode(str1));
System.out.println(str1);
}
}
- 补充
""
创建字符串变量时,栈内存中分配一个字符串变量,在堆内存中的字符串常量池中保存字符串对象,地址赋给字符串变量。
进行加运算时,新运算的对象(BlackPink
)放在堆内存中,然后 str1
指向新对象(地址赋值)。原来的字符串对象(Black
)没有发生改变,仍然在常量池中。
2.2. 构造函数创建
- 说明
通过 String 类的构造器创建对象,每 new 一次都会产生一个新对象,放在堆内存中。
- 快速入门
public class Test_02 {
public static void main(String[] args) throws UnsupportedEncodingException {
// 创建一个空白字符串对象,不含有任何内容
System.out.println(new String());
// 根据传入的字符串内容,来创建字符串对象
System.out.println(new String("BlackPink"));
// 根据字符数组的内容,来创建字符串对象
System.out.println(new String(
(new char[]{'B','l','a','c','k','P','i','n','k',})));
// 根据字节数组的内容,来创建字符串对象,可以指定编码
System.out.println(new String(
new byte[]{-60, -29, -70, -61, -93, -84, -42, -48, -71, -6},"GBK"));
// 探究具体细节
char[] chs = {'a', 'b', 'c'};
String s3 = new String(chs);
String s4 = new String(chs);
System.out.println(System.identityHashCode(s3));
System.out.println(System.identityHashCode(s4));
System.out.println(s3 == s4);
}
}
- 补充
Java 存在编译优化机制,程序在编译时“a” + “b” + “c”
会直接转成 abc 。
每 new 一次,出来的都是新对象,地址不一样
3. 字符串常用方法
3.1. 通用方法
- API
// 返回此字符串的长度
length();
// 获取某个索引位置处的字符
charAt(int index);
// 将当前字符串转换成字符数组返回
toCharArray();
// 根据开始和结束索引进行截取,得到新的字符串(包前不包后)
substring(int beginIndex, int endIndex);
// 从传入的索引处截取,截取到末尾,得到新的字符串
substring(int beginIndex);
// 使用新值,将字符串中的旧值替换,得到新的字符串
replace(A,B);
// 根据传入的规则切割字符串,得到字符串数组返回
split(String regex);
// 判断是否含有这个字符
contains(CharSequence s);
// 判断字符串首字母
startsWith(String prefix);
// 删除字符串的头尾空白符
trim();
- 注意事项
字符串的内容比较不可以使用 ==
,因为键盘输入的数据在堆内存,字符串对象在字符串常量池,地址不一样。
一般使用==
比较基本数据类型,String 提供了equlas
方法,只关心内容一样就返回true
。
3.2. 字符串编码与解码
- 常用方法
// 使用默认字符集将该 String 字符编码为一系列字节,将结果存储到新的字节数组中
byte[] getBytes();
// 使用指定字符集将该 String 编码为一系列字节,将结果存储到新的字节数组中
byte[] getBytes(String charsetName);
// 使用默认字符集解码指定的字节数组来构造新的 String
String(byte[] bytes);
// 使用指定字符集解码指定的字节数组来构造新的 String
String(byte[] bytes, String charsetName);
- 快速入门
public class Test {
public static void main(String[] args) throws Exception {
// 编码:把文字转换成字节(使用指定的编码)
String name = "我爱你中国";
byte[] defaultBytes = name.getBytes();
byte[] gbkBytes = name.getBytes("GBK");
System.out.println(defaultBytes.length);
System.out.println(Arrays.toString(defaultBytes));
System.out.println();
System.out.println(gbkBytes.length);
System.out.println(Arrays.toString(gbkBytes));
// 解码:把字节转换成对应的中文形式(编码前和编码后的字符集必须一致,否则乱码)
System.out.println(new String(defaultBytes));
System.out.println(new String(gbkBytes, "GBK"));
System.out.println(new String(defaultBytes, "GBK"));
}
}
4. 字符串拓展
4.1. 探究 String 的不可变性
- 什么是不可变的对象?
- 不可变对象是指如果一个对象在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。
- 不能改变状态的意思是不能改变对象内的成员变量,包括:
-
- 基本数据类型的值不能改变。
- 引用类型的变量不能指向其他的对象。
- 引用类型指向的对象的状态也不能改变。
- String 为什么不可变?
从源码可以看出,String 对象其实在内部就是一个个字符,存储在 value 数组里面的。value 数组用 final 修饰,final 修饰的变量值不能被修改,因此 value 不可以指向其他对象。String 类内部所有的字段都是私有的,也就是被 private 修饰,而 String 没有对外提供修改内部状态的方法,因此 value 数组不能改变。所以String 是不可变的。
String 变量每次的修改其实都是产生并指向了新的字符串对象,原来的字符串对象都是没有改变的,所以称不可变字符串。
- 为什么 String 要设计成不可变的?
- 线程安全:同一个字符串实例可以被多个线程共享,因为字符串不可变,本身就是线程安全的。
- 支持 hash 映射和缓存:String 的 hash 值经常会使用,比如作为 Map 的键,不可变的特性使得 hash 值也不会变,不需要重新计算。
- 安全考虑:网络地址 URL 、文件路径 path 、密码通常情况下都是以 String 类型保存,假若 String 不是固定不变的,将会引起各种安全隐患。比如将密码用 String 的类型保存,那么它将一直留在内存中,直到垃圾收集器把它清除。假如 String 类不是固定不变的,那么这个密码可能会被改变,导致出现安全隐患。
- 字符串常量池优化:String 对象创建之后,会缓存到字符串常量池中,下次需要创建同样的对象时,可以直接返回缓存的引用。
- 注意事项
String 是不可变的,它内部还有很多 substring, replace, replaceAll 这些操作的方法。这些方法不会改变 String 对象,我们每次调用这些方法,其实会在堆内存中创建了一个新的对象,然后其 value 数组引用指向不同的对象。
- 参考链接
4.2. StringBuilder
4.2.1. StringBuilder 概述
- 说明
StringBuilder 是一个可变的字符串类,可以把它看成是一个对象容器 ,用来提高字符串的操作效率,如拼接、修改等操作。
StringBuilder 只是拼接字符串的手段,最后还是要恢复成 String 类型。
- 构造函数
// 创建一个空白的可变的字符串对象,不包含任何内容
public StringBuilder()
// 创建一个指定字符串内容的可变字符串对象
public StringBuilder(String str)
- 常用方法
// 添加数据并返回 StringBuilder 对象本身
public StringBuilder append(任意类型)
// 将对象的内容反转
public StringBuilder reverse()
// 返回对象内容长度
public int length()
// 把 StringBuilder 转换为 String
public String toString()
4.2.2. String 和 StringBuilder
4.3. StringJoiner
如何理解java中String的不可变性_leo825...的博客-CSDN博客
@Test
public void testString() {
}
/**
* 向字符串中添加bbb
*
* @param str
*/
public String addString(String str) {
str = str + "bbb";
return str;
}
/**
* 向字符串中追加bbb
*
* @param str
*/
public StringBuilder addStringBuilder(StringBuilder str) {
str.append("bbb");
return str;
}
4.4. String 和 StringBuilder
聊聊StringBuffer,StringBuilder和String拼接字符串到底谁更快?stringbuffer连接字符串速度没有string 快向上的狼的博客-CSDN博客
4.5. String 和 StringBuilder
- 为什么拼接、反转字符串建议使用StringBuilder?
-
- String :内容是不可变的、拼接字符串性能差,浪费内存
- StringBuilder:内容是可变的、拼接字符串性能好、代码优雅。
- 定义字符串使用String,拼接、修改等操作字符串使用StringBuilder
4.5.1. String 类拼接字符串原理
进行 s1 + "b"
时,会在堆内存创建新的对象StringBuilder
进行加法,然后创建新的对象s2
,通过toString
转成String
类型.
4.5.2. StringBuilder提高效率原理
只会创建一个StringBuilder
对象,提高效率
4.6. String、StringBuffer、Stringbuilder 区别
表格比较
String | StringBuffer | StringBuilder | |
拼接速度 | 最差 | 其次 | 最高 |
线程安全 | 线程安全 | 线程安全 | 线程不安全 |
使用场景 | 少量字符串操作 | 多线程环境下的大量操作 | 单线程环境下的大量操作 |
概念
- String 代表一个不可变类,也就是说一个 String 对象创建之后,直到这个对象销毁为止,对象中的字符序列都不能被改变。
- StringBuffer:代表一个字符序列可变的字符串,当一个 StringBuffer 对象被创建之后,我们可以通过 StringBuffer 提供的 API 来改变这个字符串对象的字符序列。也可以通过 toString 将其转换为 String 对象。
- StringBuilder:JDK1.5 中新增的类,也代表可变的字符序列。
性能分析
- String 是不可变的对象,因此在每次对 String 类型进行改变的时候,会在堆内存创建新的对象 StringBuilder 并进行运算,然后创建一个新的对象,最后通过 toString 转成 String 对象。所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响。
- StringBuffer、Stringbuilder 只会创建一个对象,故每次结果都会对 StringBuffer(Stringbuilder) 对象本身进行操作。
- StringBuffer、Stringbuilder 有共同的父类 AbstractStringBuilder ,二者无论是构造器还是方法都基本相同。但 StringBuilder 没有考虑线程安全问题,Stringbuffer 的方法都有 synchronized 修饰,故所有方法均要保证同步,所以 StringBuilder 比 StringBuffer 性能略高。
最佳实践
- 如果是在单线程下操作大量数据,应优先使用 StringBuilder 类。
- 如果是在多线程下操作大量数据,应优先使用 StringBuffer 类。