1 String 概述
1.1 基本介绍
-
一个字符串是由多个字符组成的一串数据(字符序列,字符数组)
-
String类代表字符串,Java 程序中的所有字符串字面值(如 “abc” ,“你好”,“の”)都作为此类的实例实现
-
在java.lang包下,是java核心类,最常用类,但是不属于基本数据类型,引用类型
-
String类提供了字符串表示、比较、查找、截取、大小写转换等各种针对字符串的操作
1.2 构造方法
- 空字符串""
- byte[] -----> String
- char[] -----> String
//空字符串 ""
public String()
//利用字节数组,创建字节数组所表示的字符串
// 1. 字符 -> 数值形式 'a' -> 97
// 2. 所以可以用多个字节值,表示多个字符——>即字符序列 public
String(byte[] bytes)
//利用字节数数组的一部分,创建字符序列, 从byte数组的offset开始的length个字节值
public String(byte[] bytes,int offset,int length)
//利用一个字符数组创建字符数组,代表的字符序列
public String(char[] value)
// 创建value字符数组中,从第offset位置开始的count个字符,所代表的字符串对象
public String(char[] value,int offset,int count)
//知道即可
public String(String original)
实例:
public class Demo1 {
public static void main(String[] args) {
// //空字符串 ""
// public String()
String s1 = new String();
System.out.println("s1 = " + s1);
// //利用字节数组,创建字节数组所表示的字符串
// // 1. 字符 -> 数值形式 'a' -> 97
// // 2. 所以可以用多个字节值,表示多个字符——>即字符序列 public
// String(byte[] bytes)
byte[] bytes = new byte[]{97, 98, 99, 100};
String s2 = new String(bytes);
System.out.println("s2 = " + s2);
// //利用字节数数组的一部分,创建字符序列, 从byte数组的offset开始的length个字节值
// public String(byte[] bytes,int offset,int length)
String s3 = new String(bytes, 1, 2);
System.out.println("s3 = " + s3);
// //利用一个字符数组创建字符数组,代表的字符序列
// public String(char[] value)
char[] chars = {'J', 'a', 'v', 'a'};
String s4 = new String(chars);
System.out.println("s4 = " + s4);
// // 创建value字符数组中,从第offset位置开始的count个字符,所代表的字符串对象
// public String(char[] value,int offset,int count)
char[] value = {'a','b','c','H', 'e', 'l', 'l', 'o'};
String s5 = new String(value, 3, 5);
System.out.println("s5 = " + s5);
// //知道即可
// public String(String original)
String s6 = new String("hello");
System.out.println("s6 = " + s6);
}
}
2 String特点(重点)
2.1 String对象不可变
对象一旦被创建后,对象所有的状态及属性在其生命周期内不会发生任何变化。
1.请键盘录入一个任意字符串s,并用一个temp字符串引用也指向它
这个时候修改temp字符串的内容,请问s字符串的内容会随之改变吗?
/*
* 对象一旦被创建后,对象所有的状态及属性在其生命周期内不会发生任何变化。
1.请键盘录入一个任意字符串s,并用一个temp字符串引用也指向它
这个时候修改temp字符串的内容,请问s字符串的内容会随之改变吗?
不会
*/
public class Demo {
public static void main(String[] args) {
//创建Scanner对象
Scanner scanner = new Scanner(System.in);
//nexiLine接收数据
String s = scanner.nextLine();
// 使用temp引用指向
String temp = s;
//修改temp
temp = "aaa";
//打印temp和string
System.out.println("temp = " + temp);
System.out.println("s = " + s);
}
}
原因与本质
- String是一个final类,不能被继承, 代表不可变的字符序列
- 字符串是常量,用双引号引起来,他们的值在创建之后不可更改
- String对象的内容是存储在字符数组value[ ]中的
2.2 字符串常量池
字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串使用的非常多
JVM为了提高性能和减少内存的开销,在实例化字符串对象的时候进行了一些优化:使用字符串常量池。
首先要明确,Java的双引号引起来的字面值常量字符串,它们都是对象。这些对象比较特殊,程序在编译时期就能确定它们的值
每当创建字符串常量对象时,JVM会首先检查字符串常量池,如果该字符串对象引用已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串对象引用不存在于常量池中,就会实例化该字符串并且将其引用放到常量池中。
// 当常量池中已有字符串对象的引用时,不会在堆上再创建, 而使用同一引用
String s1 = "abc";
String s2 = "abc";
2.2.1 String两种实例化方式
两种方式
- 直接赋值
String s = "abc"
- 构造方法
构造方法 String s = new String("abc")
第一种 先直接赋值 再构造
String s = "abc";
String s1 = new String("abc")
第二种 先构造 再直接赋值
String s1 = new String("abc")
String s = "abc";
总结:
- 先String s = new String(“abc”), 再String s1 = “abc”. new String 的时候会创建2个对象, 直接赋值的时候, 直接用的是常量池的引用, 不再创建新的字符串对象了
- String s1 = “abc”, 再String s = new String(“abc”), 直接赋值的方式会创建一个对象, new String的时候会创建1个对象
2.2.2 字符串常见问题与练习
2.2.2.1 字符串比较
/*
* 字符串比较
* */
public class Demo4 {
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2);//false
System.out.println(s1.equals(s2));//true
String s3 = new String("hello");//创建新对象
String s4 = "hello";//不i会创建新对象,使用s1在字符串常量池的引用
System.out.println(s3 == s4);//false
System.out.println(s3.equals(s4));//true
String s5 = "hello";
String s6 = "hello";
System.out.println(s5 == s6);//true
System.out.println(s5.equals(s6));//true
}
}
==与equals方法有什么区别?
- == ,对于基本数据类型而言,比较的是内容,对于引用数据类型而言,比较的是引用变量,即所指向的地址
- equals方法是Object的方法,默认是比较2个对象的地址,若要比较内容,应当重写父类方法
String中重写的equals方法
2.2.2.2 字符串拼接
/*字符串拼接*/
public class Demo5 {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1 == s2);// true
System.out.println(s1 == s3);// true
System.out.println(s1 == s4);// false
System.out.println(s1 == s9);// false
System.out.println(s4 == s5);// false
}
}
进行字符串拼接的时候有2种情况
- 当参与字符串拼接对的2个字符串,只要有1个引用变量的形式出现时,则会在堆上创建新的字符串对象.
- 原因是因为参与了运算,无法在编译期确定其值,就不能在编译时期加入常量池
- 只有参与字符串拼接的2个字符串都是字面值常量的时候
- 如果常量池中已有该字符串对象的引用,则返回常量池中的引用
- 如果常量池中没有,则在堆上创建,并把引用放入常量池
3 String API
3.1 判断功能
用来比较字符串的内容,注意区分大小写
boolean equals(Object obj)
忽略字符串大小写比较字符串内容,常见用于比较网址URL
boolean equalsIgnoreCase(String str)
判断当前字符串对象是否包含,目标字符串的字符序列
boolean contains(String str)
判断当前字符串对象,是否已目标字符串的字符序列开头
boolean startsWith(String str)
判断当前字符串,是否以目标字符串对象的字符序列结尾,常用于确定文件后缀名格式
boolean endsWith(String str)
判断一个字符串,是不是空字符串
boolean isEmpty()
/**
* @package: _01code._01string._03api
* @description:
* @author: Yunyang
* @date: 2023/10/30 11:26
* @version:1.0
**/
public class Demo {
public static void main(String[] args) {
// 用来比较字符串的内容,注意区分大小写
// boolean equals(Object obj)
String s1 = "hello";
String s2 = new String("hello");
System.out.println(s1.equals(s2)); //true
// 忽略字符串大小写比较字符串内容,常见用于比较网址URL
// boolean equalsIgnoreCase(String str)
String s3 = "HELLO";
System.out.println(s1.equals(s3)); //false
System.out.println(s1.equalsIgnoreCase(s3)); //true
// 判断当前字符串对象是否包含,目标字符串的字符序列
// boolean contains(String str)
String str = "abcdef";
System.out.println("str.contains(\"def\") = " + str.contains("def")); //true
// 判断当前字符串对象,是否已目标字符串的字符序列开头
// boolean startsWith(String str)
System.out.println("str.startsWith(\"ab\") = " + str.startsWith("ab")); //true
// 判断当前字符串,是否以目标字符串对象的字符序列结尾,常用于确定文件后缀名格式
// boolean endsWith(String str)
System.out.println("str.endsWith(\"ef\") = " + str.endsWith("ef")); //ture
System.out.println("str.endsWith(\".exe\") = " + str.endsWith(".exe")); //false
// 判断一个字符串,是不是空字符串
// boolean isEmpty()
System.out.println("str.isEmpty() = " + str.isEmpty()); //false
System.out.println("\"\".isEmpty() = " + "".isEmpty()); //true
}
}
3.2 获取功能
获取当前字符串对象中,包含的字符个数 "abcd"
int length()
获取字符串对象代表字符序列中,指定位置的字符 从0开始"abcd"
char charAt(int index)
在当前字符串对象中查找指定的字符,如果找到就返回字符,首次出现的位置,如果没找到返回-1
也可以填字符 "abcdb"
int indexOf(int ch)
指定从当前字符串对象的指定位置开始,查找首次出现的指定字符的位置,(如果没找到返回-1)
可以填入字符
int indexOf(int ch,int fromIndex)
查找当前字符串中,目标字符串首次出现的位置(如果包含),找不到,返回-1
这里的位置是指目标字符串的第一个字符,在当前字符串对象中的位置 "abcdefg"
int indexOf(String str)
指定,从当前字符串对象的指定位置开始,查找首次出现的指定字符串的位置(如果没找到返回-1)
这里的位置是指目标字符串的第一个字符,在当前字符串对象中的位置
int indexOf(String str,int fromIndex) ,
返回字符串,该字符串只包含当前字符串中,从指定位置开始(包含指定位置字符)到结束的那部分字符串 "abcdef"
String substring(int start)
返回字符串,只包含当前字符串中,从start位置开始(包含),到end(不包含)指定的位置的字符串 [ , )
String substring(int start,int end)
3.3 转换功能
获取一个用来表示字符串对象字符序列的,字节数组
byte[] getBytes()
获取的是用来表示字符串对象字符序列的,字符数组
char[] toCharArray()
把字符数组转换成字符串
static String valueOf(char[] chs)
把各种基本数据类型和对象转换成字符串 100---> "100"
static String valueOf(int i/double...)
把字符串全部转化为小写
String toLowerCase()
把字符串全部转换为大写
String toUpperCase()
字符串拼接,作用等价于 + 实现的字符串拼接
String concat(String str)
/**
* @package: _01code._01string._03api
* @description: 转换功能
* @author: Yunyang
* @date: 2023/10/30 14:36
* @version:1.0
**/
public class Demo3 {
public static void main(String[] args) {
// 获取一个用来表示字符串对象字符序列的,字节数组
// byte[] getBytes()
String s = "abcd";
byte[] bytes = s.getBytes();
System.out.println("bytes = " + Arrays.toString(bytes));
// 获取的是用来表示字符串对象字符序列的,字符数组
// char[] toCharArray()
char[] chars = s.toCharArray();
System.out.println("chars = " + Arrays.toString(chars));
// 把字符数组转换成字符串
// static String valueOf(char[] chs)
String s1 = String.valueOf(chars);
// 把各种基本数据类型和对象转换成字符串 100---> "100"
// static String valueOf(int i/double...)
String s2 = String.valueOf(100);// "100"
String s3 = String.valueOf(3.14);// "3.14"
String s4 = String.valueOf(true);//"true"
// 把字符串全部转化为小写
// String toLowerCase()
// 把字符串全部转换为大写
// String toUpperCase()
String s5 = s.toUpperCase();
System.out.println("s5 = " + s5);
// 字符串拼接,作用等价于 + 实现的字符串拼接
// String concat(String str)
String concat = s.concat("ss");
System.out.println("concat = " + concat);
}
}
3.4 其他功能
String类的替换功能
在新的字符串中,用新(new)字符,替换旧(old)字符 "abcd"
String replace(char old,char new)
在新的字符串中,用新的字符串(new), 替换旧(old)字符串
String replace(String old, String new)
在新的字符串中,去掉开头和结尾的空格字符 "abc" "abc "
String trim()
分隔功能
将字符串按照符号分隔成字符串数组 "a,b,c,d"
String[] split(String re)
String类的比较功能
int compareTo(String str)
int compareToIgnoreCase(String str)
/**
* @package: _01code._01string._03api
* @description: 其他功能
* @author: Yunyang
* @date: 2023/10/30 15:03
* @version:1.0
**/
public class Demo4 {
public static void main(String[] args) {
// String类的替换功能
// 在新的字符串中,用新(new)字符,替换旧(old)字符 "abcd"
// String replace(char old,char new)
String s = "abcdef";
String s1 = s.replace('a','A');
System.out.println("s1 = " + s1);
// 在新的字符串中,用新的字符串(new), 替换旧(old)字符串
// String replace(String old, String new)
String s2 = s.replace("ef", "yyds");
System.out.println("s2 = " + s2);
// 在新的字符串中,去掉开头和结尾的空格字符 "abc" "abc "
// // String trim()
String s3 = " aBc ";
System.out.println("s3.trim() = " + s3.trim());
// // 分隔功能
// // 将字符串按照符号分隔成字符串数组 "a,b,c,d"
// // String[] split(String re)
String s4 = "a,b,c,d";
String[] split = s4.split(",");
System.out.println(Arrays.toString(split));
// // String类的比较功能
// // int compareTo(String str)
// // int compareToIgnoreCase(String str)
String s5 = "ab";
String s6 = "aB";
//
int i = s5.compareTo(s6);
int j = s5.compareToIgnoreCase(s6);
System.out.println("i = " + i);
System.out.println("j = " + j);
}
}
4 自然排序
4.1 Comparable接口
此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo
方法被称为它的自然比较方法
实现此接口的对象列表(和数组)可以通过 Collections.sort
(和 Arrays.sort
)进行自动排序
-
实现此接口的类,其对象数组(array)或对象容器(collection)
- 就可以通过**Arrays.sort()或Collections.sort()**进行自动排序
-
对于实现该接口的A类来说,其对象a1.compareTo(a2)方法返回值
- 小于0,表示a1对象小于a2,在自然排序中处于前面的位置
- 大于0,表示a1对象大于a2,在自然排序中处于后面的位置
- 等于0,表示a1对象等于a2
自定义类实现自然排序:
-
实现Comparable接口
-
重写compareTo方法
/**
* @package: _01code._01string._04sort
* @description:
* @author: Yunyang
* @date: 2023/10/30 16:11
* @version:1.0
**/
/*
* 练习:
定义一个学生类,让其按照学生的年龄的大小,从小到大进行排序
*/
public class Demo2 {
public static void main(String[] args) {
// 待排序数组
Student[] students = new Student[5];
students[0] = new Student("zs", 21, 66);
students[1] = new Student("ls", 20, 77);
students[2] = new Student("ww", 19, 99);
students[3] = new Student("zl", 28, 88);
students[4] = new Student("kun", 21, 59);
System.out.println("排序前:" + Arrays.toString(students));
//排序 sort方法
Arrays.sort(students);
System.out.println("排序后:" + Arrays.toString(students));
}
}
/*
自定义类实现自然排序:
- 实现Comparable接口
- 重写compareTo方法
*/
class Student implements Comparable<Student> {
String name;
int age;
int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
//比较规则
//按照年龄从小到大排序
// return this.age - o.age;
// 默认排序
// 年龄相同的情况下,按照分数从高到低排序
if (this.age == o.age) {
return o.score - this.score;
}
// // 按照年龄从大到小排序
return o.age - this.age;
}
}
4.2 Comparator接口
在排序时需要注意一个比较特殊的方法,带比较器的Arrays.sort方法,
即sort(T[] a, Comparator<? super T> c)
根据指定比较器产生的顺序对指定对象数组进行排序。其中Comparator接口的实现类对象就是比较器,该对象通过compare方法传入比较的规则
表示传入比较规则的int compare(T o1, T o2)方法:
该方法可以看成是o1-o2,如果方法返回负数,o1< o2,相反则大于,只有当方法返回0时,才表示对象相等
三种方式去实现自然排序:
- 手写接口类实现
- 匿名内部类
- lambda表达式
/**
* @package: _01code._01string._04sort
* @description:
* @author: Yunyang
* @date: 2023/10/30 16:33
* @version:1.0
**/
/*
* - 手写接口类实现
- 匿名内部类
- lambda表达式
*/
public class Demo3 {
public static void main(String[] args) {
// 创建学生对象
Student2 s1 = new Student2("zs", 20, 99);
Student2 s2 = new Student2("ls", 18, 98);
Student2 s3 = new Student2("ww", 19, 89);
Student2 s4 = new Student2("zl", 22, 95);
Student2 s5 = new Student2("zsd", 17, 89);
// 填充数组
Student2[] student2s = {s1, s2, s3, s4, s5};
// 排序前
System.out.println("排序前:");
System.out.println(Arrays.toString(student2s));
// 排序 1.使用带比较器的sort方法
// Arrays.sort(student2s,new MyComparator());
//2.匿名内部类
// Arrays.sort(student2s, new Comparator<Student2>() {
// @Override
// public int compare(Student2 o1, Student2 o2) {
// return o1.getScore() - o2.getScore();
// }
// });
//3.lambda表达式
Arrays.sort(student2s, (stu1, stu2) -> stu1.getScore() - stu2.getScore());
// 排序后
System.out.println("排序后:");
System.out.println(Arrays.toString(student2s));
}
}
class Student2 {
String name;
int age;
int score;
public Student2(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getScore() {
return score;
}
@Override
public String toString() {
return "Student2{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
// //手写实现类
// class MyComparator implements Comparator<Student2>{
//
// @Override
// public int compare(Student2 o1, Student2 o2) {
// // 按照年龄从小到大排序
// return o1.getAge() - o2.getAge();
// }
// }
4 可变长字符串
4.1 StringBuffer构造方法
public StringBuffer() // 默认容量是16
public StringBuffer(int capacity)// 容量是capacity
public StringBuffer(String str)// str的长度+16
/**
* @package: _01code._01string._05stringbuffer._02basic
* @description:
* @author: Yunyang
* @date: 2023/10/30 17:13
* @version:1.0
**/
public class Demo {
public static void main(String[] args) {
// public StringBuffer() // 默认容量是16
StringBuffer sb1 = new StringBuffer();
// public StringBuffer(int capacity)// 容量是capacity
StringBuffer sb2 = new StringBuffer(20);
// public StringBuffer(String str)// str的长度+16
StringBuffer sb3 = new StringBuffer("abc");
System.out.println(sb3);
}
}
4.2 StringBuffer成员方法
获取功能
public int capacity() 返回当前容量,数组的长度,理论值
public int length() 返回长度(字符的个数),实际值
添加功能
public StringBuffer append(String s) 将指定的字符串(其他类型有重载方法)追加到此字符序列的尾部
在指定位置把任意类型的数据插入到字符串缓冲区里面
public StringBuffer insert(int offset,String str)
删除功能
public StringBuffer deleteCharAt(int index):删除指定位置的字符
public StringBuffer delete(int start,int end):删除从指定位置开始指定位置结束的内容
替换功能
使用给定String中的字符替换词序列的子字符串中的字符
public StringBuffer replace(int start,int end,String str)
反转功能
public StringBuffer reverse():将此字符序列用其反转形式取代,返回对象本身
/**
* @package: _01code._01string._05stringbuffer._02basic
* @description:
* @author: Yunyang
* @date: 2023/10/30 17:17
* @version:1.0
**/
public class Demo2 {
public static void main(String[] args) {
// 获取功能
// public int capacity() 返回当前容量,数组的长度,理论值
StringBuffer sb = new StringBuffer();
System.out.println("sb.capacity() = " + sb.capacity());
// public int length() 返回长度(字符的个数),实际值
System.out.println("sb.length() = " + sb.length());
// 添加功能
// public StringBuffer append(String s) 将指定的字符串(其他类型有重载方法)追加到此字符序列的尾部
StringBuffer sb2 = sb.append("Hello");
System.out.println("sb2 = " + sb2);
// 在指定位置把任意类型的数据插入到字符串缓冲区里面
// public StringBuffer insert(int offset,String str)
StringBuffer sb3 = sb.insert(5, "World");
System.out.println("sb3 = " + sb3);
// 删除功能
// public StringBuffer deleteCharAt(int index):删除指定位置的字符
StringBuffer sb4 = sb3.deleteCharAt(0);
System.out.println("sb4 = " + sb4);
// public StringBuffer delete(int start,int end):删除从指定位置开始指定位置结束的内容
StringBuffer sb5 = sb3.delete(0,4);
System.out.println("sb5 = " + sb5);
// 替换功能
// 使用给定String中的字符替换词序列的子字符串中的字符
// public StringBuffer replace(int start,int end,String str)
StringBuffer sb6 = sb3.replace(5, 9, "Cat");
System.out.println("sb6 = " + sb6);
// 反转功能
// public StringBuffer reverse():将此字符序列用其反转形式取代,返回对象本身
StringBuffer sb7 = sb6.reverse();
System.out.println("sb7 = " + sb7);
}
}
4.3 常见问题
String, StringBuffer之间的相互转换
/**
* @package: _01code._01string._05stringbuffer._02basic
* @description:
* @author: Yunyang
* @date: 2023/10/30 17:21
* @version:1.0
**/
public class Demo3 {
public static void main(String[] args) {
String s = "abc";
//String--->StringBuffer
//利用构造方法
StringBuffer sb = new StringBuffer(s);
System.out.println("sb = " + sb);
//StringBuffer--->String
//利用toString方法
String s1 = sb.toString();
System.out.println("s1 = " + s1);
System.out.println(Integer.MAX_VALUE+1);
System.out.println(Byte.MAX_VALUE+1);
}
}
String, StringBuffer和StringBuilder有啥区别
StringBuffer和StringBuilder从效率上来说哪个更快?
- 和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改
- 并且不产生新的未使用对象,不会产生效率问题和空间浪费问题
- StringBuffer是线程安全的,StringBuilder是线程不安全的
- StringBuilder的效率会比StringBuffer效率更高,单线程的程序推荐使用StringBuilder
- 在多线程的程序中,应该优先考虑使用StringBuffer,安全性要更重要
- 它们的效率都比String高很多