目录
前言
一、常用方法
(一)字符串构造
(二)String 对象的比较
1. == 比较是否引用同一个对象
2.boolean equals(Object anObject) 方法:按照字典序比较
3.int compareTo(String str)方法:按照字典序进行比较
4.int compareTolgnoreCase(String str)方法:与compareTo方式相同,但是忽略大小写比较
(三)字符串查找
(四)转化
1.数值和字符串转化
2.大小写转换
3.字符串转数组
4.格式化
(五)字符串替换
(六)字符串拆分
(七)字符串截取
(八)去掉字符串中的左右空格,保留中间空格
(九)字符串常量池
1.创建对象的思考
2.字符串常量池(StringTable)
3.再谈 String 对象创建
(十)字符串的不可变性
1.String 类在设计时就是不可改变的,String 类实现描述中已经说明了
2.所有涉及到可能修改字符串内容的操作都是创建一个新对象 ,改变的是新对象
(十一)字符串修改
二、StringBuilder 和 StringBuffer
(一)StringBuilder 和 StringBuffer的介绍
(二)面试题
1.String、StringBuilder、StringBuffer 的区别
2.以下总共创建了多少个 String 对象【前提不考虑常量池是否存在】
总结
前言
String 类在 Java 中是极为特殊但又经常使用的一个类,所以我们需要重点学习
一、常用方法
(一)字符串构造
String 类提供的构造方法非常多,常用的就以下三种:
public static void main1(String[] args) {
//使用常量串构造
String s1 = "hello world!";
//可以直接输出对象的原因就是在 String 类中重写了 toSting 方法
System.out.println(s1);
//使用 new String 对象
String s2 = new String("hehe");
System.out.println(s2);
//使用字符数组进行构造
char[] array = {'a','b','c','d'};
String s3 = new String(array);
System.out.println(s3);
}
注意:
- String 是引用类型,内部并不存储字符串本身
- 在 Java 中""引起来的也是 String 类型对象
(二)String 对象的比较
1. == 比较是否引用同一个对象
2.boolean equals(Object anObject) 方法:按照字典序比较
字典序:字符大小的顺序
String类重写了父类 Object 中 equals 方法,Object 中 equals 默认按照 == 比较,String 重写 equals 方法后,按照字典序逐个字符进行比较
3.int compareTo(String str)方法:按照字典序进行比较
于 equals 不同的是,equals 返回的是 boolean 类型,而 compareTo 返回的是 int 类型。具体比较方式:
- 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
- 如果前k个字符相等(k为两个字符串长度最小值),返回两个字符串长度之间的差值
4.int compareTolgnoreCase(String str)方法:与compareTo方式相同,但是忽略大小写比较
public static void main2(String[] args) {
String s1 = "hello world!";
String s2 = new String("hello world!");
String s3 = new String("hehe");
String s4 = new String("Hehe");
System.out.println(s1 == s2);
//返回 boolean 类型
System.out.println(s1.equals(s2));
//返回 int 类型,
//1.出现的第一个不同的字符之间的差值
//2.当长度短的那个字符串遍历结束还没有找到不同的,返回两个字符串长度的差值
System.out.println(s2.compareTo(s3));
//与 compareTo 比较,忽略字母的大小写
System.out.println(s3.compareToIgnoreCase(s4));
}
(三)字符串查找
//字符串顺序查找
public static void main3(String[] args) {
//char charAt(int index);
//返回以 index 为下标的字符
String s1 = "hello world!";
System.out.println(s1.charAt(2));
//int indexOf(char ch)
//返回ch第一次出现的位置,没有返回-1
System.out.println(s1.indexOf('w'));
//int indexOf(int ch, int fromIndex)
//从fromIndex位置开始找ch第一次出现的位置,没有返回-1
System.out.println(s1.indexOf('o',5));
//int indexOf(String str)
//返回str第一次出现的位置,没有返回-1
System.out.println(s1.indexOf("or"));
//int indexOf(String str, int fromIndex)
//从fromIndex位置开始找str第一次出现的位置,没有返回-1\
String s2 = new String("abc bcd abd abc bbb");
System.out.println(s2.indexOf("abc",5));
}
public static void main4(String[] args) {
String s1 = new String("abc as ghd hac ca gac");
//int lastIndexOf(int ch)
//从后往前找,返回ch第一次出现的位置,没有返回-1
System.out.println(s1.lastIndexOf('a'));
//int lastIndexOf(int ch, int fromIndex)
//从fromIndex位置开始找,从后往前找ch第一次出现的位置,没有返回-1
System.out.println(s1.lastIndexOf('h',10));
//int lastIndexOf(String str)
//从后往前找,返回str第一次出现的位置,没有返回-1
System.out.println(s1.lastIndexOf("ac"));
//int lastIndexOf(String str, int fromIndex)
//从fromIndex位置开始找,从后往前找str第一次出现的位置,没有返回-1
System.out.println(s1.lastIndexOf("ac",12));
}
(四)转化
1.数值和字符串转化
public static void main5(String[] args) {
//数字转字符串
String s1 = String.valueOf(1234);
String s2 = String.valueOf(12.45);
String s3 = String.valueOf(true);
String s4 = String.valueOf(new Student("zhangsan",20));
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
System.out.println("==============================");
//字符串转数字
int data1 = Integer.parseInt("1234");
double data2 = Double.parseDouble("12.34");
System.out.println(data1);
System.out.println(data2);
}
2.大小写转换
public static void main6(String[] args) {
String s1 = "HELlo";
String s2 = "HEllO";
//小写转大写
System.out.println(s1.toLowerCase());
//大写转小写
System.out.println(s2.toUpperCase());
}
3.字符串转数组
public static void main7(String[] args) {
String s = "hello world";
//字符串转数组
char[] array = s.toCharArray();
for (int i = 0; i < s.length(); i++) {
System.out.print(array[i]);
}
System.out.println();
//数组转字符串
String s2 = new String(array);
System.out.println(s2);
}
4.格式化
public static void main8(String[] args) {
String s = String.format("%d-%d-%d",2019,9,14);
System.out.println(s);
}
(五)字符串替换
使用一个指定的新的字符串替换掉已有的字符串数据,可用的方法如下:
public static void main9(String[] args) {
//String replaceAll(String regex, String replacement)
//字符串不能修改,只能重新创建一个新的字符串接收返回值
//第一个形参是被修改的字符串,第二个形参是用来修改的字符串
//替换所有指定内容
String s1 = new String("abcabcbde");
String s2 = s1.replaceAll("ab","pq");
System.out.println(s2);
//String replaceFirst(String regex, String replacement)
//替换首个内容
String s3 = s1.replaceFirst("ab","pq");
System.out.println(s3);
}
注意事项:由于字符串是不可变对象,替换不修改当前的字符串,而是产生一个新的字符串
(六)字符串拆分
可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串
public static void main17(String[] args) {
String s = new String(", , , , , ab. sdf");
String[] str1 = s.split(" ");
System.out.println(str1);
/*String s1 = new String("hello world and IDEA");
//String[] split(String regex)
//将字符串以形参字符串 regex 为边界全部拆分
String[] str = s1.split(" ");
for (String s: str) {
System.out.println(s);
}
//部分拆分
System.out.println("=================");
String[] str2 = s1.split(" ",2);
for (String s: str2) {
System.out.println(s);
}
//拆分ip地址
//字符"|","*","+"都得加上转义字符,前面加上 "\\"
String s2 = new String("198.168.6.1");
String[] str3 = s2.split("\\.");
for (String s: str3) {
System.out.println(s);
}
//如果一个字符串中有多个分隔符,可以用"|"作为连字符
System.out.println("======================");
String s3 = new String("abv.sajd?fj sd");
String[] str4 = s3.split("\\.|\\?| ");
for (String s: str4) {
System.out.println(s);
}
//如果是 "\\" ,那么就得写成 "\\\\"
//具体 Java 的"\"知识点查看默认收藏夹
String s4 = new String("abc\\ade");
String[] str5 = s4.split("\\\\");
for (String s: str5) {
System.out.println(s);
}*/
}
注意事项:
- 字符"|","*","+"都得加上转义字符,前面加上"\\"
- 而如果是"\",那么就得写成"\\\\"
- 如果一个字符串中有多个分隔符,可以用"|"作为连字符
(七)字符串截取
从一个完整的字符串之中截取出部分内容
public static void main11(String[] args) {
String s1 = new String("abcdbcefabc");
//String substring(int beginIndex)
//从指定索引截取到结尾
String str1 = s1.substring(5);
System.out.println(str1);
//String substring(int beginIndex, int endIndex)
//截取部分内容,左闭右开
String str2 = s1.substring(5,10);
System.out.println(str2);
}
注意事项:
- 索引从0开始
- 注意前闭后开区间的写法,substring(0,5)表示包含 0 号下标的字符,不包含 5 号下标
(八)去掉字符串中的左右空格,保留中间空格
public static void main12(String[] args) {
String s1 = new String(" hello world ! ");
//String trim()
//去掉字符串中的左右空格,保留中间空格
String str1 = s1.trim();
System.out.println(s1);
System.out.println("[" + str1 + "]");
}
trim() 方法会去掉字符串开头和结尾的空白字符(空格,换行,制表符等)
(九)字符串常量池
1.创建对象的思考
在 Java 程序中,由于会有某些常量经常使用,为了使程序的运行速度更快、更节省内存,Java为8种基本数据类型和 String 类都提供了常量池
“池”是编程中的一种常见的,重要的提升效率的方式,我们会在未来的学习中遇到各种“内存池”,“线程池”,”数据库连接池“...
为了节省存储空间以及程序的运行效率,Java 中引入了:
-
Class 文件常量池 :每个 .java 源文件编译后生成 .Class 文件中会保存当前类中的字面常量以及符号信息
-
运行时常量池:在 .Class文件被加载时,.Class 文件中的常量池被加载到内存中称为运行时常量池,运行时常量池每个类都有一份
- 字符串常量池
2.字符串常量池(StringTable)
字符串常量池在JVM中是 StringTable 类,实际是一个固定大小的 HashTable(一种高效用来进行查找的数据结构),不同JDK版本下字符串常量池的位置以及默认大小是不同的
JDK版本 |
字符串常量池位置 |
大小设置(单位:字节) |
Java6 |
方法区 |
固定大小:1009 |
Java7 |
堆中 |
可设置,没有大小限制,默认大小:60013 |
Java8 |
堆中 |
可设置,有范围限制,最小是1009 |
3.再谈 String 对象创建
不同 JDK 版本对字符串常量池的处理方法不同,此处在 Java8 HotSpot上分析
(1)直接使用字符串常量进行赋值
//直接使用字符串常量进行赋值
public static void main13(String[] args) {
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);
}
图示说明:
说明:
- 在字节码文件加载时,"hello" 常量串已经创建好了,并保存在字符串常量池中
- 当使用 String s1 = "hello";创建对象时,先在字符串常量池中找,找到了,将该字符串引用赋值给s1,否则在字符串常量池创建"hello"字符串常量对象,并将引用赋值给s1
(2)通过 new 创建 String 类对象
//通过 new 创建 String 类对象
public static void main14(String[] args) {
String s1 = new String("hello");
String s2 = new String("hello");
//只要是 new 的对象,都是唯一的
System.out.println(s1 == s2);
}
图示说明:
通过上面例子可以看出:如果 new 一个常量字符串,那么如果哈希表中没有,就会先在哈希表创建一个常量字符串对象,然后再创建一个新的对象,用新的对象指向哈希表中的常量字符串。使用常量串创建String类型对象的效率更高,而且更节省空间。用户也可以将创建的字符串对象通过 intern 方式添加进字符串常量池中
(3)intern方法
intern 是一个 native 方法(Native 方法指:底层使用C++实现的,看不到其实现的源代码),该方法的作用是手动将创建的 String 对象添加到常量池中
// intern 方法
public static void main15(String[] args) {
char[] ch = new char[]{'a', 'b', 'c'};
String s1 = new String(ch); // s1对象并不在常量池中
//如果常量池中存在 s1 对象中的字符串,那么 s1的对象引用 将不会放入到常量池中
s1.intern(); // s1.intern();调用之后,会将s1对象的引用放入到常量池中
String s2 = "abc"; // "abc" 在常量池中存在了,s2创建时直接用常量池中"abc"的引用
System.out.println(s1 == s2);
}
面试题:请解释 String 类中两种对象实例化的区别
JDK1.8中
只会开辟一个字符串对象,创建一个字符数组对象
- String str = new String("hello");
开辟两个字符串对象(一个在),创建一个字符数组对象
- String str = new String(new char[]{'h',''e,'l','l','o'});
开辟一个字符串对象,创建两个字符数组对象(new 首先创建了一个字符数组,字符串对象创建时,会将这个字符数组进行拷贝,即返回一个新的地址的字符数组,然后再指向新的字符数组)
(十)字符串的不可变性
String 是一种不可变对象。字符串中的内容是不可改变的。字符串不可被修改,是因为:
1.String 类在设计时就是不可改变的,String 类实现描述中已经说明了
String 类中的字符实际保存在内部维护的 value 字符数组中,该图还可以看出:
- String 类被 final 修饰,表明该类不能被继承
- value 被 final 修饰,表明 value 自身的值不能改变,即不能引用其他字符数组,但是其引用空间中的内容可以修改
2.所有涉及到可能修改字符串内容的操作都是创建一个新对象 ,改变的是新对象
【误区警示】:final 修饰类表明该类不能被继承,final 修饰引用类型表明该引用类型不能再引用其他对象,但是其引用对象中的内容是可以修改的。因此,之所以说 String 类创建的对象无法被修改,是因为 val 引用被 final 修饰,不能引用其他对象,如果想要改变 val 内部的值,val 被 private 修饰,只能通过类内部进行修改,但是 String 类本身并没有提供此方法,因此说 String 是一种不可变对象
为什么 String 要设计成不可变的?
- 方便实现字符串对象池,如果 String 可变,那么对象池就需要考虑写时拷贝的问题了
- 不可变对象是线程安全的
- 不可变对象更方便缓存 hash code,作为 key 时可以更高效的保存到 HashMap 中
(十一)字符串修改
注意:尽量避免直接对 String 类型对象进行修改,因为 String 类是不能修改的,所有的修改都会创建新对象,效率极其低下
如果要修改建议使用 StringBuffer 和 StringBuilder
二、StringBuilder 和 StringBuffer
(一)StringBuilder 和 StringBuffer的介绍
由于 String 的不可更改特性,为了方便字符串的修改,Java 中又提供了 StringBuilder 和 StringBuffer 两大类。这两大类大部分功能是相同的。
- StringBuilder 和 StringBuffer 以及 String 是三个不同的类,三者不能混为一谈,但是前两个直接重写了 toString 方法,可以通过调用 toString 方法进行转换
- StringBuilder 和 StringBuffer都不能直接赋值,必须 new 对象(就是为了保证字符串的可变性,不能直接接收一个常量字符串)
StringBuilder 的常用方法较少,适用于单线程,StringBuffer 的方法较多,适用于多线程,下面介绍一些 StringBuilder 和 StringBuffer 常用的一些方法(前面写类型的就是返回调用方法的对象的类型)
方法 |
说明 |
类型 append(String str) |
在尾部追加,相当于 String 的 +=,可以追加:boolean,char,char[],double,float,int,long,Object,String,和自身类型的变量 |
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 最后一次出现的位置 |
类型 insert(int offset, String str) |
在 offset 位置插入:八种基本类型 & String类型 & Object 类型数据 |
类型 deleteCharAt(int index) |
删除index位置字符 |
类型 delete(int start, int end) |
删除[start,end)区间内的字符 |
类型 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 的方式返回 |
类型 reverse() |
反转字符串 |
String toString() |
将所有字符按照 String 的方式返回 |
从方法上就可以看出:String 和 StringBuilder 和 StringBuffer 最大的区别在于 String 的内容无法修改,而 StringBuilder 的内容可以修改。频繁修改字符串的情况考虑使用 StringBuilder 或者 StringBuffer
注意:String 和 StringBuilder 类不能直接转换。如果要想互相转换,可以采用下面的方法:
- String 转为 StringBuilder:利用 StringBuilder 的构造方法或 append() 方法
- StringBuilder 变为 String:调用 toString() 方法
(二)面试题
1.String、StringBuilder、StringBuffer 的区别
- String 内容不可被修改,StringBuilder、StringBuffer的内容可以修改
- StringBuilder 和 StringBuffer 的大部分功能是相似的
- StringBuffer 采用同步处理,属于线程安全操作;而 StringBuilder 未采用同步处理,属于线程不安全操作
2.以下总共创建了多少个 String 对象【前提不考虑常量池是否存在】
String str = new String("ab"); // 会创建多少个对象
String str = new String("a") + new String("b"); // 会创建多少个对象
解答:
- 第一个,首先会在常量池创建一个字符串对象和一个字符数组对象,新的字符串 new 了之后再创建一个新的字符串对象,里面 val 引用指向常量池中的字符数组,所以会创建两个字符串对象,一个字符数组对象
- 第二个,首先会在常量池创建字符串为 "a" 和 "b" 的字符串对象,然后因为 new 所以会再在堆里创建两个字符串对象, String 连接创建新的 StringBuilder 对象,再然后调用 toString() 方法再创建一个 String 对象,所以会创建六个对象
总结
以上就是今天要讲的内容,与诸君共勉