文章目录
- String类
- 字符串的构造
- String对象的比较
- 字符串查找
- 字符串中的各种操作方法
- 转化操作
- 数值和字符串的转化
- 大小写转换
- 字符串转化成字符数组
- 格式化转化成字符串
- 字符串替换操作
- replaceFirst方法替换首个内容
- replace方法替换全部内容
- replaceAll方法替换全部内容
- 字符串拆分操作
- 字符串的截取操作
- 去除字符串两端空格操作
- 字符串常量池(StringTable)
- 字符串的不可变性
- StringBuilder类和StringBuffer类
String类
字符串的构造
在Java中,对字符串进行构造常见的有三种方法
- 使用常量串进行构造
String str1="abcd";
- 接使用 new String 对象
String str2=new String("abcd");
- 使用字符数组进行构造
char[] array={'a','b','c','d'};
String str3=new String(array);
注意:
- 在上面的三种方法,方法一的底层逻辑其实也是和方法二一样的,都是需要使用** new String 对象**来进行构造的,只是对其进行了简写而已。
- **String是引用类型,内部其实是不存储字符串本身的。**在源码中会发现,字符串实际是保存在char类型的数组中的。
- 既然String是引用类型,那么下面的这段代码也就可以解释通了:
String str4=str1;
通过前面对“类和对象”的学习,也就不难看出,这段代码其实就是str4引用了str1引用的对象,最后str1和str4中的字符串就会是一样的。
(这里只是用简单帮助理解的图,后面引出字符串常量池后,还会再将图描述得更加准确。String类中会分为两部分:一是value值,也就是用来存放字符串的;二是hash值)
String对象的比较
在Java中,字符串的比较有四种(前两种方法是比较主要的)
在之前的文章“抽象类和接口”一文中讲到过(本图从“抽象类和接口”一文中截取过来,此图对帮助这部分理解起到至关重要的作用):
- ==比较是否引用同一个对象
从上面的图中我们可以知道,对于基本(内置)类型,比较的是变量的值;对于引用类型比较的是引用中的地址。
String str1=new String("abc");
String str2=new String("abc");
System.out.println(str1==str2); //false
因为new出来的对象会在堆上创建一块空间,所以str1和str2是存在在堆上的两块不同的空间(地方)中,因此==比较的是这两个引用的地址,他们的地址是不相等的,故返回false。
这时候,有人又写出了下面的一段代码:
String str3="abc";
String str4="abc";
System.out.println(str3==str4); //true
在这时候,执行的结果是true。不是说这种使用常量串进行构造底层也是会使用 new String 对象的吗?为什么相同的操作,在这里却是true而在上面的例子中是false呢?
对于这样的疑问,只有引出后面的字符串常量池后才能够解决,请耐心继续往下看。
- equals方法:按字典序比较
从上面的图中,我们知道使用equals方法对引用类型进行比较的时候,默认是按照地址进行比较的,除非有对其进行重写的。
String str1=new String("abc");
String str2=new String("abc");
System.out.println(str1.equals(str2)); //true
在运行上面这段代码的时候,会发现结果是true。这就说明了此处的str1和str2并不是按照地址进行比较的,这也就进一步说明了String类重写了父类Object中的equals方法,使其变成了按照指定的规则进行比较(具体的规则可见如下代码,了解即可):
public boolean equals(Object anObject){
// 1.先检测this 和 anObject 是否为同一个对象比较,如果是返回true
if(this==anObject){
return true;
}
// 2.检测anObject是否为String类型的对象,如果是继续比较,否则返回false
if(anObject instanceof String) {
// 将anObject向下转型为String类型对象
String anotherString = (String) anObject;
int n = value.length;
// 3.this和anObject两个字符串的长度是否相同,是继续比较,否则返回false
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
// 4.按照字典序,从前往后逐个字符进行比较
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
- compareTo:按字典序比较
前面equals方法的返回类型是boolean,而接下来要说到的compaTo方法的返回类型是int类型。
compareTo对字符串的比较规则有以下几点:
- 从前向后,依次按照字典次序大小进行比较,若出现两个不相等的字符,则会返回这两个字符大小的差值
- 若通过上面规则比较出来的字符完全相等,但是有一方字符串走完一方未走完,则会返回这两个字符串长度的差值
- 若比较的字符完全相等,且两字符串的长度也都是相等的,那么会返回0
String str1="abc";
String str2="ac";
String str3="abc";
String str4="abcde";
str1.compareTo(str2); //-1
str1.compareTo(str3); //0
str1.compareTo(str4); //-1
- compareToIgnoreCase:按字典序比较
compareToIgnoreCase方法的比较规则基本上与compareTo方法的比较规则的相似的,只是多了一个忽略大小写的比较而言。
String str1="abc";
String str2="ac";
String str3="ABC";
String str4="abcde";
str1.compareToIgnoreCase(str2); //-1
str1.compareToIgnoreCase(str3); //0
str1.compareToIgnoreCase(str4); //-1
字符串查找
- charAt(int index)方法:返回index位置上的字符,如果index为负数或者越界,则会抛出越界异常(IndexOutOfBoundsException异常)
String str="abcdefabc";
char ch=str.charAt(2); //c
- indexOf( )方法:这个方法可以传如下四种类型的参数列表来达到不同的效果
- indexOf(char ch):返回ch第一次出现的位置,没有则返回-1
String str="abcdefabc";
int index1=str.indexOf('b'); //1
- indexOf(char ch, int fromindex):从fromindex位置开始找ch第一次出现的位置,没有则返回-1
String str="abcdefabc";
int index2=str.indexOf('b',2); //7
- indexOf(String str):返回str第一次出现的位置,没有则返回-1
String str="abcdefabc";
int index3=str.indexOf("abc"); //0
- indexOf(String str, int fromindex):从fromindex位置开始找str第一次出现的位置,没有则返回-1
String str="abcdefabc";
int index4=str.indexOf("abc",1); //6
- lastIndexOf( )方法:和indexOf方法同样的,这个方法也可以传如下四种类型的参数列表来达到不同的效果(注意:lastIndexOf( )方法与indexOf( )方法唯一的区别就是indexOf( )方法是从前向后找;而lastIndexOf( )方法是从后向前找的,其他基本都是类似的)
String str="abcdefabc";
int index5=str.lastIndexOf('b'); //7
int index6=str.lastIndexOf('b',6); //1
int index7=str.lastIndexOf("abc"); //6
int index8=str.lastIndexOf("abc",5); //0
字符串中的各种操作方法
转化操作
对字符串进行转化操作主要有4种:数值和字符串的转化、字符串大小写的转化、字符串转化为字符数组、格式化转化。
数值和字符串的转化
其他类型转化成字符串:
String str1=String.valueOf(123); //123
String str2=String.valueOf(1.23); //1.23
String str3=String.valueOf(true); //true
String str4=String.valueOf(new Student("haha",6)); //test4.Student@1b6d3586
这里会发现:不仅仅是整型或者是浮点型可以转化成字符串,布尔类型甚至是一个自定义类型对象也是可以转化成字符串的(但是结果会包含地址的哈希值)。
字符串转化成数值:
int data1=Integer.parseInt("123"); //123
double data2=Double.parseDouble("1.23"); //1.23
大小写转换
使用toUpperCase()和toLowerCase()方法即可对字符串大小写进行转化。
String str1="abcd";
String str2=str1.toUpperCase(); //ABCD
String str3=str2.toLowerCase(); //abcd
但是要注意的一点是:类似这些对字符串的操作,底层都是先将整个字符串拷贝一份出来后再进行操作的(下面的字符串其他操作也是这样的),这一点在后面的内容中会重点介绍到,请继续往下看。
字符串转化成字符数组
使用toCharArray()方法即可实现字符串转化数组的操作。
String str1="abcd";
char[] ch= str1.toCharArray();
for (int i = 0; i < ch.length; i++) {
System.out.print(ch[i]+" "); //a b c d
}
格式化转化成字符串
使用format()方法即可格式化转化成字符串。
效果其实是和C语言中printf函数是类似的。
String.format("%d %d %d",12,23,34); //12 23 34
字符串替换操作
字符串的替换有三种方式:replace、replaceAll、replaceFirst。
replaceFirst方法替换首个内容
String str="ababc";
String str3=str.replaceFirst("ab","cd"); //cdabc
这里会发现是从前往后找,找到第一个出现的位置进行替换。
replace方法替换全部内容
String str="ababc";
String str1=str.replace("ab","cd"); //cdcdc
replaceAll方法替换全部内容
String str="ababc";
String str2=str.replaceAll("ab","cd"); //cdcdc
这时候就有人会问:replace和replaceAll都是将指定的内容全部都替换掉,那为什么还要分出他们两个出来呢?
这个问题在后面会详细讲到,这里向简单的说一下:replaceAll是支持正则表达式的,会对其中的参数进行解析,而replace是不支持正则表达式的。(关于正则表达式会在后面文章中说到)
字符串拆分操作
字符串拆分可以使用split( )方法进行拆分,且有两种不同的参数列表供使用。
- split(String str)方法:将字符串全部按照str来进行拆分
String str="ab abcd cd";
String[] ch1=str.split(" ");
for (int i = 0; i < ch1.length; i++) {
System.out.println(ch1[i]); //ab abcd cd
}
- split(String str, int limit)方法:将字符串以指定格式进行拆分,拆分成limit组
String str="ab abcd cd";
String[] ch2=str.split(" ",2);
for (int i = 0; i < ch2.length; i++) {
System.out.println(ch2[i]); //ab abcd cd
}
注意:有一些情况下是要对特殊字符进行拆分的,这时候可能就要加上转义字符了。
字符串的截取操作
使用substring( )方法可以对字符串进行截取操作。
- substring(int beginIndex, int endIndex)方法:截取从beginIndex到endIndex部分的内容,注意这里的范围是左闭右开的(既是包含beginIndex,不包含endIndex)
String str="ab abcd cd";
System.out.println(str.substring(0,6)); //ab abc
如果参数列表中没有传入endIndex部分的值,那么则会默认是到这个字符串的结尾处。
去除字符串两端空格操作
使用str.trim( )方法可以对字符串两端多于的空格去掉。
String str=" ab cd ";
System.out.println(str.trim()); //ab cd
字符串常量池(StringTable)
字符串常量池在JVM中是StringTable类,实际上是一个固定大小的HashTable(哈希表,后面文章中会介绍到)。
下面就以一段简单的代码来演示到底什么是字符串常量池:
String str3="abc";
String str4="abc";
System.out.println(str3==str4); //true
(这段代码其实就是上面String对象比较中的一个例子)
当str3存入进去的时候,会先在字符串常量池中找是否有包含这个字符串,发现字符串常量池中并没有找到这个字符串,则将这个字符串的地址存在字符串常量池当中,并且新开辟一块空间,创建这个字符串对象,这时候的“String str3=“abc”;”这条语句其实就相当于“String str3=new String(“abc”);”语句,都是要new的。
同样的,str4在存进去的时候,也会先在字符串常量池中查找,发现字符串常量池中0x99地址处的字符串正好就是str4的字符串,所以不会在堆中重新开辟内存,而是会直接引用这个字符串的对象,这样一来,str3和str4的地址就是一样的,结果当然也就是true了,这也就解释了上面留下来的疑问。
通过各种资料的收集对比以及总结后,可以得出下面这条结论:只要是双引号引起来的元素,全部都会存在字符串常量池当中,且仅有一份。
这时候有人就会拿出一段代码然后就开始问了:我写的这两个字符串对象都是有双引号引起来的,并且也完全符合上面例子的逻辑,为什么结果就是跟预期的不一样呢?
String str5=new String("abc");
String str6="abc";
System.out.println(str5==str6); //false
其实这个疑问就是我自己最开始的疑问,第一条语句中有引号那么就说明会在字符串常量池中存起来,在执行第二条语句的时候,引号的内容就会去字符串常量池中找,发现存在这个字符串,则不会重新再堆上开辟空间,那么他们的地址不就应该是一样的吗?其实通过查阅资料和画图之后,就大概能够明白了,解析如下:
实际上,将双引号内容存放在字符串常量池中并且在堆中开辟一块空间来存储是在str5在创建对象之前就执行的,而str5在创建新的对象的时候,又会在堆中重新开辟一块空间,这样的话,str5和str6的地址也就不一样了。
注意:只要是new出来的,就一定会在堆上开辟一块行的空间。
此外,在字符串常量池中还引出了intern( )方法,用来将原本没有在字符串常量池中出现的字符串自动导入到字符串常量池当中,示例如下:
char[] ch={'a','b','c'};
String str1=new String(ch);
str1.intern();
String str2="abc";
System.out.println(str1==str2); //true
字符串的不可变性
从上面的种种方法对字符串进行操作中,我们会发现,他们都不是简单地直接对字符串本身进行修改,而是向将原先的字符串进行拷贝后,在对这个拷贝后的字符串进行修改的。这是因为String实际上是一种不可变的对象,字符串中的内容是不能够被改变的。
String类中的字符实际上是保存在内部维护的values字符数组中,由于该字符数组是被final修饰的,所以表明了这个类时不能够被继承的,所以也就不存在什么子类中对这个values字符数组进行重写子类的操作,又因为该字符数组是被private修饰的,表明了这个字符数组在类外是不能进行访问的,而在String类中有没有关于这个字符数组的set和get方法,通过上面的这两点,就可以判断字符串是不可以被修改的。
这时候,就会又有人对下面这个代码有疑问:
String str="abc";
str+="abc";
System.out.println(str); //abcabc
这段代码也是对字符串进行操作的,最后打印的还是原字符串,但确确实实就是被修改了啊,这又是为什么呢?
其实这是因为+/+=操作底层是通过StringBuilder类中的toString方法进行实现的,实际上这段代码会一连串地创建出很多对象的,不是简单的拼接操作,效率是非常慢的,这其中的原因还是字符串是不可变的。所以,我们要尽量少地对字符串进行这样的操作。
StringBuilder类和StringBuffer类
由于String是不可变的,那么有些情况下会因为这一特性造成不必要的麻烦,所以这时候Java又提供了StringBuilder和StringBuffer类来方便修改字符串,其中StringBuilder和StringBuffer的基本功能都是类似的,所以只介绍其中一种,另外一种是类似的。
就常用的方法来说,StringBuilder和StringBuffer相比于String多出了两个主要的方法:
- append方法:可以直接在尾部追加东西,最后实现的效果与String中的+=是类似的,但是空间的占用却比+=少了非常多。
- reserve方法:可以直接实现字符串的反转。
还有其他的操作放在下面的表格中:
方法 | 说明 |
StringBuff append(String str) | 在尾部追加,相当于String的+=,可以追加:boolean、char、char[]、double、float、int、long、Object、String、StringBuff的变量 |
char charAt(int index) | 获取index位置的字符 |
int length() | 获取字符串的长度 |
int capacity() | 获取底层保存字符串空间总的大小 |
void ensureCapacity(int mininmumCapacity) | 扩容 |
void setCharAt(int index, char ch) | 将index位置的字符设置为ch |
int indexOf(String str) | 返回str第一次出现的位置 |
int indexOf(String str, int fromIndex) | 从fromIndex位置开始查找str第一次出现的位置 |
int lastIndexOf(String str) | 返回最后一次出现str的位置 |
方法 | 说明 |
int lastIndexOf(String str, | |
int fromIndex) | 从fromIndex位置开始找str最后一次出现的位置 |
StringBuff insert(int | |
offset, String str) | 在offset位置插入:八种基类类型 & String类型 & Object类型数据 |
StringBuffer | |
deleteCharAt(int index) | 删除index位置字符 |
StringBuffer delete(int | |
start, int end) | 删除[start, end)区间内的字符 |
StringBuffer replace(int | |
start, int end, String str) | 将[start, end)位置的字符替换为str |
String substring(int start) | 从start开始一直到末尾的字符以String的方式返回 |
String substring(int | |
start,int end) | 将[start, end)范围内的字符以String的方式返回 |
StringBuffer reverse() | 反转字符串 |
String toString() | 将所有字符按照String的方式返回 |
注意:String和StringBuilder或者StringBuffer都是不能够直接进行转换的,如果要转换需要如下条件:
- String转StringBuilder或者StringBuffer:需要利用StringBuilder或者StringBuffer的构造方法或者append( )方法。
- StringBuilder或者StringBuffer转String:需要调用toString( )方法或者substring( )方法。
篇幅有限,有关String、StringBuilder和StringBuffer三者的比较和区别会放在下一篇文章中讲解。