常用类上 - 字符串相关的类:String、StringBuffer、StringBuilder
1 字符串相关的类
1.1 String类及常用方法
1.1.1 String的特性
-
String声明为final的,不可被继承
-
String实现了Serializable接口:表示字符串是支持序列化的
实现了Comparable接口:表示String可以比较大小
-
String内部定义了final char[] value用于存储字符串数据
-
String:代表不可变的字符序列。简称:不可变性
体现:不同内容的字符串,就会重新造
-
当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值
String s1 = "abc"; s1 = "Hi";
-
当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
String s2 = "abc"; s2 += "def";
-
当调用String的replace()方式修改内部指定的字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
Srting s3 = "abc"; String s4 = s3.replace('a','m');
-
-
通过字面量的方式(不同于new)给一个字符串赋值,此时的字符串值声明在方法区的字符串常量池中
-
字符串常量池中是不会存储相同内容
1.1.2 String对象的创建
1.1.2.1 内存解析
1.1.2.2 练习1 字面量定义值与new定义比较
String str = new String(“123”);创建了两个对象
String s1 =“123”;
这种方式是字面量定义,"123"是放在方法区的字符串常量池(1.8后常量池还是在方法区里面)
String s2 = new String(“123”);
new 出来的String对象是放在堆当中的,但是"123"这个value值它还是存在方法区中,也就是在方法区也创建一个”123“.所以有问题说String s2 = new String(“123”)创建了几个对象,答案是两个
1.1.2.3 练习2 对象字符串属性
1.1.2.4 练习3 各种拼接
1.1.2.5 使用陷阱(上面部分小结)
1.1.2.6 面试题-关于String和值传递
值传递-其中方法形参相当于在栈中有了一个新的指向同一个地址
good
best
public class StringTransmit {
String str = new String("good");
char [] ch = {'t','e','s','t'};
public void change(String str1,char ch[]){
System.out.println(this.str == str1);//true
str1 = "test ok";
System.out.println(this.str == str1);//false
//String heapString = new String("test ok");
//System.out.println(str1==heapString);//false
ch[0] = 'b';
System.out.println(this.ch==ch);//true
}
public static void main(String[] args) {
StringTransmit ex = new StringTransmit();
ex.change(ex.str,ex.ch);
System.out.println(ex.str);//good
System.out.println(ex.ch);//best
}
}
其实从第一个打印语句System.out.println(this.str == str1);//true的结果是true,经过赋值操作之后的第二个打印语句System.out.println(this.str == str1);//false的结果是false,已经是给出了一个线索了,说明什么呢?在经过赋值操作之后,str 成员变量和str1 形参它们的地址值是不一样的,那么str1的修改不是去操作str 指向的地址的对象就可以理解了,因为如果是修改了str 指向的地址值的对象,那么它两的地址值应该是一致的,从结果看,并不是 。
下面说说详细的过程,调用ex.change(ex.str,ex.ch)方法,str 成员变量把存放对象堆中的地址给str1 ,那么当前str 和 str 1指向的对象是一致的。执行str1 = “test ok”,此时str1想去修改共同指向的对象的值,然后发现这个对象是不可变的。**(String不可变性值的是堆中的String对象或方法区字符串常量池中的对象是不能被改变的)**那么怎么办呢,我需要有个地方存放”test ok“这个值,如果是普通的对象,直接在堆中申请一块内存,把new 出来的新对象放在内存当中,然后把”test ok“放进去,最后把str1和新开辟的对象关联上。但是String比较特殊,采用str1 = “test ok"字面量定义的方式,test ok是放在方法区的常量池当中的,然后把方法区中的对象和str1关联起来。但无论是放在堆中和方法区中,因为String对象的不可变性,都是需要新的一块内存区域来存放"test ok”,也就是说原先对象的内容不会被改变。
1.1.3 常用方法
int length():返回字符串的长度: return value.length
⭐️char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
⭐️boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大 小写
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
⭐️int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从 beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字 符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。左闭右开
是否以指定前/后缀开始/结束
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的 子字符串是否以指定前缀开始
是否包含(返回索引)
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列 时,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出 现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后 一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1
替换
String replace(char oldChar, char newChar):返回一个新的字符串,它是 通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement):使 用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement) : 使 用 给 定 的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement) : 使 用 给 定 的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
匹配
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
切片
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此 字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
1.1.4 String与其他数据类型的转换
String str1 = "123";
int num = Integer.parseInt(str1);//123
String str2 = String.valueOf(num);//"123"
String str3 = num + "";
String str1 = "123";
char[] charArray = str1.toCharArray();//{'1','2','3'}
char[] arr = new char[]{'a','b','c'};
String str2 = new String(arr);//"abc"
String str1 = "abc123中国";
byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码
byte[] gbks = str1.getBytes("gbk");//使用gbk字符集,进行编码
String str2 = new String(bytes);//用默认的字符集,进行解码
String str3 = new String(gbks);//出现乱码,编码集和解码集不一样
String str2 = new String(gbks,"gbk");//没有出现乱码
编码:字符串 --> 字节(看得懂 --> 看不懂的二进制数据)
解码:编码的逆过程:字节 --> 字符串(看不懂的二进制数据 --> 看得懂)
1.1.5 常见算法题目
-
模拟一个trim方法,去除字符串两端的空格。
public class exer05 { public static void main(String[] args) { String str1 = " Hello World "; System.out.println(myTrim(str1)); } public static String myTrim(String str){ int start = 0; int end = str.length() - 1; while(start < end && str.charAt(start) == ' ' ){ start++; } while (end > start && str.charAt(end) == ' '){ end--; } if(start == end){ return ""; } return str.substring(start,end+1); } }
-
将一个字符串进行反转。将字符串中指定部分进行反转。
比如“abcdefg”反 转为”abfedcg”
public class exer06 { public static void main(String[] args) { String str1 = "HelloWorld"; System.out.println(reverseString1(str1, 2, 5)); System.out.println(reverseString2(str1, 2, 5)); System.out.println(reverseString3(str1, 2, 5)); } private static String reverseString1(String str, int start, int end) { char[] c = str.toCharArray(); for (int i = start, j = end; i < j ; i++,j--) { char temp = c[i]; c[i] = c[j]; c[j] = temp; } return new String(c); } private static String reverseString2(String str, int start, int end) { String str1 = str.substring(0,start); for (int i = end; i >= start; i--) { char c = str.charAt(i); str1 += c; } str1 += str.substring(end + 1); return str1; } private static String reverseString3(String str1, int start, int end) { StringBuilder builder = new StringBuilder(str1.length()); builder.append(str1.substring(0,start)); for (int i = end; i >= start; i--) { builder.append(str1.charAt(i)); } builder.append(str1.substring(end + 1)); return builder.toString(); } }
-
获取一个字符串在另一个字符串中出现的次数。
比如:获取“ ab”在 “abkkcadkabkebfkabkskab” 中出现的次数
public class exer07 { public static void main(String[] args) { String str1 = "abkkcadkabkebfkabkskab"; System.out.println(getTime1(str1, "ab")); System.out.println(getTime2(str1, "ab")); } private static int getTime1(String str1, String str2) { int count = 0,start = 0; while((start = str1.indexOf(str2)) != -1){ count++; str1 = str1.substring(start + str2.length()); } return count; } private static int getTime2(String str1, String str2) { int count = 0,start = 0; while((start = str1.indexOf(str2,start)) != -1){ count++; start += str2.length(); } return count; } }
-
获取两个字符串中最大相同子串。
比如: str1 = "abcwerthelloyuiodef“;str2 = “cvhellobnm” 提示:将短的那个串进行长度依次递减的子串与较长的串比较。
public class exer08 { public static void main(String[] args) { String str1 = "abcwerthelloyuiodef"; String str2 = "cvhellohellobnm"; System.out.println(getMaxString(str1,str2)); System.out.println(getMaxSubString(str1,str2)); } private static String getMaxString(String str1, String str2) { String maxStr = (str1.length() >= str2.length())? str1 : str2; String minStr = (str1.length() < str2.length())? str1 : str2; String str = null; int length = minStr.length(); for (int i = 0; i < length; i++) { for (int x = 0,y = length - i; y < length; x++,y++) { str = minStr.substring(x,y); if(maxStr.contains(str)){ return str; } } } return null; } public static List<String> getMaxSubString(String str1,String str2){ String maxStr = (str1.length() > str2.length())? str1 : str2; String minStr = (str1.length() < str2.length())? str1 : str2; int len = minStr.length(); List<String> list = new ArrayList<>(); for(int i = 0;i < len;i++){ for(int x = 0,y = len - i;y <= len;x++,y++){ String str = minStr.substring(x, y); if(maxStr.contains(str)){ list.add(str); } } if(list.size() != 0){ return list; } } return null; } }
-
对字符串中字符进行自然顺序排序。
提示:
1)字符串变成字符数组。
2)对数组排序,选择,冒泡,Arrays.sort();
3)将排序后的数组变成字符串。
public class exer09 { public static void main(String[] args) { String str1 = "abkkcadkabkebfkabkskab"; System.out.println(sort(str1)); } public static String sort(String str){ char[] c = str.toCharArray(); Arrays.sort(c); return new String(c); } }
1.2 StringBuffer
1.2.1 创建底层
String str1 = new String();//char[] value = new char[0];
String str2 = new String("abc");//char[] value = new char[]{'a','b','c'};
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度为16的数组
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';
StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];
1.2.2 一些问题
-
问题1:
System.out.println(sb2.length());//3
-
问题2:
扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组
默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新的数组中
value.length << 1 相当于乘2
1.2.3 常用方法
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer s1 = new StringBuffer("abc");//abc
s1.append(1);//abc1
s1.append('1');//abc11
StringBuffer delete(int start,int end):删除指定位置的内容。左闭右开
s1.delete(2,4);//ab1
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str 。左闭右开
s1.replace(2,4,"hello");//abhello1
StringBuffer insert(int offset, xxx):在指定位置插入xxx
s1.insert(2,false);//abfalsec11
StringBuffer reverse() :把当前字符序列逆转
s1.reverse();//11cba
public int indexOf(String str)
public String substring(int start,int end)
**public int length() **
public char charAt(int n )
public void setCharAt(int n ,char ch)
- 总结:
- 增:append(xxx)
- 删:delete(int start,int end)
- 改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
- 查:charAt(int n )
- 插:insert(int offset, xxx)
- 长度:length()
- 遍历:for() + chaAt() / toSring()
1.3 StringBuilder
与StringBuffer差不多,只是StringBuilder是线程不安全的,效率高点
1.4 String与StringBuffer、StringBuilder之间的转换
1.4.1 String —> StringBuffer、StringBuilder
调用StringBuffer、StringBuilder的构造器
1.4.2 StringBuffer、StringBuilder —> String
- 调用String的构造器;
- StringBuffer、StringBuilder的toString()
1.5 三者异同
- String:不可变的字符序列;底层使用char[]存储;
- StringBuffer:可变的字符序列;底层使用char[]存储;线程安全的,效率低;
- StringBuilder:可变的字符序列;底层使用char[]存储;线程不安全的,效率高;JDK5.0新增的;
1.6 三者效率
//初始设置
long startTime = 0L;
long endTime = 0L;
String text = "";
StringBuffer buffer = new StringBuffer("");
StringBuilder builder = new StringBuilder("");
//开始对比
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer的执行时间:" + (endTime - startTime));//7
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder的执行时间:" + (endTime - startTime));//4
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String的执行时间:" + (endTime - startTime));//1026
1.7 两道面试题
//4
//“null”
//抛异常了NullPointerException