10. 字符串
10.1 字符串类 String
字符串其实就是若干字符组成的有序序列。
在String类中,维护了一个字符数组,字符串数组的存储其本质是实现一个字符数组。
10.1.1 字符串类型的内存分析
字符串是一个类,同时也是一个引用数据类型,但区别于其他类或者接口(表现之一:直接给字符串赋值时,其空间开辟不在堆上而在常量池上开辟,而常量池隶属于方法区;而通过构造方法实例化的字符串则是在堆上开辟空间并指向方法池)。
双引号就代表在常量池上指引内容。
字符串中遵循享元原则:第一次使用到某字符串时,在常量池中会将字母排列组合,将地址引用赋值,自此以后每当再使用该字符串时,将原地址给调用方赋值。
例:
String string = "hello";
String string2 = "hello";
String string3 = new String("hello");
//true,理由是存储在方法池上的字母已经有hello排序,再次赋值直接赋地址,故地址相同
System.out.println(string ==string2);
//false,理由是构造方法实例化的字符串地址在堆上而非方法池中
System.out.println(string ==string3);
//true,理由是equals方法比较的是数组而非地址
System.out.println(string.equals(string3));
10.1.2 字符串常用的方法
构造方法
String str1 = new String(); //实例化一个空字符串
String str2 = new String("123");//通过一个字符串实例化一个字符串对象
String str3 = new String(new char[] {'a','b','c'});//通过一个字符数组实例化一个字符串对象
int offset = 0,count = 3;
String str4 = new String(new char[] {'a','b','c','d','e'},offset,count);//从第offset第索引/*
* 通过⼀个字节数组,实例化⼀个字符串
* new String(byte[] array)
* 通过⼀个字节数组的指定部分,实例化⼀个字符串
* new String(byte[] array, int offset, int length);
* 通过⼀个字节数组,使⽤指定的字符集实例化⼀个字符串
* new String(byte[] array, String charsetName)
* 通过⼀个字节数组的指定部分,使⽤指定的字符集实例化⼀个字符串
* new String(byte[] array, int offset, int length, String charsetName);
*
* 通过⼀个StringBuffer实例化⼀个字符串
* new String(StringBuffer sb);
* 通过⼀个StringBuilder实例化⼀个字符串
* new String(StringBuilder sb);
*/
非静态方法
这些⽅法,基本都是对⼀个字符串进⾏操作的⽅法。但是,由于字符串是⼀个常量,内容是不能改变
的,因此每⼀个修改字符串的⽅法,都是需要接收返回值来得到新的结果的。
//1.将两个字符串拼接到⼀起,这个过程中,会new⼀个新的字符串对象,并返回结果
String ret1 = str1 + str2;
//2.将两个字符串拼接到⼀起,这个过程中,会new⼀个新的字符串对象,并返回结果
String ret2 = str1.concat(str2);
//3.从指定的下标位开始截取字符串,⼀直到最后⼀位,此时,结果为 "llo"
String ret3 = str1.substring(2);
//4.截取⼀个字符串的指定范围 [fromIndex, endIndex),此时,结果为 "ll"
String ret4 = str1.substring(2, 4);
//5.截取⼀个字符序列的指定范围 [fromIndex, endIndex),返回值CharSequence是⼀个接⼝
CharSequence ret5 = str1.subSequence(2, 4);
//6.将一个字符串中固定的字符序列替换成新的字符序列
String string6 = "hello world".replace('l', 'L');
String string6_1 = "hello world".replace("el", "ELLLL");
//除去字符串中所有空格
String string6_2 = " hello world ".replace(" ", "");
//7.将一个字符串转成一个字符数组
char[]array7 = "hello world".toCharArray();
String string7= Arrays.toString(array7);
//8.1将⼀个字符串转成⼀个字节数组,以项⽬默认的字符集进⾏转换
byte[] array8_1 = str1.getBytes();
//8.2将一个字符串转成指定的字节数组
try {
byte[] array8_2 = str1.getBytes("utf8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//9.下标查询(若查询失败则返回-1)
//9.1查询目标字符/字符串在字符串中第一次出现的下标
int index9_1 = "hello world".indexOf("ll");
//9.2查询目标字符/字符串在字符串中最后一次出现的下标
int index9_2 = "hello world".lastIndexOf("ll");
//9.3查询目标字符/字符串自第count位起第一次出现的下标
int index9_3 = "hello world".indexOf("ll",count);
//9.4查询目标字符/字符串自第count位起最后一次出现的下标
int index9_4 = "hello world".lastIndexOf("ll",count);
/*
* 以下四个查询下标的⽅法,和上⽅查询字符下标的逻辑是⼀模⼀样的
* 查询到的下标是字符串中的第0个字符的下标
*
* indexOf(String str)
* indexOf(String str, int fromIndex)
* lastIndexOf(String str)
* lastIndexOf(String str, int fromIndex)
*/
//10.大小写转换
//10.1小写转大写
String string10_1 = "hello world".toUpperCase();
//10.2大写转小写
String string10_2 = "HELLO WORLD".toLowerCase();
//11.获取字符串的长度
int length11 = "hello world".length();
//12.判断字符串是否为空
boolean boolean12 = "".isEmpty();
//13.判断⼀个字符串中是否包含指定的⼦字符串
boolean ret13 = str1.contains("ll");
//14.判断一个字符串的前后缀
//14.1判断一个字符串是否以目标字符串开头
boolean boolean14_1 = "hello world".startsWith("he");
//14.2判断一个字符串是否以目标字符串结尾
boolean boolean14_2 = "hello world".endsWith("ld");
//15.判断一个字符串是否包含目标字符串
boolean boolean15 = "hello world".contains("lo");
//16.判断两个字符串的内容是否相同
boolean boolean16_1 = "hello world".equals("hello world");
//忽略大小写做判断
boolean boolean16_2 = "hello world".equalsIgnoreCase("HELLO WORLD");
//17.除去字符串前后两端的空格
String string17 = " hello world ".trim();
//18.字符串的比较:
//依次从前往后比较相同索引字符的ASCII码值,直至某索引的字符可以比较出大小或字符串结束。
int int18_1 = "hello".compareTo("HELLO"); //返回首次出现的非零ASCII差值或者返回0
int int18_2 = "hello".compareToIgnoreCase("HELLO"); //忽略大小写进行比较(所有大写均视为小写)
//19.字符串格式化:将若干变量的内容按照一定的格式拼接到一个字符串中,例:
String name19 = "xiaoming";int age15 = 10;char gender15 = '男';
// 拼成:姓名:xiaoming ,性别:男,年龄:10岁
//在format方法中可以定制一个字符串的格式,在格式字符串中,可以使用指定符号进行占位。
String string19_1 = String.format("姓名:%s ,性别:%c,年龄:%d岁", name19_1,gender19_1,age19_1);
//20.字符串切割:按照指定字符切割字符串,例:下列字符串按逗号切割成字符串数组
String[] strings20 = "xiaoming,xiaohong,xiaoli,xiaolan,xiaohei,xiaobai".split(",");
静态方法
String ret = String.join(", ", "xiaoming", "xiaobai", "xiaohei", "xiaolv");
System.out.println(ret);
10.2 StringBuffer、StringBuilder
StringBuffer:字符串缓冲池,线程安全的可变序列。
int index = 5,start = 5,end= 11;char ch = ' ';String str = "@";
//实例化一个StringBuffer类的空字符串对象
StringBuffer sb = new StringBuffer();
//通过一个字符串字面量来实例化一个StringBuffer类的字符串对象
StringBuffer stringBuffer = new StringBuffer("hello world");
//常规操作
//1.增:在对象自身的尾部拼接一个新的字符串
stringBuffer.append("!");
//2.增:在索引位插入一个新的字符串str
stringBuffer.insert(index, str);
//3.删:删除[start,end)索引范围的元素
stringBuffer.delete(start, end);
//4.删:删除索引位上的元素
stringBuffer.deleteCharAt(5);
//5.改:将[start,end)索引范围的元素改成字符串str
stringBuffer.replace(start, end, str);
//6.改:将索引位的元素修改为字符ch
stringBuffer.setCharAt(index, ch);
//7.字符串倒置:将字符串中所有字符逆序组合排列
stringBuffer.reverse();
//8.StringBuffer类型->String类型:
String string = stringBuffer.toString();
StringBuilder:字符串构建器,线程不安全的可变序列,是使用工厂模式实现的字符串的常见操作。
StringBuilder常见方法的使用与StringBuffer完全相同。
两者区别:StringBuffer线程安全,代码同步,而StringBuilder线程不安全,因为没有做代码同步,但比StringBuffer高效,因此在不考虑线程安全的情况(例:单线程中),推荐使用StringBuilder。
补充:在字符串的操作中三者效率:StringBuilder > StringBuffer >>> String。
10.3 正则表达式
正则表达式不是Java特有的内容,是一种独立的表达式,其主要作用是:做字符串的校验,具体做法是:指定一些字符串应遵循的规则并判断是否满足这些规则,在校验的基础上,⼜添加了若⼲个其他的引⽤场景,例如: 批量的查找、替换、切割…。
使用关键字:matches 用于判断指定字符串是否满足正则表达式的规则,语法
字符串对象.matches(正则表达式);
10.3.1 正则表达式中的基本元字符
Java中的正则表达式支持的元字符如下:
(用于分组) [用于限定单个元素的校验] {用于限定次数} \用于转义 ^行首或非 $行尾 |或 ?出现0次或1次 *出现0次或多次 +出现1次或多次 .通配 < > - = !
[]字符类的使用示例 | 含义 |
---|---|
[abc] | 字符a,b或c |
[^xyz] | 除x,y和z以外的字符 |
[a-z] | 字符a到z |
[a-cx-z] | 字符a到c或x到z,其将包括a,b,c,x,y或z。 |
[0-9&&[4-8]] | 两个范围(4,5,6,7或8)的交叉, |
[a-z&&[^aeiou]] | 所有小写字母减元音 |
分组相关字符
元字符 | 用处 |
---|---|
() | 分组符号 |
$n | 第n组 |
下表列出了一些常用的预定义字符类。
含义 | |
---|---|
. | 任何字符 |
\d | 数字。 与[0-9]相同 |
\D | 非数字。 与[^ 0-9]相同 |
\s | 空格字符。 包括与[\ t \ n \ x0B \ f \ r]相同。空格标签换行符垂直标签表单Feed回车字符 |
\S | 非空白字符。 与[^ \ s]相同 |
\w | 一个字符。 与[a-zA-Z_0-9]相同。 |
\W | 非字字符。 与[^ \ w]相同。 |
我们可以使用量词判断该量词前一个或一串字符是否满足规则,下面列出了量词及其含义。
量词 | 意味该量词前一位或一串字符连续出现 |
---|---|
* | 零次一次或多次(全情况均true) |
+ | 一次或多次 |
? | 一次或根本不 |
{m} | 正好m次 |
{m,} | 至少m次 |
{m,n} | 至少m,但不超过n次 |
要匹配一行的开头,或匹配整个单词,不是任何单词的一部分,我们必须为匹配器设置边界。
下表列出了正则表达式中的边界匹配器
边界匹配 | 含义 |
---|---|
^ | 一行的开始 |
$ | 一行的结束 |
\b | 字边界 |
\B | 非字边界 |
\A | 输入的开始 |
\G | 上一次匹配的结束 |
\Z | 输入的结束,但是对于最终终止符,如果有的话 |
\z | 输入的结束 |
10.3.2 元字符的实际使用案例
万事不会问度娘
// []
// 验证⼀个字符串是否以⼩写字⺟开头
System.out.println("Hello world".matches("[a-z]ello world"));
// 验证⼀个字符串是否以字⺟开头(包括⼤写字⺟和⼩写字⺟)
System.out.println("Hello world".matches("[a-zA-Z]ello world"));
// 验证⼀个字符串是否是以⾮⼤写字⺟开头的
System.out.println("hello world".matches("[^A-Z]ello world"));
// 验证⼀个字符串是否是以(任意的⾮⼩写字⺟,但是 h 除外)开头的
System.out.println("hello world".matches("[^a-z[h]]ello world"));
// 验证⼀个字符串是否以字符 . 作为开头的
System.out.println(".ello world".matches("\\.ello world"));
// 验证⼀个字符串是否以数字开头
System.out.println("9e".matches("\\de"));
// 验证l出现了1次或多次
System.out.println("hellllllllllo".matches("hel+o"));
// 验证l出现了1次或0次
System.out.println("heo".matches("hel?o"));
// 验证l出现了0次或多次
System.out.println("helo".matches("hel*o"));
// 验证l出现了3次
System.out.println("helllo".matches("hel{3}o"));
// 验证l出现了⾄少3次
System.out.println("helllllllllo".matches("hel{3,}o"));
// 验证l出现了3次到5次 [3,5]
System.out.println("helllllo".matches("hel{3,5}o"));
10.3.3 正则表达式的实际案例
//常见方法
//1.利用正则表达式指定切割规则
String names = "Lily , Lucy ,Polly ,Jim Green, uncle wang ,Hanmeimei ";
String[] namesArray = names.split(", *");
for(String n :namesArray) {
System.out.println(n);
}
//2.利用正则表达式指定替换规则
String result2 = names.replaceAll(" *, *", ", ");
System.out.println(result2);
//3.利用正则表达式分组并替换,例:18720867688 ->187****7688
String phone3 = "18720867688";
String regex = "^(1[4678]\\d)(\\d{4})(\\d{4})$";
String result3 = phone3.replaceAll(regex, "$1****$3");
System.out.println(result3);
//4.|:或
//例:根据拓展名判断一个文件是否是视频文件:.mp4,.rmb,.avi,.mkv,.rm
String vedioname4 = "Harry Potter.avi";
String regex4 = "^(.+)\\.(mp4|rmb|mkv|rm|avi)$";
boolean result4 = vedioname4.matches(regex4);
System.out.println(result4);
案例1:字符串校验
// 1、QQ号的校验
// "[0-9]\\d{4,10}"
// 2、⼿机号
// 1[35678]\\d{9}
案例2:字符串切割
// 字符串的切割
String str = "xiaoming xiaoli xiaohong xiaobai";
String[] names = str.split(" +");
System.out.println(names.length);
for (String name : names) {
System.out.println(name);
}
案例3:字符串替换
// 字符串的替换
// replaceAll(String regex, String replacement)
// 将⼀个字符串中的所有的满⾜正则规则部分的字符串,替换成指定的字符串
// replaceFirst(String regex, String replacement)
// 将⼀个字符串中的第⼀个满⾜正则规则部分的字符串,替换成指定的字符串
String ret = str.replaceAll("[#*$]+", ", ");
System.out.println(ret);
注意:
1.编译全程无提示,书写需小心,千万不能加空格。
2.凡是字符串对象中出现元字符而产生歧义时,需要在改元字符前加双斜杠进行双转义,例:
"hello[world]".matches("^hello\\[world]$");
10.4 Pattern类和Matcher类
Pattern类用来制定正则表达式的规则,Matcher类用来承接结果,二者往往一起使用。
10.4.1 Pattern类
其实,这个类,才是真正来操作正则表达式的类。在String类中的提供的 matches、split、replace等⽅法,其实都是对这个类中的某些⽅法的封装。
10.4.2 Matcher类
Pattern类⽤来做校验、匹配的⼀个结果。
// Pattern 类的使用
// boolean ret = Pattern.matches("\\d+", "123456");
// System.out.println(ret);
// 1. 获取 Pattern 对象
// 由于Pattern类中只有一个构造方法,且是私有的,因此不能通过new的方式实例化对象
// 需要使用 compile 方法,将一个正则表达式编译成 Pattern 对象
Pattern pattern = Pattern.compile("\\d+");
// 2. 将一个字符串进行切割
String[] strs = pattern.split("hello121world123hello", 2);
System.out.println(Arrays.toString(strs));
// 3. 使用这个正则对象,和一个字符串进行比对校验
// 校验结果,存储于 matcher 对象
Matcher matcher = pattern.matcher("123hello456world789aaa012bbb");
// 4. matches(): 对整个字符串进行校验,校验这个字符串是否和指定的正则规则相匹配
// System.out.println(matcher.matches());
// 5. find(): 对字符串中进行部分匹配
// group(): 返回单次匹配的字符串
// start(): 返回单次匹配的起点,闭
// end(): 返回单次匹配的终点,开
while (matcher.find()) {
System.out.print("匹配的字符串: " + matcher.group());
System.out.printf(" 范围:[%d, %d)\n", matcher.start(), matcher.end());
}
// 6. lookingAt : 始终从第一个字符匹配
System.out.println(matcher.lookingAt() + ", " + matcher.group() +",start: " + matcher.start() + " end: " + matcher.end());
Pattern pattern = Pattern.compile("(1\\d{2})(\\d{4})(\\d{4})");
Matcher matcher = pattern.matcher("17788889999");
System.out.println(matcher.groupCount());
matcher.matches();
String str = matcher.group(1);
System.out.println(str);