深入Java字符串

  |  

摘要: 本文是关于 Java 中的字符串的要点总结。

【对算法,数学,计算机感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:算法题刷刷
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


== 与 equals

1
2
3
4
5
String str1 = String("abc");
String str2 = String("abc");
if(str1 == str2) {
...
}

上面代码中的 if 判断是失败的,要解释原因需要理解以下三点:

  1. boolean、byte、short、int、long、char、float 和 double 是基本数据类型,其余的都是引用类型。
  2. Java 中所有的字符串都是 String 类型的对象,包括字符串字面常量。
  3. == 运算符比较两个变量的值是否相等,对于引用类型,值是对象的地址。

== 比较的是变量的值,对于基本数据类型,直接 == 相当于比较了对象的内容。但是对于引用类型,想要比较对象的内容,需要覆盖 equals 方法。

Object 类中的 equals 方法只有在两个引用值指向同一个对象时才返回真,和 == 运算符一样。我们需要在类中覆盖 equals 方法,比较对象的内容。String 类重写了 equals 方法,对比字符序列。


compareTo

按照字典顺序来比较两个字符串的大小,则可以使用 String 类的 compareTo 方法来比较两个字符串。

str1.compareTo(str2),如果 str1 大于 str2,则返回正整数,小于则返回负整数,相等返回 0。


字符串拼接

String 的 “+” 和 “+=” 运算符是 Java 中唯一被重载的运算符;Java 中不允许程序员重载运算符。

1
2
3
str = str1 + str2;
str += str1;
str.concat(str1);

操作字符串

长度

1
str.length();

查找

查找字符

1
2
public int indexOf(int ch)
public int indexOf(int ch, int fromIndex)

查找字符串

1
2
public int indexOf(String str)
public int indexOf(String str, int fromIndex)

indexOf 返回该字符或子字符串第一次出现的索引,索引是从 0 开始计数的;如果没有找到,则返回 -1。

反向查找

1
2
3
4
public int lastIndexOf(int ch)
public int lastIndexOf(int ch, int fromIndex)
public int lastIndexOf(String str)
public int lastIndexOf(String str, int fromIndex)

判断开始与收尾

判断是否以某个前缀开始或后缀结尾。

1
2
3
public boolean startsWith(String prefix)
public boolean startsWith(String prefix, int toffset)
public boolean endsWith(String suffix)

指定索引位置的字符

1
public char charAt(int index)

截取子串

1
2
public String substring(int beginIndex)
public String substring(int beginIndex, int endIndex)

分割子串

有时候我们会将一组字符串合并为一个字符串进行存储,各个字符串之间通过一个分隔符来分隔。这种做法在使用数据库保存数据时很常见,可以节省表字段的数量,提高查询效率。

1
public String[] split(String regex)

替换字符或字符串

CharSequence 是 java.lang 包中定义的一个接口类型,String 类实现了该接口。

1
2
public String replace(char oldChar, char newChar)
public String replace(CharSequence target, CharSequence replacement)

正则表达式替换

1
2
public String replaceFirst(String regex, String replacement)
public String replaceAll(String regex, String replacement)

合并字符串

参数 delimiter 用于指定分隔各个字符串的分隔符,elements 是变长参数,可以传入任意多个字符串。

1
public static String join(CharSequence delimiter, CharSequence...elements)

转换为字符数组

1
public char[] toCharArray()

重复字符串

1
public String repeat(int count)

大小写转换

1
2
public String toLowerCase()
public String toUpperCase()

去除首位空白

空白(空格、制表符、换行、换页和回车)在字符串中属于合法的字符,但字符串首尾的空白通常是没有用的,一般都是由用户误输入而产生的。

trim 方法删除的空白字符是其 Unicode 码小于等于 U+0020 的任何字符;而 strip 方法删除的是 java.lang.Character 类的静态方法 isWhitespace(int codePoint)判断为 true 的空白字符。

1
2
3
4
public String trim()
public String strip()
public String stripLeading()
public String stripTrailing()

判断空串

1
public boolean isEmpty()

提取字符串的行流

某些字符串由多个子串组成,子串之间以行终止符分隔,行终止符可以是换行(\n)、回车(\r),或者回车换行(\r\n)。

lines 方法,可以根据行终止符从字符串中提取行流。

1
public Stream<String> lines()

与字节数组的相互转换

Java I/O 和网络编程中,经常需要将字节数组和字符串进行相互转换。

1
2
3
public String(byte[] bytes)
public String (byte[] bytes, int offset, int length)
public byte[] getBytes()

StringBuffer 和 StringBuilder

如果你想创建一个内容可以改变的字符串对象,那么String类显然不符合要求,这时,我们需要用到另外两个类: StringBuffer 类和 StringBuilder 类。

StringBuilder 类的用法和 StringBuffer 是一样的,区别在于 StringBuffer 是线程安全的,而 StringBuilder 不是线程安全的。

由于 StringBuffer 是线程安全的,其内部方法使用了同步机制,导致在调用方法时,频繁地加锁和解锁,就影响了执行效率。在实际开发中,很多字符串都是在方法内部操作的,并不存在线程安全的问题,此时应该用 StringBuffer。

StringBuffer 类包含可变的字符序列,这个类包含了以下三大类操作字符串的方法。

(1) 构建字符串

  • append 方法向字符序列末尾加入字符串。
  • insert 方法向字符序列的指定位置处插入字符串。
  • 创建 StringBuffer 对象时设置一个初始的字符串。

(2) 获取字符串

  • 使用 substring 截取当前字符序列的一部分。
  • 使用 toString 方法得到一个表示当前字符序列的新的 String 对象。

(3) 修改字符串

  • 使用 replace 方法来替换当前字符序列中的部分内容
  • 使用 delete 方法来删除字符序列中的某些字符,甚至删除整个字符序列。

StringBuffer 内部会根据字符序列的长度自动扩充内存容量,初始容量为16。当不断添加字符串时,就会引起内存的重新分配,为了提高效率,在程序中可以根据要操作的字符串的字符总量,提前分配好内存,方法是在构造 StringBuffer 对象时,传入需要的字符容量。

1
public StringBuffer(int capacity)

格式化输出

C 语言中,字符串不能像 Java 那样直接使用 “+” 和 “+=” 运算符来拼接,我们用的最多的是格式化输出函数 printf,该函数使用格式化字符串来插入数据,从而实现格式化输出。

1
printf("title: %s, price: %f\n", title, price)

Java 也有与 C 语言 printf 风格类似的 format 方法和 printf 方法,可以用于 java.io.PrintStream 和 Java.io.kPrintWriter 对象。

格式说明符

1
%[argument_index$][flags][width][.precision]conversion
  • argument_index: 是参数索引。
1
System.out.format("title: %2$s, price: %1$f", price, title);
  • flags 是一组修改输出格式的字符。
格式说明符 含义
d 十进制整数
o 八进制整数
x, X 十六进制整数
f 十进制浮点数
e, E 科学技术浮点数
t, T 日期/时间
c, C Unicode 字符
s, S 字符串
b, B 布尔值
h, H 十六进制散列码
% 字符%
n 行分隔符
  • width 一个正十进制整数,表示要写入/输出的最小字符数。

宽度表示要输出的最小字符数,可以用它来输出额外的空格以对齐数据;

在格式化字符串中指定宽度时,如果字符数不足,则在左边添加空格以补足宽度;

如果希望在右边添加空格,则可以在宽度数字前面添加一个标志“-”。

1
2
System.out.format("%20s, %20s, %20s", str1, str2, str3) // 在左边添加空格补足宽度
System.out.format("%-20s, %-20s, %-20s", str1, str2, str3) // 在右边添加空格补足宽度
  • precision 代表精度,通常用于限制字符数,精度在点号(.)后面给出。

精度用于字符串时,表示打印字符串时输出字符的最大数量;

用于浮点数时,它表示小数部分要显示出来的位数(默认是 6 位小数),如果小数位数过多则四舍五入,太少则在尾部补 0。

  • conversion 是格式说明字符。
标志 含义
- 输出结果左对齐
# 在和八进制说明符 o 一起使用时,输出值前面加 0
在和十六进制说明符 x 一起使用时,输出值前面加 0x
+ 在正数前面显示一个加号,负数前面显示一个减号
空格 再没有打印+标志的正数前面添加一个空格
0 用了 0 填充宽度
, 结果将包含特定区域的组分隔符
( 结果将把负数扩在圆括号内

生成格式化的 String 对象

在C语言中还有一个sprintf函数,它是将格式化后的字符串存储到一个字符缓冲区中,而不是打印输出。

String 的静态方法 format 是类似的方法。接受与 PrintStream 和 PrintWriter 类的 format 方法相同的参数,返回 String 对象。

1
String bookInfo = String.format("title: %s, price: %f\n", title, price);

正则表达式

正则表达式的组成部分

下面是常用的部分,更详细的内容看 java.util.regex.Pattern 的文档。

字符

字符 含义
x 字符 x
\\ 反斜杠字符
\0xhh 十六进制表示为 0xhh 的字符
\uhhhh 十六进制表示为 0xhhhh 的 Unicode 字符
\t 制表符
\n 换行符
\r 回车符

字符类

字符 含义
[abc] a, b, c 任意一个字符
[^abc] 除 a, b, c 外的任意一个字符
[a-zA-Z] 从 a 到 z 或从 A 到 Z 的任意一个字符
[a-d[m-p]] 即 [a-dm-p]
[a-z&&[def]] 即 [def]
[a-z&&[^bc]] 即 [ad-z]
[a-z&&[^m-p]] 即 [a-lq-z]
[abc[xyz]] 即 [abcxyz]

预定义字符类

字符 含义
. 任意字符
\d 数字 [0-9]
\D 非字符 [^0-9]
\s 空白字符(空格、制表符、换行、换页、回车)
\S 非空白字符
\w 单词字符[a-zA-Z_0-9]
\W 非单词字符[^\w]

边界匹配符

字符 含义
^ 一行开始
$ 一行末尾
\b 词的边界,例如有 “hello world” 这个字符串两个单词之间的空格
\B 非词的边界
\G 前一个匹配的结束

逻辑操作符

字符 含义
XY X 后跟 Y
X | Y X 或者 Y
(X) X 作为捕获组

量词

字符 含义
* 零个或多个
+ 一个或多个
? 一个或零个
{m, n} 至少 m 次,至多 n 次
{m,} 至少 m 次
{m} 恰好 m 次
  • 匹配优先模式:这是量词匹配的默认模式,它尽可能多地匹配字符。
  • 忽略优先模式:需要在量词后面加上“?”号来启动这种模式,这种模式尽可能少地匹配字符。
  • 占有优先模式:需要在量词后面加上“+”号来启动这个模式,这种模式与匹配优先模式类似,不过这种模式匹配的内容不会“交还”。

String 类的正则表达式方法

1
2
3
4
public String[] split(String regex)
public String replaceFirst(String regex, String replacement)
public String replaceAll(String regex, String replacement)
public boolean matches(String regex)

Java 中的反斜杠本身就有特殊意义,用来转义特殊字符,所以在正则表达式中使用预定义字符类时,需要使用两个反斜杠 \\,表示要插入正则表达式的反斜杠。例如:

1
2
String str = "1.one22.two3.three";
String[] numStr = str.split("\\d+\\.");

Pattern 和 Matcher

使用正则表达式包的流程如下:

第一步,创建 Pattern 对象:Pattern p = Pattern.compile(reg);reg 为正则表达式字符串。
第二步,获取 Matcher 对象:Matcher m = p.matcher(str);str 为需要匹配的字符串。
第三步,调用 Matcher 对象的 find 方法进行匹配:m.find();如果字符串中有多个匹配项,则可以多次调用 find 方法。如果字符串可以被匹配的话,则 find 方法返回 true,否则返回 false。
第四步,调用 Matcher 对象的其他方法来获取当前匹配的内容。

组匹配

正则表达式中的圆括号可以圈定一个捕获组,而这个组中匹配的内容是可以在匹配操作完成后从匹配器中获取的。

在 Matcher 类中,有两个方法可以获取捕获组匹配的内容,如下:

1
2
public String group()
public String group(int group)

捕获组是从 1 开始从左到右的索引,组 0 表示整个模式。

替换字符串

Matcher 对象的 replaceAll 和 replaceFirst 方法,传入需要替换的值,返回替换后的字符串。


总结

目前的 Java 对字符串操作的支持已经相当完善,相较于 C/C++,Java 中的字符串操作更为简单,且不容易出错,这得益于 Java 的自动垃圾内存回收机制和 String 类的使用。

Java 内置了对正则表达式的支持。

String 类是 final 类,且 String 对象是常量对象,其内容不能被修改。


Share