文章目录
🚀 一、String 类的常用方法
🌟 1.1 字符串构造
String 类提供的构造方法,常用的有以下三种:
// 使用常量串构造
String s1 = "hello";
// 直接 new 一个 String 对象
String s2 = new String("hello");
// 使用字符数组进行构造
char[] str = {'h','e','l','l','o'};
String s3 = new String(str);
❗ 注意 :
- String 是引用类型,内部存储的不是字符串本身,而是字符串在堆区中的地址。
String s1 = new String("hello");
String s2 = new String("world");
String s3 = s1;
// s1 和 s3 引用的是同一对象
- 在Java中“”引起来的也是String类型对象。
// 打印"hello"字符串(String对象)的长度
System.out.println("hello".length());
🌟 1.2 String 对象的比较
字符串的比较是是常见的操作。Java 中总共提供了 4 种方式:
1.== 比较是否引用同一个对象
注意:对于基本变量,‘ == ’比较的是变量的值;对于引用类型,比较的是引用中的地址是否相同。
int a = 10;
int b = 20;
int c = 10;
// 对于基本类型变量,==比较两个变量中存储的值是否相同
System.out.println(a == b); // false
System.out.println(a == c); // true
// 对于引用类型变量,==比较两个引用变量引用的是否为同一个对象
String s1 = new String("hello");
String s2 = new String("hello");
String s3 = new String("world");
String s4 = s1;
System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // false
System.out.println(s1 == s4); // true
- equals(String str) 方法:按照字典序比较
String类重写了父类Object中equals方法,Object中equals默认按照==比较,String重写equals方法后,按照如下规则进行比较,比如: s1.equals(s2).
public boolean equals(Object o1){
// 1. 先检测 this 和 o1 是否是同一对象的比较,如果是,返回 true。
if(this == o1){
return true;
}
// 2. 检测 o1 是否是 String 类的对象,如果是,继续比较。
if(o1 instanceof String){
// 将 o1 向下转型为 String 类
String s1 = (String)o1;
int n = this.value.length;
// 3. 检测 this 和 s1 字符串长度是否相同,相同就继续比较
if(n==s1.value.length){
char[] v1 = this.value;
char[] v2 = s1.value;
// 4. 按照字典序,从前往后逐个字符比较
int i = 0;
while(n-- != 0){
if(v1[i] != v2[i]){
return false;
}
i++;
}
// 5. 检测完毕,两字符串值相同
return true;
}
}
return false;
}
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = new String("hello");
String s3 = new String("Hello");
// s1、s2、s3引用的是三个不同对象,因此==比较结果全部为false
System.out.println(s1 == s2); // false
System.out.println(s1 == s3); // false
// equals比较:String对象中的逐个字符
// 虽然s1与s2引用的不是同一个对象,但是两个对象中放置的内容相同,因此输出true
// s1与s3引用的不是同一个对象,而且两个对象中内容也不同,因此输出false
System.out.println(s1.equals(s2)); // true
System.out.println(s1.equals(s3)); // false
}
- compareTo(String str) 方法:按照字典序进行比较。
与equals不同的是,equals返回的是boolean类型,而compareTo返回的是int类型。具体比较方式:- 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
- 如果前k个字符相等(k为两个字符长度最小值),返回值两个字符串长度差值
public static void main(String[] args) {
String s1 = new String("abc");
String s2 = new String("ac");
String s3 = new String("abc");
String s4 = new String("abcdef");
System.out.println(s1.compareTo(s2)); // 不同输出字符差值-1
System.out.println(s1.compareTo(s3)); // 相同输出 0
System.out.println(s1.compareTo(s4)); // 前k个字符完全相同,输出长度差值 -3
}
4.compareToIgnoreCase(String str) 方法:
与 compareTo 方式相同,但是忽略大小写比较。
🌟 1.3 字符串查找
🌟 1.4 转化
- 字符串转数值
public static void main(String[] args) {
// 数组转字符串
String s1 = String.valueOf(1234);
String s2 = String.valueOf(12.34);
String s3 = String.valueOf(true);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
// 字符串转数字, Integer,Double 为包装类型
int data1 = Integer.parseInt("1234");
double data2 = Double.parseDouble("65.984");
System.out.println(data1);
System.out.println(data2);
}
- 大小写转换
public static void main(String[] args) {
String s1 = "abcd";
String s2 = s1.toUpperCase();
String s3 = s2.toLowerCase();
System.out.println(s2);
System.out.println(s3);
// ABCD
// abcd
}
- 字符串转数组
public static void main(String[] args) {
String s1 = "hello world!";
// 字符串转字符数组
char[] ch = s1.toCharArray();
for(int i=0;i<ch.length;i++){
System.out.print(ch[i]);
}
System.out.println();
// 字符数组转字符串
String s2 = new String(ch);
System.out.println(s2);
}
// hello world!
// hello world!
🌟 1.5 字符串替换
使用一个指定的新的字符串替换掉已有的字符串数据,可用的方法如下:
public static void main(String[] args) {
String s1 = "hello";
System.out.println(s1.replaceAll("l","_"));
System.out.println(s1.replaceFirst("l","_"));
}
// he__o
// he_lo
❗
注意事项: 由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串。所有String方法,如果返回值是 String 类型,一定产生了新对象。
🌟 1.6 字符串拆分
可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串。
str.split(',');
// // 以 ',' 完全分割。
str.split(',',3);
// 以 ',' 分割,总共分成 3 组。
拆分是特别常用的操作. 一定要重点掌握. 另外有些特殊字符作为分割符可能无法正确切分, 需要加上转义。
String str = "192.168.1.1" ;
String[] result = str.split("\\.") ;
for(String s: result) {
System.out.println(s);
}
// 192
// 168
// 1
// 1
❗ 注意事项:
- 字符 ’ | ’ , ’ * ’ , ’ + ’ ,都要进行转义,前面加上 " \ "。
- 而如果是 " \ " ,那么就得写成 “\\\\”
🌟 1.7 字符串截取
String str = "helloworld" ;
System.out.println(str.substring(5));
System.out.println(str.substring(0, 5));
// world
// hello
❗ 注意:
1.索引从 0 开始。
2. 注意前闭后开的写法,substring(0, 5) 表示包含 0 号下标的字符, 不包含 5 号下标。
🚀 二、 字符串常量池
🌟 2.1 对于创建对象的思考
下面两种创建 String 对象的方式相同吗?
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s3 == s4); // false
// s1 与 s2 是一个对象
// s3 与 s4 不是同一个对象
// 为什么呢?
}
在Java程序中,类似于:1, 2, 3,3.14,“hello”等字面类型的常量经常频繁使用,为了使程序的运行速度更快、更节省内存,Java为8种基本数据类型和 String 类都提供了常量池。
" 池 " 是编程中一种常见的、重要的提升效率的方式,我们以后会遇到各种 “ 内存池” ,“ 线程池 ”,“ 数据库连接池 ”。
为节省存储空间及提高程序运行效率,Java 中引入了:
- class 文件常量池:每个 .Java 源文件编译后生成 .Class 文件中会保存当前类中的字面常量以及符号信息。
- 运行时常量池:在 .Class 文件被加载时,.Class 文件中的常量池被加载到内存中称为运行时常量池,运行时常量池每个类都有一份。
- 字符串常量池:主要存放的是字符串常量,本质是一个哈希表,StringTable。
🌟 2.2 字符串常量池(StringTable)
字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable。不同 JDK 版本下字符串常量池的位置以及默认大小是不同的。
🌟 2.3 再谈 String 对象的创建
基于Java8 HotSpot分析
- 直接使用字符串常量赋值。
String s1 = "hello";
String s2 = "hello";
s1==s2 ?
// true
2. 通过 new 创建 String 对象
String s1 = "hello";
String s2 = "hello";
String s3 = new String("world");
String s4 = new String("world");
⛅结论:
只要是 new 出来的对象,都是唯一的。使用常量串创建 String 类型对象的效率更高,更节省空间。
- intern() 方法
intern 是一个 native 方法( Native方法指:底层使用C++实现的,看不到其实现的源代码 ),该方法的作用是手动将创建的String对象添加到常量池中。
public static void main(String[] args) {
char[] ch = new char[]{'a', 'b', 'c'};
String s1 = new String(ch); // s1对象并不在常量池中
//s1.intern(); // s1.intern();调用之后,会将s1对象的引用放入到常量池中
String s2 = "abc"; // "abc" 在常量池中存在了,s2创建时直接用常量池 中"abc"的引用
System.out.println(s1 == s2);
}
// 输出false
// 将上述方法打开之后,就会输出true
⛅⛅⛅
String 类中几种对象实例化的区别:
String str = " hello "
只会开辟一块堆内存空间,保存在字符串常量池中,然后 str 共享常量池中的 String 对象。String str = new String(" hello ")
会开辟两块堆内存空间,字符串 " hello " 保存至字符串常量池,然后用常量池中的 String 对象给新开辟的 String 对象赋值。String str = new String(new char[]{‘h’, ‘e’, ‘l’, ‘l’, ‘o’})
先在堆上创建一个 String 对象,然后利用 copyOf 重新开辟数组空间,将参数字符串数组中内容拷贝到 String 对象中。
🚀 三、字符串的不可变性
String是一种不可变对象. 字符串中的内容是不可改变。字符串不可被修改,是因为:
-
String类在设计时就是不可改变的,String类实现描述中已经说明了
🌕🌕
String类中的字符实际保存在内部维护的value字符数组中,该图还可以看出:- String 类被 final 修饰,表明该类不能被继承。
- value 被修饰符 final 修饰,表明 value 自身的值不能改变,即不能引用其它字符数组,但是其引用空间中的内容可以修改。
-
所有涉及到可能修改字符串内容的操作都是创建一个新对象,返回新对象。
为什么 String 类要设计为不可变的?有什么好处?
- 方便实现字符串常量池。如果 String 可变,那么对象池就要考虑拷贝的问题。
- 不可变对象是线程安全的。
- 不可变内存方便缓存 hashcode 对象,作为 key 时可以更高效保存到 HashMap中。
🚀 四 、StringBuffer 和 StringBuilder
🌟 4.1 StringBuilder 的介绍
由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilder和StringBuffer类。这两个类大部分功能是相同的,这里介绍 StringBuilder常用的一些方法。
从上述例子可以看出:String和StringBuilder最大的区别在于String的内容无法修改,而StringBuilder的内容可以修改。频繁修改字符串的情况考虑使用StringBuilder。
❗ 注意:String 和 StringBuilder 不能直接转换,但是可以调用下面的方法
- String 转换为 StringBuilder :利用 StringBuilder 的构造方法或者 append() 方法。
- StringBuilder 转换为 String :调用 toString() 方法。
🌟 4.2 常见问题
1. String 、StringBuffer、StringBuilder 的区别?
- String 内容不可修改,StringBuffer 和 StringBuilder 内容可以修改。
- StringBuffer 和 StringBuilder 大部分功能是相同的。
- StringBuffer 采用同步处理,属于线程安全操作;StringBuilder 未采用同步处理,属于线程不安全操作
2.以下代码总共创建了几个对象?
String str = new String("ab"); // 创建多少对象?
String str = new String("a") + String("b"); // 创建多少对象?
//