目录
一. String
1. 什么是String
2. String常用构造器
3. 字符串的判断
4. 字符串的获取
5. 字符串的转换
6. 字符串比较和替换
7. 字符串的切割
二. StringBuffer与StringBuilder
2.1 关于StringBuffer
2.1.1 定义
2.1.2 构造方法
2.2 关于StringBuffer
三. StringJoiner的使用
四. 关于常量池的面试
🐼个人主页:爪哇斗罗
🐼博主介绍:一名打工人
🐼签名:圣人之道,为而不争。
🐼一起交流,一起进步,一起互动。
一. String
1. 什么是String
首先,String属于引用数据类型,而不是基本数据类型。它是用来存储字符串的,使用双引号括起来的Unicode字符序列。
每个用双引号""括起来的都是属于String的一个实例。
String str = "hello"
阅读源码(Java8)会发现,String实现Serializable接口,表示字符串可以被序列化。
同时还实现Comparable接口表示字符串可以比较大小。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];// ①
}
标注①可看出Java使用char[]来存储字符串的值,Java9以后使用byte[]存储字符串。由此说明Sting底层实际上是一个字符数组。
所以上述的"hello"实际上存的是['h','e','l','l','o']。
除此之外,计算机并不会存储像h,e甚至是中文这样的字符,此时就和编码表也就是字符集有关系。
指定不同的字符集,程序底层会将对应的字符按照对应的编码表进行编码存储至内存或者磁盘中。
常见的字符集编码有utf8,GBK等。utf8一个字符对应三个字节,GBK一个字符对应两个字节。
2. String常用构造器
String() | 构造一个空的字符串 |
String(byte[] arr) | 将字节数组转换为字符串 |
String(byte[] arr, int offset, int lengh) | 将字节数组部分转换为字符串 |
String(char[] arr) | 将char字节数组转换为字符串 |
String(char[] arr, int offset, int length) | 将char字节数组部分转换为字符串 |
String(String original) | 字符串常量构建字符串 |
String(byte bytes[], int offset, int length, Charset charset) | 将字节数组部分转换为指定字符集的字符串 |
byte[] b = {97,98,99,100};
String str = new String(b);
System.out.println(str);//abcd
byte数组转换为String字符串会有一个解码的过程,就是将b存入内存中然后进行解码输出对应字符串得到abcd。
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
// 解码
this.value = StringCoding.decode(bytes, offset, length);
}
char数组转String字符串方式都和上述大同小异,这里不在赘述。
3. 字符串的判断
boolean equals(Object obj) | 判断两个字符串内容是否相等 |
boolean equalsIgnorecase(String str) | 忽略大小写判断两个对象是否相等 |
boolean contains(String str) | 判断大字符串是否包含小字符串 |
boolean startsWith(String str) | 判断是否以指定的字符串开头 |
boolean endsWIth(String str) | 判断是否以指定的字符串结尾 |
boolean isEmpty() | 判断字符串是否为空 |
public static void main(String[] args) {
String s1 = "abcde";
String s2 = "AbCde";
String s3 = "abcde";
//equals(): 比较两个字符串是否相等。
System.out.println(s1.equals(s2));//true
System.out.println(s1.equals(s3));//false
//equalsIgnoreCase():忽略大小写比较字符串相等。
System.out.println(s1.equalsIgnoreCase(s2));//true
System.out.println(s1.equalsIgnoreCase(s3));//true
//contains():是否包含指定字符串。
System.out.println(s1.contains("bd"));//false
//startsWith():是否以指定字符串开头
System.out.println(s1.startsWith("ab"));//true
// 第二个参数表示在索引为2的字符开始比较
System.out.println(s1.startsWith("cde",2));//true
//是否以字符串结尾
System.out.println(s1.endsWith(s3));//true
//是否为空
System.out.println(s1.isEmpty());//false
}
4. 字符串的获取
length() | 获取字符串的长度 |
charAt(inx index) | 获取某个索引的字符 |
indexOf(int ch) | 获取指定字符在字符串第一次出现的位置,可以写对应的ASCALL码值 |
indexOf(int ch, int fromIndex) | 获取从指定的索引开始,字符出现的位置 |
indexOf(String str) | 获取指定的字符串在原字符串的位置 |
indexOf(String str, int fromIndex) | 获取从指定的索引开始,字符串第一次出现的位置 |
lastIndexOf(int ch) | 获取指定字符最后一次出现的索引值 |
lastIndexOf(String str,int fromIndex) | 获取指定字符串最后出现的索引值 |
subString(int start) | 从指定位置开始截取字符串 |
subString(int start, int end) | 从指定位置到指定位置截取字符串 |
public static void main(String[] args) {
String str = "abcdeababcdacaeca";
//长度
System.out.println(str.length());//17
//获取某个索引的字符
System.out.println(str.charAt(1));//b
//获取最后一个元素值
System.out.println(str.charAt(str.length() - 1));//a
//获取'a'第一次出现的索引值
System.out.println(str.indexOf('a'));//0
System.out.println(str.indexOf(97));//0
//从第二个索引值开始,'a'第一次出现的位置
System.out.println(str.indexOf('a', 2));//5
//获取"bc"第一次出现的索引值
System.out.println(str.indexOf("bc"));//1
//从2索引开始"ad"第一次出现的索引值
System.out.println(str.indexOf("ad", 2));//-1
//获取'a'最后一次存储的索引值
System.out.println(str.lastIndexOf('a'));//16
//获取'bc'最后一次出现的索引值
System.out.println(str.lastIndexOf("bc"));//8
//从7索引结束前获取'a'最后一次索引值
System.out.println(str.lastIndexOf('a', 7));//7
System.out.println(str.lastIndexOf('c', 6));//2
//从索引5截取到末尾
String s1 = str.substring(5);
System.out.println(s1);//ababcdacaeca
//"abcdeabca" ===> "deab"
String s2 = str.substring(3, 7);
System.out.println(s2);
}
注意:如果没有找到都会返回-1
5. 字符串的转换
byte[] getBytes() | 将字符串转换为字节数组 |
byte[] getBytes(String charset) | 通过指定的字符集,将字符串转成字节数组 |
char[] toCharArray() | 将字符串转成字符数组 |
static valueOf(char[] chs) | 将字符数组转成字符串 |
static valueOf(Object obj) | 将任意的引用数据转成字符串 |
toLowerCase() | 转成小写 |
toUpperCase() | 转成大写 |
concat(String str) | 字符串连接 |
trim() | 去除两边空格 |
public static void main(String[] args) {
String s = "Ab三上悠亚";
//String转为byte[]
byte[] bys =s.getBytes();
//[65, 98, -28, -72, -119, -28, -72, -118, -26, -126, -96, -28, -70, -102]
System.out.println(Arrays.toString(bys));
//String转为char[]
char[] c = s.toCharArray();
//[A, b, 三, 上, 悠, 亚]
System.out.println(Arrays.toString(c));
//其它大部分类型转String
System.out.println(String.valueOf(100));//100
System.out.println(String.valueOf(false));//false
System.out.println(String.valueOf('a'));//a
System.out.println(String.valueOf(3.23f));//3.23
System.out.println(String.valueOf(c,0,3));//Ab三 从0索引开始截取三个字符
char[] data = {'A','B','c'};
System.out.println(String.valueOf(data));//ABc
System.out.println(s.toLowerCase());//ab三上悠亚
System.out.println(s.toUpperCase());//AB三上悠亚
}
6. 字符串比较和替换
replace(char old, char new) | 新的字符替换旧的字符 |
replace(String old, Stringnew) | 新的字符串替换旧的字符串 |
String replace(CharSequence target, CharSequence replacement) | 替换指定的字符序列 |
int compareTo(String str) | 字典比较字符串 |
int compareToIgnoreCase(String str) | 忽略大小写比较 |
public static void main(String[] args) {
String s = "abc三上悠亚abc";
//b替换B 字符替换
String s1 = s.replace('b', 'B');
System.out.println(s1);//aBc三上悠亚aBc
//三上悠亚替换为JAPAN字符串替换
String s2 = s.replace("三上悠亚", "JAPAN");
System.out.println(s2);//abcJAPANabc
//替换指定的字符序列
String s4 = "abc";
String s5 = "abcd";
String s6 = s.replace(s4, s5);
System.out.println(s6);//abcd三上悠亚abcd
}
7. 字符串的切割
String[] split(String regex, int limit) | 将字符串按照某种方式进行分割并指定分割多少 |
String[] split(String regex) | 将字符串按照某种方式进行分割 |
public static void main(String[] args) {
String str = "1-2-3-4-5";
// 按照规则来切割字符串
String[] split1 = str.split("-");
// 按照规则来切割几段字符串
String[] split2 = str.split("-",2);
// [1,2,3,4.5]
System.out.println(Arrays.toString(split1));
// [1,2-3-4-5]
System.out.println(Arrays.toString(split2));
}
二. StringBuffer与StringBuilder
2.1 关于StringBuffer
2.1.1 定义
作为线程不安全的可变字符序列,StringBuilder类似于String的字符缓冲区,可以看做是一个高级的String。
与String的区别就是,它是一个可变的字符序列。记住:StringBuilder是线程不安全的!!!所以在多线程场景下不可使用。
2.1.2 构造方法
StringBuilder构造
- StringBuilder():空的构造,底层默认创建容量为16的字符缓冲区对象。
- StringBuilder(int capacity):可以指定容量创建StringBuilder对象。
- StringBuilder(String str):创建指定字符串的StringBuilder对象。
StringBuilder sb = new StringBuilder();
System.out.println(sb);//空
System.out.println(sb.length());//0
System.out.println(sb.capacity());//16
StringBuilder sb1 = new StringBuilder(100);
System.out.println(sb1);//空
System.out.println(sb1.length());//0
System.out.println(sb1.capacity());//100
StringBuilder sb3 = new StringBuilder("java");
System.out.println(sb3);//java
System.out.println(sb3.length());//4
System.out.println(sb3.capacity());//20
StringBuilder成员方法
-
增加功能:
-
String append(Object obj):括号中可以是任意类型
-
insert(int offset, String str):任意地方添加指定类型
-
-
删除功能:
- deleteCharAt(int index) 指定位置删除对应的元素
- delete(int index, int end)删除[index,end-1]之间的元素
-
修改功能:
- setCharAt(int n, char ch):
- replace(int start,int end,String str):
-
查询功能
- charAt(int n):
- int Capacity():
- int length():
-
反转功能
- reverse() 反转功能
-
截取功能
- String substring(int start):截取指定位置一直到末尾
- String substring(int start,int end):截取[start,end-1]范围
StringBuilder sb = new StringBuilder();
//添加
sb.append('j').append("av").append(false).append(100);//javfalse100
sb.insert(1, false);//jfalseavfalse100
//删除
sb.deleteCharAt(0);//删除第一个字符 falseavfals100
sb.delete(0, 3);//删除[0-2]的字符 seavfals100
//修改
sb.setCharAt(3, 't');//seatfalse100
sb.replace(3,7,"true");//seatruelse100
//查询元素
System.out.println(sb.charAt(6));//e
System.out.println(sb.length());//12
System.out.println(sb.capacity());//初始容量16
//截取功能
System.out.println(sb.substring(3));//truelse100
System.out.println(sb.substring(4, 7));//rue 截取[4,6]
2.2 关于StringBuffer
StringBuffer与StringBuilder的用法是一模一样的,这里不再赘述相关方法,主要对比他们两有什么区别。
常见的String,StringBuffer,StringBuilder的面试题。
-
可变性
- String用final修饰,不可变
- StringBuffer与StringBuilder都是继承AbstratBuilder类,存储的char[]并未用final修饰,是可变的
-
线程安全性
- String对象不可变,可以视为线程安全
- StringBuffer加了同步锁,线程安全
- StringBuilder方法未加同步锁,线程不安全
-
性能
- 每次对String进行赋值时都会产生新对象,然后将指针指向新的对象。
- StringBuffer每次都会对对象的本身进行操作,而不是产生新的对象去引用它,相同情况下,使用StringBuilder的性能会提高,但是会有线程不安全的风险。
代码比较StringBuilder与StringBuffer的性能:
private static void demo02() {
StringBuilder sb = new StringBuilder("abc");
//当前时间
long l1 = System.currentTimeMillis();
for(int i=0; i<100000;i++) {
sb.append("hello");
}
//跑完循环
long l2 = System.currentTimeMillis();
System.out.println("String连接耗时:"+ (l2 -l1));
}
private static void demo01() {
String s = "abc";
//当堆空间溢出会发生OutOfMemoryError
//当前时间
long l1 = System.currentTimeMillis();
for(int i=0; i<100000;i++) {
s += "hello";
}
//跑完循环
long l2 = System.currentTimeMillis();
System.out.println("String连接耗时:"+ (l2 -l1));
}
- 操作少量的数据:String
- 单线程操作大量数据:StringBuilder
- 多线程操作大量数据:StringBuffer
三. StringJoiner的使用
上面,我们总是使用SpringBuilder去拼接字符串,使用的分割符号很多。
并且代码量相对来说也是比较多的,Java1.8给我们提供了一个StringJoiner
类来专门拼接字符串。
先看一个简单的例子,将集合中的元素通过逗号分割开并输出一个字符串:
public static void main(String[] args) {
List<Integer> integers = new ArrayList<Integer>();
integers.add(1);
integers.add(2);
integers.add(3);
integers.add(4);
integers.add(5);
StringBuilder stringBuilder = new StringBuilder();
for (Integer integer : integers) {
stringBuilder.append(integer + ",");
}
// 1,2,3,4,5
System.out.println(stringBuilder.toString().substring(0, stringBuilder.length() - 1));
}
这样的代码是不是看起来繁杂,并且只能通过手动去拼接,如果使用StringJoiner
去拼接字符串就会一步到位:
public static void main(String[] args) {
List<Integer> integers = new ArrayList<Integer>();
integers.add(1);
integers.add(2);
integers.add(3);
integers.add(4);
integers.add(5);
// 序号1
StringJoiner stringJoiner = new StringJoiner(",");
for (Integer integer : integers) {
stringJoiner.add(String.valueOf(integer));
}
// 1,2,3,4,5
System.out.println(stringJoiner.toString());
}
以上两段代码输出的结果都是一样的,都是1,2,3,4,5
,如果我们需要输出这样的字符串[1,2,3,4,5]
怎么办?其实StringJoiner
也提供了这样的方法,只需要将上述的序号1代码换成:
StringJoiner stringJoiner = new StringJoiner(",","[","]");
添加两个参数,分别代表的是前缀与后缀,是不是非常简单呢,这个在开发中也是很常用的。
如果获取的数据是空的,我们呢又要给其设置一个默认的值。
那么可以使用setEmptyValue()
方法就可以了:
stringJoiner.setEmptyValue("1");
拼接字符串使用StringJoiner
还是挺方便的,赶快用起来吧!
四. 关于常量池的面试
先看如下所示的代码:s1与s2相等吗?
String s1 = new String(abc");
String s2 = "abc";
s1与s2虽然内容都是一样,但是两者是不相等的。
因为String是引用数据类型,如果比较相等(==),两者比较的是地址与内容是否相等。这就会引出常量池的概念。
s1是引用对象,首先会在栈内存中会开辟一个s1的引用对象的空间,而abc
这个字符串常量是存在方法区常量池中。通过s1这个对象引用指向abc。
s2则是s2的一个引用指向了abc。
注意:JDK1.7之前,常量池在方法区中,JDK1.7及以后常量池放在了堆里面,我们通常指的的是1.7之前的常量池
下面来看==与equals()的比较字符串是否相等的使用。
- == 比较的地址和内容都相等才相等
- equals()内容相等即是相等
理解上面两句,看如下代码就会清晰很多:
String s1 = "123";①
String s2 = "123";②
String s3 = new String("123");③
System.out.println(s1==s2);//正确
System.out.println(s1==s3);//错误
只要明白了内存分布,判断不成问题。对于①,②来说,s1,s2都在栈内存中。
对于③来说,会先在栈中开辟一个内存空间存放引用对象s3。
然后会在堆内存中重新开辟空间存放new String("123")
中的String匿名对象的值123
所以s1==s3是错误的!!!
对于equals()就不一样了比较的是内容是否相等,三者内容都是相等的,所以equals是true
。
PS: null," "的区别
null代表的是
空对象
,并不是字符串,可以赋给任何对象,字符串中表示只是一个引用,还没有内存空间的分配“ ”表示引用已经指向了 一块内存空间了,是一个实际的东西,可以进行操作了,表示一个长度为0的字符串
练习一:
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1==s2);
System.out.println(s1.equals(s2));
String s3 = new String("hello");
String s4 = "hello";
System.out.println(s3==s4);
System.out.println(s3.equals(s4));
String s5 = "hello";
String s6 = "hello";
System.out.println(s5==s6);
System.out.println(s5.equals(s6));
答案:F T F T T T
- 常量与常量的拼接还在常量池中
- 常量池不可有相同的常量
- 拼接的时候,只要存在变量都会存到堆中
- 调用intern()方法返回常量池里面的常量
String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
System.out.println(s3==(s1+s2));//F 变量的连接存在堆中不相等
System.out.println(s3==(s1+s2).intern());//T 获取的是值相等
System.out.println(s3.equals(s1+s2));//T 获取内容相等
System.out.println(s3=="hello" + "world");//T 常量与常量连接还在常量池中
System.out.println(s3.equals("hello"+"world"));//T 内容相等