1. String
1.1 字符串的不可变性
String
是一种不可变对象,字符串中的内容是不可改变的。那么在java中为什么说String字符串具有不可变性?
观察String类的设计,我们发现String类中有一个value字符数组,这个数组是String类中的字符实际保存的位置。仔细观察,我们能看到这个value数组被final和private修饰着。
final byte[] value = {'H','e','l','l','o'};
//被final修饰的值是一个常量,也就是说我们不能更改value数组中存放的引用
value = new byte[10];//Error
//当我们想要更改value数组中的引用时,编译器会直接报错
//-------------------------------------
//被final修饰说明这个引用不能更改,但是我们却可以通过这个引用去更改原数组的内容
value[0] = 'A';//被final修饰的数组,通过引用去修改数组内容
我们发现被final
修饰的byte[] value
不是字符串不可变性的原因,这种说法确切的说是错误的,我们修改引用对象中的内容。那么String字符串具有不可变性的原因是什么呢?
- 也就是说String类的不可变性的原因之一其实是实际存放字符串的字符数组被
private
修饰,这就使得这个数组只能在当前类中使用,而在外界获得不到。 - Java内部在实现String类的过程中将所有涉及到可能修改字符串内容的操作,都采取创建一个新对象,我们所有的改变都是针对新对象而言,而不改变原对象。
1.2 字符常量池
Java中规定,""
双引号括起来的字符串,我们称之为字符串常量
都是直接存储在堆中的字符串常量池
的。那么为什么会有字符串常量池呢?
字符串作为最基础的数据类型,在大量实例化对象的时候会极其耗费时间和空间,
JVM
为了提高性能,减少资源的耗费会在实力会字符串常量的时候做一些优化,例如:(1)开辟一个字符串常量池String Table
;
(2) 创建字符串常量前,先去字符串常量池中检查该字符串是否存在
(3)存在则返回字符串常量池中该字符串的引用;不存在则实例该字符串放入字符串常量池中,并返回该引用。
//我们从下面的代码进行引入,理解字符串常量池
String s1 = "HELLO";//(1)
String s2 = "WORLD";//(2)
String s3 = "HELLO";//(3)
String s4 = new String("HELLO");//(4)
System.out.println("s1的引用是否等于s2的引用:" + (s1 == s2));
System.out.println("s1的引用是否等于s3的引用:" + (s1 == s3));
System.out.println("s1的引用是否等于s4的引用:" + (s1 == s4));
程序的执行结果如下:
我们可以发现s1 == s3,s1 != s4;这是为什么呢?
- s1 == s3这也正说明当我们创建字符串常量前,我们会去看字符串常量池中的值,在执行(1)步时我们会先在字符串常量池中寻找"HELLO",当我们发现字符串常量池中没有的时候,我们会在字符串常量池中创建一个并返回当前引用,所以(3)得到的引用与(1)中获得的引用相同。
- s1 != s4 其实是在堆中创建一个新的String对象,这个String对象会与字符串常量中的String对象引用同一个字符数组,但是属于不同的对象
也就是说在代码执行前4步时,我们可以理解堆栈图中的关系是这样的:
1.2.1 intern()
intern()
方法是一个native方法,底层由C++实现,看不到具体实现代码,该方法的作用是手动将创建的String对象添加到常量池中。
byte [] b = new byte[] {'a','b','c'};
String s1 = new String(b);//(1)
//s1.intern();
String s2 = "abc";//(2)
System.out.println("s1的引用是否等于s2的引用:" + (s1 == s2));
运行程序结果如下:
将注释段落打开,观察运行结果如下:
- 当我们用字符数组为参数创建一个String对象引用时,此时String对象并不在常量池中。也就是说在执行(1)时,我们并没有在字符串常量池中创建一个String对象,所以在注释//s1.intern()时我们s1所引用对象并不在字符串常量池中,当执行(2)时,我们会在常量池中创建一个String对象,"abc"在常量池中便存在了,这时候我们发现s1 != s2;
- 当我们放开s1.intern();之后,我们会将s1对象的引用放入常量池中,这时候我们可以得到s1 == s2;
- 总结:
-
- 当我们调用intern()方法时,如果字符串常量池中已经包含当前字符串,则返回字符串常量池中的字符串。
-
- 如果字符串常量池中没有这样一个字符串,则会将
intern
返回的引用指向当前字符串s1
,也就说会返回堆中·字符串常量池中的字符串。
- 如果字符串常量池中没有这样一个字符串,则会将
1.3 String类中的常用方法
1.3.1 字符串的构造
//常用的构造方式:
//(1)使用常量串构造
String s1 = "HELLO WORLD";
System.out.println(s1);
//(2)new String 对象
String s2 = new("HELLO WORLD");
System.out.println(s2);
//(3)使用字符数组构造
byte[] array = {'H','E','L','L','O','','W','O','R','L','D'};
String s3 = new String(array);
System.out.println(s3);
1.3.2 其他常见方法
方法 | 功能 |
---|---|
boolean equals(Object anObject) | 判断两个字符串是否相等 |
int compareTo(String s) | 比较两个字符串,相等则返回0,调用者大返回> 0的值,调用者小返回< 0的值 |
int compareToIgnoreCase(String str) | 与compareTo()方法的功能相同,但是忽略大小写的比较 |
char charAt(int index) | 返回index位置上字符,如果index为负数或者越界,抛出IndexOutOfBoundsException异常 |
int indexOf(int ch) | 返回ch第一次出现的位置,没有返回-1 |
int indexOf(int ch, int fromIndex) | 从fromIndex位置开始找ch第一次出现的位置,没有返回-1 |
int indexOf(String str) | 返回str第一次出现的位置,没有返回-1 |
int indexOf(String str, int fromIndex) | 从fromIndex位置开始找str第一次出现的位置,没有返回-1 |
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 |
char[] toCharArray() | 将字符串转换成数组 |
String toLowerCase() | 字符串转小写 |
String toUpperCase() | 字符串转大写 |
String replaceAll(String regex, String replacement) | 替换所有的指定内容 |
String replaceFirst(String regex, String replacement) | 替换首个出现内容 |
String[] split(String regex) | 将字符串以指定格式全部拆分 |
String[] split(String regex, int limit) | 将字符串以制定格式拆分,拆分为limit组 |
String substring(int beginIndex) | 从指定索引截取到结尾 |
String substring(int beginIndex, int endIndex) | 截取部分内容 |
String trim() | 去除字符串两边空格,保留中间的空格 |
public static void main(String[] args) {
//判断两个字符串是否相等
String s1 = "hello";
String s2 = "world";
System.out.println(s1.equals(s2));//false
//比较两个字符串
String s3 = "abc";
String s4 = "abc";
String s5 = "abcd";
String s6 = "ab";
System.out.println(s3.compareTo(s4));// 0
System.out.println(s3.compareTo(s5));// <0
System.out.println(s3.compareTo(s6));// >0
//比较两个字符串(忽略大小写)
String s7 = "Abc";
System.out.println(s3.compareTo(s7));// 0
//字符串查找
String s8 = "abcdefghijklmnoocpqrst";
System.out.println(s8.charAt(3));// 'd'
System.out.println(s8.indexOf('c'));// 2
System.out.println(s8.indexOf('c', 10));// 16
System.out.println(s8.indexOf("oocp"));// 14
System.out.println(s8.indexOf("op", 10));// -1
System.out.println(s8.lastIndexOf('c'));// 16
System.out.println(s8.lastIndexOf('c', 10));// 2
System.out.println(s8.lastIndexOf("pqr"));// 17
System.out.println(s8.lastIndexOf("pqr", 19));// 17
//字符串转数组
String s9 = "hello";
char[] ch = s9.toCharArray();
for(int i = 0;i < ch.length;i++) {
System.out.println(ch[i]);
}
System.out.println();
//字符串转大写,字符串转小写
String s10 = "hello";
String s11 = "HELLO";
System.out.println(s10.toUpperCase());//HELLO
System.out.println(s11.toLowerCase());//hello
//字符串替换
String s12 = "helloworld" ;
System.out.println(s12.replaceAll("l", "_"));//he__owor_d
System.out.println(s12.replaceFirst("l", "_"));//he_loworld
//将字符串以指定格式全部拆分
String s13 = "hello world and code";
String[] result1 = s13.split(" ");// 按照空格拆分
for(String s: result1) {
System.out.println(s);
}
//hello
//world
//and
//code
//将字符串以指定格式部份拆分
String[] result2 = s13.split(" ",2) ;
for(String s: result2) {
System.out.println(s);
}
//hello
//world and code
//字符串截取
String s14 = "helloworld" ;
System.out.println(s14.substring(5));//world
System.out.println(s14.substring(0, 5));//hello
//去除字符串两边空格
String s15 = " hello world " ;
System.out.println("[" + s15 + "]");//[ hello world ]
System.out.println("[" + s15.trim() + "]");//[hello world]
}
好了,以上就是关于String类中常见方法的介绍了,根据实际的应用常见调用String类中的方法可以大大提高我们解决问题的效率。