文章目录
前言
字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。
一、String类
1.创建字符串
创建字符串最简单的方法:
String str = "Runoob";
其他创建字符串的方法:
public class StringDemo2 {
public static void main(String[] args) {
// 创建方法1:直接使用双引号得到字符串对象(最常用)
String s1 = "Hello World";
System.out.println("s1 = " + s1); // s1 = Hello World
// 创建方法2:public String(String): 根据传入的字符串内容,来创建字符串对象(几乎不用)
String s2 = new String("Java");
System.out.println("s2 = " + s2); // s2 = Java
// 创建方法3:public String(char[] c): 根据字符数组的内容,来创建字符串对象(几乎不用)
char[] chars = {'我', '爱', 'J', 'a', 'v', 'a'};
String s3 = new String(chars);
System.out.println("s3 = " + s3); // s3 = 我爱Java
// 创建方法4:public String(byte[] b): 根据字节数组的内容,来创建字符串对象(几乎不用)
byte[] bytes = {97, 98, 99, 65, 66, 67};
String s4 = new String(bytes);
System.out.println("s4 = " + s4); // s4 = abcABC
}
}
2.创建字符串的位置
String 创建的字符串存储在公共池中,而 new 创建的字符串对象在堆内存上:
public class StringDemo2 {
public static void main(String[] args) {
System.out.println("----创建String位置(常量池、堆内存)----");
String ss1 = "abc";
String ss2 = "abc";
// 相同内容在字符串常量池中只存储一份,所以引用地址相同
System.out.println("ss1 == ss2 = " + (ss1 == ss2)); // ss1 == ss2 = true
char[] chars1 = {'a', 'b', 'c'};
// 通过构造器new对象,每次new都会产生新对象,放在堆内存中,所以引用地址不同
String ss3 = new String(chars1);
String ss4 = new String(chars1);
System.out.println("ss3 == ss4 = " + (ss3 == ss4)); // ss3 == ss4 = false
}
}
3.String常用的API
下面是 String 类支持的方法,更多方法,可以查看API文档。
public class StringAPIOtherDemo5 {
public static void main(String[] args) {
// 1、length(): 获取字符串的长度
String name = "我爱你中国";
System.out.println("变量name的长度 = " + name.length()); // 变量name的长度 = 5
// 2、charAt(int index): 获取某个索引位置处的字符
char c = name.charAt(1); // 0我 1爱 2你 3中 4国
System.out.println("变量name索引1处的字符 = " + c); // 变量name索引1处的字符 = 爱
String s1 = "Hello";
String s2 = "hello";
String s3 = "Hello";
// 3、equals比较区分大小写
System.out.println("s1.equals(s2) = " + s1.equals(s2)); // s1.equals(s2) = false
System.out.println("s1.equals(s3) = " + s1.equals(s3)); // s1.equals(s3) = true
// 4、equalsIgnoreCase比较不区分大小写
System.out.println("s1.equalsIgnoreCase(s2) = " + s1.equalsIgnoreCase(s2)); // s1.equalsIgnoreCase(s2) = true
System.out.println("s1.equalsIgnoreCase(s3) = " + s1.equalsIgnoreCase(s3)); // s1.equalsIgnoreCase(s3) = true
// 5、hashCode返回此字符串的哈希码
System.out.println("s1.hashCode() = " + s1.hashCode()); // s1.hashCode() = 69609650
System.out.println("s2.hashCode() = " + s2.hashCode()); // s2.hashCode() = 99162322
System.out.println("s3.hashCode() = " + s3.hashCode()); // s3.hashCode() = 69609650
// 6、toCharArray():把字符串转换成字符数组
System.out.println("~~~~把字符串转换成字符数组再遍历~~~~");
char[] chars = name.toCharArray();
for (int i = 0; i < chars.length; i++) {
char ch = chars[i];
System.out.println(ch);
}
// 7、substring(int beginIndex, int endIndex) :截取内容,(包前不包后的)
System.out.println("~~~~截取字符串~~~~");
String name2 = "Java是最厉害的编程语言!";
// 0J 1a 2v 3a 4是 5最 6厉 7害 8的 9编 10程 11语 12言 13!
String rs = name2.substring(0, 9);
System.out.println(rs); // Java是最厉害的
String rs1 = name2.substring(4, 9);
System.out.println(rs1); // 是最厉害的
// 8、substring(int beginIndex):从当前索引一直截取到末尾
System.out.println("~~~~截取从中间到最后~~~~");
String rs2 = name2.substring(4);
System.out.println(rs2); // 是最厉害的编程语言!
// 9、replace(CharSequence target, CharSequence replacement):字符串替换
System.out.println("~~~~字符串替换~~~~");
String name3 = "孙悟空有金箍棒,孙悟空会法术!我好爱孙悟空";
String rs3 = name3.replace("孙悟空", "***");
System.out.println(rs3); // ***有金箍棒,***会法术!我好爱***
// 10、contains(CharSequence s):判断是否存在
System.out.println("~~~~判断是否存在~~~~");
System.out.println(name3.contains("孙悟空")); // true
System.out.println(name3.contains("猪八戒")); // false
// 11、startsWith(String prefix):判断是否由prefix开始
System.out.println("~~~~判断是否由prefix开始~~~~~");
System.out.println(name3.startsWith("孙悟空")); // true
System.out.println(name3.startsWith("孙悟空有")); // true
System.out.println(name3.startsWith("孙悟空没有")); // false
// 12、split(String s): 按照某个内容把字符串分割成字符串数组返回。
System.out.println("~~~~分割字符串~~~~");
String name4 = "王宝强,贾乃亮,陈羽凡";
String[] names = name4.split(","); // 用,将字符串进行分割
for (int i = 0; i < names.length; i++) {
System.out.println("选择了:" + names[i]);
}
}
}
编译运行结果如下:
变量name的长度 = 5
变量name索引1处的字符 = 爱
s1.equals(s2) = false
s1.equals(s3) = true
s1.equalsIgnoreCase(s2) = true
s1.equalsIgnoreCase(s3) = true
s1.hashCode() = 69609650
s2.hashCode() = 99162322
s3.hashCode() = 69609650
~~~~把字符串转换成字符数组再遍历~~~~
我
爱
你
中
国
~~~~截取字符串~~~~
Java是最厉害的
是最厉害的
~~~~截取从中间到最后~~~~
是最厉害的编程语言!
~~~~字符串替换~~~~
***有金箍棒,***会法术!我好爱***
~~~~判断是否存在~~~~
true
false
~~~~判断是否由prefix开始~~~~~
true
true
false
~~~~分割字符串~~~~
选择了:王宝强
选择了:贾乃亮
选择了:陈羽凡
4.String类不可变
在 Java 中,String 底层不可变的原因是因为在String类的实现中,使用了byte数组(byte array)来存储字符串内容,并且在创建String对象时将字符数组声明为final,从而保证了字符串内容的不可修改性。
首先看这段代码:
public class StringDemo1 {
public static void main(String[] args) {
String name = "超级";
System.out.println("name = " + name);
System.out.println("name.hashCode() = " + name.hashCode());
name += "赛亚人"; // name = name + "赛亚人"
System.out.println("name = " + name);
System.out.println("name.hashCode() = " + name.hashCode());
name += "贝吉塔"; // name = name + "赛亚人" + "贝吉塔"
System.out.println("name = " + name); // 根据地址找到内容
System.out.println("name.hashCode() = " + name.hashCode());
}
}
编译运行结果如下:
name = 超级
name.hashCode() = 1155522
name = 超级赛亚人
name.hashCode() = 99837177
name = 超级赛亚人贝吉塔
name.hashCode() = -2127590481
从运行结果可以看出,name的值的确改变了。其实不然,因为name只是一个String对象的引用,并不是String对象本身。
当运行给变量name赋值的代码之后,会先在方法区的运行时常量池创建一个String对象"超级",然后在Java栈中创建一个String对象的引用name,并让name指向"超级"。再在常量池创建String对象"超级赛亚人",再让name引用新的String对象的地址,所以每次name的哈希值都不一样。
二、拼接字符串
因为 String 类是不可变类,因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,这样不仅效率低下,而且大量浪费有限的内存空间,所以经常改变内容的字符串最好不要用 String 。因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢。
StringBuffer 和 StringBuilder 特点及使用场景:
- 应用于对字符串进行修改的时候,特别是字符串对象经常改变的情况下。
- 和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
- 由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
1.“+” 拼接字符串
因为 String 类是不可变类,因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,不经常改变字符串对象的时候,可以用 “+” 拼接字符串。
public class StringDemo {
public static void main(String[] args) {
String name = "小明";
System.out.println("name.hashCode() = " + name.hashCode()); // name.hashCode() = 756703
String name1 = "小明";
System.out.println("name1.hashCode() = " + name1.hashCode()); // name1.hashCode() = 756703
// 哈希值一样,说明引用地址一样,常量池只创建了一个对象
String s1 = "喜欢学习";
String s2 = "Java";
String rs = name + s1 + s2; // 用 "+" 拼接字符串
System.out.println("rs = " + rs); // rs = 小明喜欢学习Java
}
}
编译运行结果如下:
name.hashCode() = 756703
name1.hashCode() = 756703
rs = 小明喜欢学习Java
2.StringBuilder 拼接字符串
StringBuilder 的API方法和基本用法与 StringBuffer 一样。
StringBuilder 是可变类,任何对它所指代的字符串的改变都不会产生新的对象。
单线程操作大量数据,用 StringBuilder。
StringBuilder 并没有对方法进行加 synchronized(同步锁),所以是非线程安全的。
/**
* @ClassName: StringBuilderDemo1
* @Description: StringBuilder 拼接字符串
* 单线程操作大量数据,用StringBuilder。
* StringBuilder 并没有对方法进行加synchronized(同步锁),所以是非线程安全的。
* StringBuffer和StringBuilder这两个可变类的原理和操作基本相同。
* @author: Zh
* @date: 2024/4/8 16:41
*/
public class StringBuilderDemo1 {
public static void main(String[] args) {
// StringBuilder的字符串拼接.append
StringBuilder sb = new StringBuilder();
sb.append("a"); // 字符
sb.append(1); // 整数
sb.append(false); // 布尔类型
sb.append(3.3); // 小数
sb.append("abc"); // 字符串
System.out.println("append的应用:" + sb); // append的应用:a1false3.3abc
StringBuilder sb1 = new StringBuilder();
// 支持链式编程
sb1.append("a").append("b").append("c").append("我爱你中国");
System.out.println("append支持链式编程:" + sb1); // append支持链式编程:abc我爱你中国
// reverse反转
sb1.reverse();
System.out.println("reverse反转:" + sb1); // reverse反转:国中你爱我cba
// 字符串长度length
System.out.println("字符串长度length:" + sb1.length()); // 字符串长度length:8
// 注意:StringBuilder只是拼接字符串的手段:效率好。
// 最终的目的还是要恢复成String类型。
StringBuilder sb2 = new StringBuilder();
sb2.append("123").append("阿拉伯数字");
// check(sb2); // 报错,因为sb2不是String类型
System.out.println("拼接字符串:" + sb2); // 拼接字符串:123阿拉伯数字
// 恢复成String类型
String rs = sb2.toString();
check(rs); // String类型:123阿拉伯数字
}
/**
* 检查传入的参数是否是String类型
*
* @param abc
*/
public static void check(String abc) {
System.out.println("String类型:" + abc);
}
}
编译运行结果如下:
append的应用:a1false3.3abc
append支持链式编程:abc我爱你中国
reverse反转:国中你爱我cba
字符串长度length:8
拼接字符串:123阿拉伯数字
String类型:123阿拉伯数字
3.StringBuffer 拼接字符串
StringBuffer 的API方法和基本用法与 StringBuilder 一样。
StringBuffer 是可变类,任何对它所指代的字符串的改变都不会产生新的对象。
多线程操作大量数据,用 StringBuffer。
StringBuffer 对方法加了synchronized(同步锁)或者对调用的方法加了同步锁,所以是线程安全的。
/**
* @ClassName: StringBufferDemo1
* @Description: StringBuffer 拼接字符串
* 多线程操作大量数据,用StringBuffer。
* StringBuffer 对方法加了synchronized(同步锁)或者对调用的方法加了同步锁,所以是线程安全的。
* StringBuffer和StringBuilder这两个可变类的原理和操作基本相同。
* @author: Zh
* @date: 2024/4/8 16:56
*/
public class StringBufferDemo1 {
public static void main(String[] args) {
// 创建StringBuffer对象
StringBuffer sb = new StringBuffer("孙悟空");
// 在字符串后面追加新的字符串
sb.append("会龟派气功");
System.out.println("StringBuffer对象:" + sb); // StringBuffer对象:孙悟空会龟派气功
// 删除指定位置上的字符串,从指定的下标开始和结束,下标从0开始
// 0孙 1悟 2空 3会 4龟 5派 6气 7功
sb.delete(2, 4); // 2空 3会
System.out.println("delete删除:" + sb); // delete删除:孙悟龟派气功
// 在指定下标位置上添加指定的字符串
sb.insert(2, "123");
System.out.println("insert新增:" + sb); // insert新增:孙悟123龟派气功
// 将字符串翻转
sb.reverse();
System.out.println("reverse反转:" + sb); // reverse反转:功气派龟321悟孙
//将StringBuffer转换成String类型
String s = sb.toString();
check(s); // String类型:功气派龟321悟孙
}
/**
* 检查传入的参数是否是String类型
*
* @param abc
*/
public static void check(String abc) {
System.out.println("String类型:" + abc);
}
}
编译运行结果如下:
StringBuffer对象:孙悟空会龟派气功
delete删除:孙悟龟派气功
insert新增:孙悟123龟派气功
reverse反转:功气派龟321悟孙
String类型:功气派龟321悟孙
4.StringBuilder 和 StringBuffer 区别
- 单线程操作大量数据,用 StringBuilder。
- 多线程操作大量数据,用 StringBuffer。
- StringBuilder 并没有对方法进行加 synchronized(同步锁),所以是非线程安全的。
- StringBuffer 对方法加了synchronized(同步锁)或者对调用的方法加了同步锁,所以是线程安全的。
- 由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。
- 在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。