一.String类的常用方法
1.1 字符串的构造
String的构造方法有很多重载
当然不需要全部记住!我们只需要熟练使用常用的就可以
下面是常用构造方法的举例
1.使用常量字符串构造
String str1="hello world";
String str2=new String("hello world");
第一行代码只是第二行代码的简写,但是在字符串的比较上有所不同,下文会提到
2.使用字符串的引用构造
String str3=new String(str1);
3.使用字符数组构造
char[] arr={'h','e','l','l','o'};
String str4=new String(arr);
注意:
一.String是引用类型,存储的不是字符串本身
上图是String的部分成员(其余成员本文不提),可以得到下面三个信息:
1.String由final修饰,表示不能被继承
2.字符串实际上是保存在value属性里
所以内存里的String实际上是这样的
String str2=new String("hello world");
String str3=new String(str2);
3.String实现了Comparable接口(其余接口以后再谈)
二.由""包含的也是String类型,可以调用String类的方法
String str4=new String("hello");//创建String类对象,其value值为hello
System.out.println("hello".length());//value="hello"的String对象引用调用了length()方法
System.out.println("hello".equals(str4));//value="hello"的String对象引用调用了equals()方法
//输出结果
5
true
1.2 字符串的比较
字符串的比较是经常使用的方法,Java中提供了四种方式
1.2.1 ==比较是否引用同一对象
对于基本数据类型变量,存储的内容就是它们的值,“==”比较的就是变量的值是否相同
对于引用数据类型变量,存储的内容是它们指向对象的地址,“==”比较的就是变量是否指向了同一个对象
String str1="hello";
String str2="hello";
System. out.println(str1==str2);//比较str1和str2的地址
String str3=new String("hello");
String str4=new String("hello");
System. out.println(str3==str4);//比较str3和str4的地址
很容易就可以猜到,输出的结果都是false,但答案并不如我们想的那样
来一起分析一下它们的内存图
上文中是不是有提到由“”包括的也是String引用类型,这样的对象在堆中只有一份
所以str1="hello",str2="hello"实际上是让str1和str2的引用指向了这个对象
再来看看str3和str4
1.2.2 equals方法
大多数情况下,我们想要比较的肯定不是两个String对象的地址,而是比较它们value的值是否相同
String类重写了父类Object的equals方法,源码如下
public boolean equals(Object anObject) {
//如果引用的同一个对象返回true
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
//比较字符串长度是否相同,不同返回false
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
//比较每个字符是否相同
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
equals是String类的成员方法,需要实例化出对象,然后通过该对象的引用去调用
String str1="hello";
String str2=new String("hello");
System. out.println(str1.equals(str2));
System. out.println(str1.equals("hello"));
//输出结果
true
true
实际上两个true表示的含义是不一样的,str1和str2因为value的值相同,返回结果为true;
str1和"hello"因为指向的是同一个对象所以返回true
1.2.3 compareTo方法
上文提到过,String类实现了comparable接口,所以String类重写了compareTo方法
下面是compareTo方法的源码
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
//获取较短字符串的长度
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
//在min长度里比较每个字符是否相同,如果不同返回字符差值
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
//如果在min长度里每个字符都相同,返回两个字符串长度的差值
return len1 - len2;
}
可以看到,compareTo方法和C语言中的strcmp函数实现逻辑是一样的,下面进行使用该方法的例子
String str1="hello";
String str2=new String("hello world");
System. out.println(str1.compareTo(str2));//str1是str2的子串,返回长度差值
//输出结果
-6
1.2.4 compareToIgnoreCase方法
与compareTo方法不同的是,compareToIgnoreCase方法会忽略字母大小写
下面是compare方法的源码
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
//获得较短字符串的长度min
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
//将两个不同的字符全部转换成大写比较
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
//将两个不同的字符全部转换成小写比较
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
//如果前面的内容相同,返回长度的差值
return n1 - n2;
}
可以看到,compareToIgnoreCase方法的实现逻辑和compareTo方法是一样的,不过在比较字符时会试试转换成大写或小写看看是否相同
1.3 字符串的查找
字符串的查找也是我们在刷题的时候经常使用的操作
下面是常用的字符串查找的方法
方法 | 功能 |
char charAt(int index) | 查找字符串中下标为index的字符,如果index<0或者index大于字符串的长度会抛异常,否则返回该字符 |
int indexOf(int ch) | 返回ch第一次出现的位置,没有返回-1 |
int indexOf(int ch, int fromIndex) | 从fromIndex位置开始找ch第一次出现的位置,没有返回-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 |
下面进行部分方法的演示,各位看官自己操作一遍就行
String str1="hello world";
System. out.println(str1.charAt(6));//输出下标为6的字符
System. out.println(str1.indexOf('l'));//输出'l'第一次出现的下标
System. out.println(str1.indexOf('l',3));//从下标为3的元素开始查找,输出'l'第一次出现的下标
System. out.println(str1.lastIndexOf('l'));//从字符串末尾开始查找,输出'l'第一次出现的下标
输出结果如下
1.4 字符串的转化
1.4.1 其他数据类型转字符串
前面提到过String.valueOf()方法,用于把int类型转化为String类,实际上valueOf()方法有很多重载,不止可以转int 类型
简单地写个代码证明下
System. out.println(String. valueOf(123));//int类型转字符串
System. out.println(String. valueOf(126.7));//double类型转字符串
System. out.println(String. valueOf(true));//boolean类型转字符串
System. out.println(String. valueOf('4'));//字符型转字符串
最绝的是这个重载,它可以接收任意类并且把它转成字符串!!!
比如我们定义一个学生类
//定义学生类
class Student {
String name="小明";
int age=10;
}
public class Test {
public static void main(String[] args) {
Student st=new Student();//创建学生对象
System.out.println(String.valueOf(st));//使用valueOf把该对象转化为字符串输出
}
}
输出结果
这肯定不是我们想要的结果啊,我们想要输出的是这个学生的信息
实际上根据valueOf()方法的源码我们也可以知道,它的内部调用了toString()方法,而父类Object的toString方法返回的是类名+@+hashCode,所以只要在类的内部重写toString方法就行
class Student {
String name="小明";
int age=10;
//重写String方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
这样就可以看到我们想要的结果了
1.4.2 字符串转数值类型
先上代码看看
int a=Integer. parseInt("123");//字符串转int类型
System. out.println(a);//输出123
double b=Double. parseDouble("126.7");//字符串转double类型
System. out.println(b);//输出126.7
int c=Integer. valueOf("12",16);//字符串转int类型,按照16进制
System. out.println(c);//输出18
整型,浮点型和布尔型的包装类都有prase+类名()方法把字符串转换成对应的类型
也有valueOf()方法,实际上valueOf方法内部也是调用的第一种方法
上述两种方法还可以按照radix进制把字符串转成整型
1.4.3 大小写转换
我们刚才在compareToIgnoreCase方法那里见过了这两个方法,分别是toLowerCase()和toUpperCase()
String str1="hello WORLD";
String str2=str1 .toLowerCase(Locale. ROOT);//str2存储str1转换成小写的内容
String str3=str1. toUpperCase(Locale. ROOT);//str3存储str1转换成大写的内容
//输出str2,str3
System. out.println(str2);
System. out.println(str3);
//输出结果
hello world
HELLO WORLD
1.4.4 字符串转数组
通过字符数组可以初始化String对象,同样的,通过String对象的引用也可以创建数组
String类提供了toCharArray()方法,使用方式如下
String str="hello";
char[] chs=str.toCharArray();//把字符串转换成数组
//输出字符数组中的元素
for(char e:chs) {
System. out.println(e+" ");
}
//输出结果
h e l l o
1.4.5 格式化字符串
使用String类的成员方法format(),见下例
String str=String. format("%d-%d-%d",2023,3,26);
System. out.println(str);
//输出结果
2023-3-26
format格式化的使用的符号和printf是一样的,具体参照http://t.csdn.cn/GdQCj
1.5 字符串替换
使用一个指定的新的字符串替换掉已有的字符串数据,具体方法如下
方法 | 功能 |
String replace(char oldChar, char newChar) | 将oldChar全部替换成newChar |
String replaceFirst(String regex, String replacement) | 将第一个regex替换成replacement |
String replaceAll(String regex, String replacement) | 将所有的regex替换为replacement |
String replace(CharSequence target, CharSequence replacement) | 将实现了CharSequence接口的target全部替换成replacement |
第4个方法中的接口我们看到过,String类实现了这个接口,在此处不提
下面来看一下这些方法的使用
//方法1
public static void main(String[] args) {
String str1="hello everybody";
String str2=str1.replace('e','E');//将str1中的'e'全部替换为'E'
System.out.println(str2);
}
//输出结果
hEllo EvErybody
//方法2
public static void main(String[] args) {
String str1="hello everybody";
String str2=str1.replaceFirst("e","8899");//将第一个"e"替换成“8899”
System.out.println(str2);
}
//输出结果
h8899llo everybody
//方法3
public static void main(String[] args) {
String str1="hello everybody";
String str2=str1.replaceAll("e","8899");//将全部的"e"替换成"8899"
System.out.println(str2);
}
//输出结果
h8899llo 8899v8899rybody
//方法4
public static void main(String[] args) {
String str1="hello everybody";
String str2=str1.replace("l","8899");//把"l"全部替换成"8899"
System.out.println(str2);
}
//输出结果
he88998899o everybody
1.6 字符串的拆分
可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串。具体方法如下
方法 | 功能 |
String[] split(String regex, int limit) | 将字符串拆成limit个部分 |
String[] split(String regex) | 将字符串全部拆分 |
来看代码示例
//方法1演示
public static void main(String[] args) {
String str1="one two three four";
String[] str2=str1.split(" ",2);
for(String s:str2) {
System.out.println(s);
}
}
//输出结果
one
two three four
//方法2演示
public static void main(String[] args) {
String str1="one two three four";
String[] str2=str1.split(" ");
for(String s:str2) {
System.out.println(s);
}
}
//输出结果
one
two
three
four
1.如果分隔符是 "|","*","+",".",前面加上 "\\"(因为要进行转义,在字符串中转义字符'\'要使用"\\"表示)
2.如果分隔符是"\\",前面加上"\\\"(原因同上)
public static void main(String[] args) {
String IP="255.196.201.34";
String[] strs=IP.split("\\.");
for(String s:strs) {
System.out.println(s);
}
}
//输出结果
255
196
201
34
public static void main(String[] args) {
String IP="255\\196\\201\\34";//字符串内的'\'不能只写一个,否则会被当成转义字符
String[] strs=IP.split("\\\\");
for(String s:strs) {
System.out.println(s);
}
}
//输出结果
255
196
201
34
3.如果使用多个分隔符进行拆分,分隔符之间用"|"连接
public static void main(String[] args) {
String IP="255=196 201 34=69";
String[] strs=IP.split("=| ");//使用=或' '划分字符串
for(String s:strs) {
System.out.println(s);
}
}
//输出结果
255
196
201
34
69
1.7 字符串截取
从一个完整的字符串之中截取出部分内容,可用方法如下
方法 | 功能 |
String substring(int beginIndex) | 从指定下标截取到字符串末尾 |
String substring(int beginIndex, int endIndex) | 左闭右开区间,截取字符串部分内容 |
代码示例
public static void main(String[] args) {
String str="It'fine today!";
String str2=str.substring(2);//从下标为2的元素开始截取
String str3=str.substring(2,8);//从下标为2的元素开始截取,到下标为7截止
System.out.println(str2);
System.out.println(str3);
}
//输出结果
'fine today!
'fine
1.8 去除字符串两端空格
方法如下
public String trim()
示例
public static void main(String[] args) {
String str=" hello myself abc ";
System.out.println(str.trim());
}
//运行结果
hello myself abc
二.字符串的不可变性
刚才所有的操作方法都是产生了一个经过原字符串转化后的新字符串,而不是对原字符串本身进行操作
public static void main(String[] args) {
String str1="hello world";
System.out.println(str1.toUpperCase(Locale.ROOT));
System.out.println(str1);
}
//输出结果
HELLO WORLD
hello world
来看一下Java中对String类的介绍
标红的部分大意就是String对象一经创建不可更改,Java中对String类的定义也诠释了这一句话:
你们肯定会有疑问:
final那么明显,被final修饰的变量不可以更改,放着final不标红,为啥要标红private?
被final修饰的变量按照类型可以分为两种:
1.基本数据类型的变量,被final修饰后相当于常量
2.引用类型的变量,被final修饰后,该变量指向的地址不可以更改
我想强调的意思是,引用变量存储的是对象的地址,这个地址不可以更改,但是对象是可以改变的
有点绕对吧!下面举个例子
final char[] chs={'h','e','l','l','o'};
chs[2]='E';
for(char e:chs) {
System. out.print(e+" ");
}
//输出结果
h e E l o
我们可以画个内存图来看一下
final修饰的是chs,就是说chs的内容0X99不能更改,但是0X99指向的对象是可以修改的
所以和chs一样,value字符数组也是可以更改的,但问题的关键在于String类不仅把value设成了private成员,并且没有提供方法让我们得到value,所以value自然是不可以更改的
三.StringBuilder和StringBuffer
3.1 StringBuilder和StringBuffer类的介绍
String类具有不可变性,所以每次对String对象进行操作都会产生新的对象。来看下面的代码进行验证:
public class Test {
public static void main(String[] args) {
String str="keep ";
str+="myself";
System.out.println(str);
}
}
我们可以使用反汇编来看看编译器都进行了什么操作?
来看看StringBuil的toString方法---又创建了一个String对象作为返回值
所以直接对String对象进行修改操作的代价是非常大的,于是Java又提供了StringBuilder和StringBuffer类,能够直接对原对象进行修改,不需要创建新的对象
现在我们更改这三个类的对象,观察损耗的时间
public class Test {
public static void main(String[] args) {
//String类测试
long start=System.currentTimeMillis();//记录执行到当前语句的时间戳,单位是毫秒
String s="";
for (int i = 0; i < 10000; i++) {
s+=i;
}
long end=System.currentTimeMillis();
System.out.println(end-start);
//StringBuilder类测试
start=System.currentTimeMillis();
StringBuilder sb=new StringBuilder("");
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
end=System.currentTimeMillis();
System.out.println(end-start);
//StringBuffer类测试
start=System.currentTimeMillis();
StringBuffer sf=new StringBuffer("");
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
end=System.currentTimeMillis();
System.out.println(end-start);
}
}
来看一下测试结果
所以涉及到对字符串的更改,尽量使用StringBuilder和StringBuffer类
3.2 StringBuffer和StringBuilder的方法
StringBuffer和StringBuilder类的成员方法基本都相同,下面均以StringBuilder为例
先来看看构造方法的使用
StringBuilder() | StringBuilder的无参构造 |
StringBuilder(String str) | 使用字符串构造StringBuilder对象 |
StringBuffer和StringBuilder类有很多方法与String类的使用规则是一样的
方法 | 功能 |
char charAt(int index) | 获取index位置的字符 |
int length() | 获取字符串的长度 |
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最后一次出现的位置 |
为了便于修改,StringBuilder类还增加了其他的方法
StringBuilder append(String str) | 在StringBuilder对象后面追加str字符串 |
StringBuilder insert(int offset, String str) | 在offset下标插入str或8种基本类型或Object对象 |
StringBuilder delete(int start, int end) | 删除[start, end)区间内的字符 |
StringBuilder deleteCharAt(int index) | 删除下标为index的字符 |
StringBuilder replace(int start, int end, String str) | 将[start, end)位置的字符替换为str |
StringBuilder reverse() | 反转字符串 |
对这两个类型的对象进行修改操作时,操作的都是原来的对象
public static void main(String[] args) {
StringBuilder s=new StringBuilder("hello world");
s.replace(0,5,"sss");
System.out.println(s);
}
//输出结果
sss world
3.3 StringBuilder和StringBuffer的区别
虽然这两个类的方法的使用是一样的,但是StringBuffer类的方法(比如append)前面有这样一个修饰
synchronized :可以理解为是一把锁,能保证同一时刻只有一个线程执行,可以保证线程安全
很喜欢一个例子:
(先让我笑三秒)
因为厕所只能同时被一个人使用,为了防止被偷窥,就要在门上添加一把锁防止别人进来
最后,String类和StringBuilder,StringBuffer类是可以相互转换的
StringBuffer或StringBuilder类----》String类,使用toString方法
String类----》StringBuffer或StringBuilder类,使用StringBuilder/StringBuffer类的构造方法即可