文章目录
- String类
- String的构造及内存分布
- 构造
- 内存分布
- 常用方法
- 判等
- 比较
- 查找
- 转化
- 替换
- 拆分
- 截取
- 字符串的不可变性
- StringBuilder和StringBuffer
String类
C语言中没有专门的字符串类型,一般使用字符数组或字符指针表示字符串,而字符串的函数需要包含头文件才能使用,这种数据与方法分离的做法不符合面向对象的思想。
Java中提供了String类
来表示字符串
String的构造及内存分布
构造
字符串的构造方式主要有三种:
-
常量串构造
String str1 = "hello";
-
直接
new
String
对象String str2 = new String("hello");
-
使用字符数组进行构造
char[] ch = new char[]{'h', 'e', 'l', 'l', 'o'}; String str3 = new String(ch);
内存分布
核心就是 字符串常量池
JDK6及以前,字符串常量池存放在在永久代(内存的永久保存区域)
JDK7之后,字符串常量池存放在堆
字符串常量构造字符串时,Java在字符串常量池中寻找该字符串的内存地址,找到则将地址赋值给变量,如果没有找到,则在常量池开辟空间并将这块空间赋值该字符串,将这块地址赋值给变量。
new
构造字符串时,Java先在堆区开辟一块空间,存放String对象
,然后在字符串常量池中寻找字符串的内存地址,找到则将该地址的引用传给堆区的对象,如果没有找到,则在常量池开辟空间并将这块空间赋值该字符串,将地址的引用传给堆区的对象,最后,将堆区对象的地址赋值给变量。
如下代码及内存分布:
public static void main(String[] args) {String s1 = "hello";String s2 = "hello";String s3 = new String("hello");String s4 = new String("hello");}
我们在IDEA
中观察String
的源码:
我们可以看到,String
类中包含很多的成员变量,我们仅关注value
数组即可,它存放的就是字符串常量池的某个字符串的地址,其内存分布简单表示如下图:
String str1 = new String("hello");
字符串非常重要,我们接下来介绍一下Java中字符串的常用方法。
常用方法
判等
字符串的判等区分两种:
==
:判断是否引用同一个对象boolean equals()
:判断字符串的内容是否相等
字符串类型是引用类型,==
判断两个字符串的地址,而equals()
方法判断两个字符串的内容是否相等(无关地址)
我们看下面一段代码,体会==
与equals()
的区别:
public class Test {public static void main(String[] args) {//new了三个不同的对象String str1 = new String("hello");String str2 = new String("HELLO");String str3 = new String("hello");System.out.println(str1 == str2);//不同对象的地址不同System.out.println(str1 == str3);//不同对象的地址不同System.out.println(str1.equals(str2));//两个字符串的内容不同System.out.println(str1.equals(str3));//两个字符串的内容相同System.out.println("=============");str2 = str1;//将引用变量str1的值赋给str2,此时str1和str2指向同一个对象System.out.println(str1 == str2);//指向同一个对象后,地址相同System.out.println(str1.equals(str2));//指向同一个对象,内容自然也相同}
}
打印观察:
我们再来看一段代码:
public static void main(String[] args) {String str1 = "hello";String str2 = "HELLO";String str3 = "hello";System.out.println(str1 == str2);//直接指向了字符串常量池的不同字符串,地址当然不同System.out.println(str1 == str3);//直接指向了字符串常量池的同一字符串,地址相同System.out.println(str1.equals(str2));//内容不同System.out.println(str1.equals(str3));//内容相同System.out.println("=============");str2 = str1;System.out.println(str1 == str2);System.out.println(str1.equals(str2));}
打印结果是:
前后两次区别在于构造字符串的方式不同,第一次是使用new
构造,第二次是直接使用常量串构造;
第一次不同对象指向字符串常量池的同一地址,==
结果是false
;而第二次相同的常量串构造,==
结果是true
,这刚好验证了我们上面介绍的内存分布情况。
比较
如下两种:
int compareTo()
int compareToIgnoreCase()
【compareTo】
前面介绍的equals()
的返回值是boolean
类型且只能判断是否相等,而compareTo()
返回值是int
类型,不同的比较结果返回不同特征的值。
compareTo()
的比较规则如下:
- 先按照字典序大小依次比较,如果出现不等的字符,直接返回两个字符的大小差值
- 如果前n个字符都相等(n为要比较的两个字符串的长度较小值),返回两个字符串长度差值
- 按照上面的比较规则,如果
>
就返回一个正数;如果<
就返回一个负数;如果==
就返回0
例如:
public static void main(String[] args) {String s1 = new String("hello");String s2 = new String("Hello");String s3 = new String("zero");String s4 = new String("hello");System.out.println(s1.compareTo(s2));//第一个字符'h'与'H'比较,'h' > 'H',所以返回正值System.out.println(s1.compareTo(s3));//第一个字符'h'与'z'比较,'h' < 'z',所以返回负值System.out.println(s1.compareTo(s4));//所有字符依次比较,全部相等,返回0}
【compareToIgnoreCase】
与compareTo()
的区别是,compareToIgnoreCase()
比较时忽略大小写,如:
public static void main(String[] args) {String s1 = "HELLO";String s2 = "hello";System.out.println(s1.compareTo(s2));System.out.println(s1.compareToIgnoreCase(s2));}
注意:
compareTo()
和compareToIgnoreCase()
比较时的主体都是调用方法的那个字符串而非传入的参数
查找
char charAt()
int indexOf()
及其重载方法
int lastIndexOf()
及其重载方法
【charAt】
给charAt()
方法传入一个int
类型的值,这个值即下标值,它将返回指定下标位置的字符(char
类型)
public static void main(String[] args) {String s1 = "hello";System.out.println(s1.charAt(0));System.out.println(s1.charAt(1));System.out.println(s1.charAt(2));System.out.println(s1.charAt(3));System.out.println(s1.charAt(4));}
【indexOf及其重载方法】
-
int indexOf(int ch)
:返回 ch 第一次出现的位置,没有则返回 -1public static void main(String[] args) {String s1 = new String("hello");System.out.println(s1.indexOf('h'));System.out.println(s1.indexOf('l'));System.out.println(s1.indexOf('i'));}
-
int indexOf(int ch, int fromIndex)
:从fromIndex(下标值)
位置开始找,返 ch 第一次出现的位置,没有找到则返回 -1public static void main(String[] args) {String s1 = "abcabcdabcde";System.out.println(s1.indexOf('c', 4));System.out.println(s1.indexOf('f', 0));}
-
int indexOf(String str)
:返回 str 第一次出现的位置,没有找到则返回 -1public static void main(String[] args) {String s1 = new String("hello");System.out.println(s1.indexOf("ll"));System.out.println(s1.indexOf("lll"));}
-
int indexOf(String str, int fromIndex)
:从fromIndex(下标值)
的位置开始找,返回 str 第一次出现的位置,没有则返回 -1public static void main(String[] args) {String s1 = "abcabcdabcde";System.out.println(s1.indexOf("abc", 4));System.out.println(s1.indexOf("abcf", 2));}
【lastIndexOf】
与前面indexOf()
的区别是:查找方向不同,lastIndexOf()
从后往前找
4种方式:
int lastIndexOf(int ch)
:从后往前找,返回 ch 第一次出现的位置,没有则返回 -1;int lastIndexOf(int ch, int fromIndex)
:从fromIndex
的位置开始从后往前找,返回 ch 第一次出现的位置,没有则返回 -1;int lastIndexOf(String str)
:从后往前找,返回 str 第一次出现的位置,没有则返回 -1;int lastIndexOf(String str, int fromIndex)
:从fromIndex
的位置开始从后往前找,返回 str 第一次出现的位置,没有则返回 -1;
public static void main(String[] args) {String s1 = "hello";String s2 = "abcabcdabcde";System.out.println(s1.lastIndexOf('l'));System.out.println(s1.lastIndexOf('l', 2));System.out.println(s2.lastIndexOf("abc"));System.out.println(s2.lastIndexOf("abc", 6));}
转化
转化部分介绍:
- 数值和字符串转化
- 大小写转化
- 字符串转数组
- 格式化
【数值和字符串转化】
数值转化为字符串:
String String.valueOf()
字符串转化为数值:
包装类对应类型 包装类.valueOf(String str)
当然不是所有的转化都会成功
注意:需要使用类名调用valueOf()
方法,这是因为valueOf()
方法被static
修饰
public static void main(String[] args) {String s1 = String.valueOf(123);int i1 = Integer.valueOf("123");int i2 = Integer.parseInt("123");int i3 = Integer.valueOf("123");double d1 = Double.valueOf("12.2");System.out.println(s1);System.out.println(i1);System.out.println(i2);System.out.println(i3);System.out.println(d1);}
上图中穿插了一个parseInt()
方法,其与valueOf()
没有差别,观察valueOf()
方法:
发现,valueOf()
底层就调用了parseInt()
方法
【大小写转化】
转大写:
String toUpperCase()
转小写
String toLowerCase()
public static void main(String[] args) {String s1 = "hello";String s2 = s1.toUpperCase();System.out.println(s2);System.out.println(s2.toLowerCase());
}
【字符串转数组】
char[] toCharArray()
public static void main(String[] args) {String s1 = "hello";char[] chars = s1.toCharArray();for (char tmp : chars) {System.out.println(tmp);}}
【格式化】
String format()
需要了解占位符的知识,Java较少使用,但学过C语言的应该不陌生
public static void main(String[] args) {String s1 = String.format("%d-%d-%d", 2024, 5, 31);System.out.println(s1);}
替换
String replace()
String replaceAll(String regex, String replacement)
:替换所有指定内容
String replaceFirst(String regex, String replacement)
:替换首个指定内容
public static void main(String[] args) {String s1 = "hello";String s2 = s1.replace('l', 'o');String s3 = s1.replace("ll", "oo");String s4 = s1.replaceAll("l", "o");String s5 = s1.replaceFirst("ll", "oo");System.out.println(s2);System.out.println(s3);System.out.println(s4);System.out.println(s5);}
拆分
String[] split(String regex)
:以regex
为分割符拆分字符串,返回一个字符串数组
String[] split(String regex, int limit)
:将字符串以指定的格式,拆分为limit
组
【String[] split(String regex)
:以regex
为分割符拆分字符】
public static void main(String[] args) {String s1 = "hello world!";String[] strings1 = s1.split(" ");for (String tmp : strings1) {System.out.print(tmp + " ");}//换行System.out.println();//检验分割后返回的数组System.out.println(Arrays.deepToString(strings1));}
如果有多个分割符可以使用|
,如:
public static void main(String[] args) {String s1 = "I am@OK!";String[] strings = s1.split(" |@");System.out.println(Arrays.deepToString(strings));
}
【String[] split(String regex, int limit)
:将字符串以指定的格式,拆分为limit
组】
public static void main(String[] args) {String s1 = "i am a bit";String[] strings = s1.split(" ", 2);//仅被分为两组System.out.println(Arrays.deepToString(strings));}
有些特殊字符作为分割符可能无法正确切分,需要加上转义
- 字符
|
、*
、,
、+
、.
都得加上转义字符,即\\
- 如果想以
\
为分割符,就得写成\\\\
;单独的\
不能出现在字符串中,必须以\\
表示\
- 如果一个字符串中有多个分割符,可以使用
|
作为连字符,上面演示过了
public static void main(String[] args) {String s1 = "2024.5.31";String[] strings1 = s1.split("\\.");System.out.println(Arrays.deepToString(strings1));String s2 = "2024+5+31";strings1 = s2.split("\\+");System.out.println(Arrays.deepToString(strings1));String s3 = "2024\\5\\31";strings1 = s3.split("\\\\");System.out.println(Arrays.deepToString(strings1));}
截取
String substring(int beginIndex)
:从beginIndex
下标位置截取到结尾
String substring(int beginIndex, int endIndex)
:从beginIndex
下标位置截取到endIndex - 1
下标位置,也就是说[beginIndex, endIndex)
public static void main(String[] args) {String s1 = "hello world!";String s2 = s1.substring(6);System.out.println(s2);String s3 = s1.substring(6, 11);System.out.println(s3);}
【补充】
String trim()
:去掉字符串中的左右空格,保留中间空格
public static void main(String[] args) {String s1 = new String(" hello world ");String s2 = s1.trim();System.out.println(s2);}
字符串的不可变性
String
是一种不可变对象。字符串不可修改,我们从String
类源码观察:
如图:
-
第一个
final
表示String类
不能被继承; -
第二个
final
表示value
不能指向新的对象,但是其内容可以改变,我们可以验证这一点:
public static void main(String[] args) {final String[] strings = new String[]{"hello", " world", "!"};//修改内容,不报错strings[1] = "bit";//修改strings指向的对象,报错!strings = new String[10];}
- 真正决定
String
不可被修改的是蓝框里的private
修饰符,value
被private
修饰,意味着value
只能在当前的String
类中使用,而String
类没有提供value
的get
和set
方法,所以我们没有办法拿到value
值,自然就不能修改字符串了。反过来,只要给value
提供get
和set
方法,我们就可以修改字符串了。
基于字符串的不可变性,我们补充一点注意点:
上面我们介绍的所有的方法,都不是在原字符串上进行操作的,都是重新创建了一个新的字符串返回
【字符串的追加(拼接)操作】
使用+
即可
public static void main(String[] args) {String s1 = "hello";String s2 = s1 + " world";System.out.println(s2);}
此操作容易被误认为修改了字符串,其实并不是。
这段代码实际上如此:
public static void main(String[] args) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append("hello");stringBuilder.append(" world");String s2 = stringBuilder.toString();System.out.println(s2);}
并不是在原字符串上修改的,而是使用了StringBuilder
类,这是个什么类呢?我们接下来讲!
StringBuilder和StringBuffer
Java中字符串的不可变性使得字符串的操作要不断地new
新的对象,使得便捷性与效率并不理想。
为了方便字符串的修改,Java提供了StringBuilder
和StringBuffer
两个类,String
调用这两个类的构造方法或append
方法即可实现转化,这两个类中有非常多的操作字符串的方法(包含String
类方法并比String
类更丰富),如上面展示过的append()
追加方法、reverse()
反转方法,调用这些方法时无需创建新对象,直接在它们的类对象上操作即可,若要转换成字符串,只需调用toString()
方法并接收即可。
我们演示一下:
public static void main(String[] args) {//创建StringBuilder类对象StringBuilder stringBuilder = new StringBuilder("hello");//直接操作stringBuilder.reverse();//转换为字符串String str = stringBuilder.toString();//打印System.out.println(stringBuilder);System.out.println(str);}
以后我们想要对字符串执行追加操作时,要尽量使用StringBuilder
或StringBuffer
两个类的追加方法,它们的运行效率更高!
StringBuilder
、StringBuffer
类与String
类最大的区别就是String
类的内容无法修改,而StringBuilder
、StringBuffer
的内容可以修改。
【StringBuilder
与StringBuffer
的区别】
StringBuffer
采用同步处理,属于线程安全操作,用于保障多线程安全StringBuilder
未采用同步处理,属于线程不安全操作,用于单线程
那么,我们以后都使用StringBuffer
?
不是的。虽然StringBuffer
可以保证多线程安全,但是相比StringBuilder
多了一把"锁",“开锁” 和 “上锁” 也是会有损耗的,其效率会较低。对于现在的我们,只需要记住,具体场景具体分析,合理选择。
以上就是对 String
类的所有介绍了
接下来就是异常了,异常结束后,JavaSE
的语法部分就基本结束了,后续会将所有的知识整合在一起,发一篇SE语法全集
!