一、字符串概述
在Java中只要是双引号""引起来的就是字符串,字符串是Java.lang.String类的实例,用于表示一系列字符序列。字符串是不可变的,这意味着字符串一旦创建,就不能更改字符串的内容。
字符串的操作:拼接、比较、替换、截取、查找、切割、检索、加密、打乱顺序、大小写转换....操作
本文章主要包括的内容有:
1、String、StringBuilder、StringJonier、Pattern、Matcher等字符串常见操作
2、实际开发中的案例
3、字符串相关的底层原理
二、String
1、创建字符串的两种方式
1、直接赋值
String name = "张三";
2、new
构造方法 | 说明 |
public String() | 创建空白字符串,不包含任何内容 |
public String(String original) | 根据传入的字符串,创建字符串对象 |
public String(char[] chs) | 根据字符数组,创建字符串对象 |
public String(byte[] chs) | 根据字节数组,创建字符串对象 |
public static void main(String[] args) {
//1、直接赋值
String s1 = "abc";
System.out.println(s1);
//2、使用new的方式来获取一个字符串对象(不使用)
String s2 = new String();
System.out.println(s2);
//3、传递一个字符串,根据传递的字符串内容再创建一个新的 字符串对象(不使用)
String s3 = new String("abc");
System.out.println(s3);
//4、传递一个字符数组,根据字符数组的内容创建一个新的字符串
//对于这个操作是有应用场景的
//例如:修改字符串的内容、abc -> Qbc
//我们可以将abc先转换成字符数组,然后根据索引修改里面的值
char[] chs = {'a', 'b', 'c', 'd'};
String s4 = new String(chs);
System.out.println(s4);
//5、传递一个字节数组,根据字节数组的内容创建一个新的字符串对象
//应用场景:以后在网络中传输的数据其实是字节信息
//我们一般要把字节信息进行转换,转成字符串,此时就要用到这个构造了
byte[] bytes = {97, 98, 99};
String s5 = new String(bytes);
System.out.println(s5);
}
2、字符串在内存的表现形式
这里会涉及到Java中三块内存分别是:栈内存、堆内存、方法区、字符串常量池
栈:方法运行的时候进栈,执行完出栈
堆:new出来的对象都在这里
方法区:类加载的时候、class文件会临时存储在这里
串池:字符串常量池,直接赋值的字符串才会在这个里面,new出来的不在这个里面((JDK7以前串池是在方法区中的,7以后该成在堆内存中了))
例1(直接赋值字符串内存图):
public class StringDemo{
public static void main(String[] args){
String s1 = "abc";
String s2 = "abc";
}
}
程序运行,首先main方法加载入栈,然后从main方法里面,从上往下执行代码
第一行代码是等号直接赋值的,在main方法中首先会创建一个变量s1,等号的右边,系统首先会看串池中有没有abc,第一次加载是没有的,在串池中就会创建abc,然后把地址赋值给main方法中的s1变量。
执行第二行代码时,由于串池中现在已经有abc了,它现在就不会去创建一个新的,而是会复用串池中abc,将abc的地址值赋值给s2
例2(通过new创建字符串的内存图)
public class StringTest{
public static void main(String[] args){
char[] chs = {'a','b','c'};
String s1 = new String(chs);
String s2 = new String(chs);
}
}
首先程序运行,main方法入栈,运行第一行创建字符数组,堆内存中创建变量chs保存在堆内存中开辟空间存储的字符数组内存地址。
第二行代码,此时等号的右边是new出来的,等号左边是一个变量s1存储在栈中,右边在堆内存中开辟一个空间,存储abc值,并将地址值赋值给变量s1
第三行代码,由于是new创建的,我们知道new一次就会开辟一次空间,所以就会创建一个新的空间再次存储abc,并将地址值赋值给s2
这里我们可以知道,s1、s2记录的地址值不一样,这种方式创建的字符串不会复用,如果创建过多,会浪费系统空间。
3、字符串常用方法
Java中的String
类提供了许多用于处理字符串的常用方法。以下是一些最常用的方法:
length() - 返回字符串的长度。
String str = "Hello"; | |
int len = str.length(); // 结果为5 |
charAt(int index) - 返回指定索引处的字符。
String str = "Hello"; | |
char ch = str.charAt(1); // 结果为'e' |
concat(String str) - 连接两个字符串。
String str1 = "Hello"; | |
String str2 = "World"; | |
String str3 = str1.concat(str2); // 结果为"HelloWorld" |
equals(Object obj) - 比较两个字符串的内容是否相同。
String str1 = "Hello"; | |
String str2 = "Hello"; | |
boolean isEqual = str1.equals(str2); // 结果为true |
equalsIgnoreCase(String anotherString) - 比较两个字符串,忽略大小写。
String str1 = "Hello"; | |
String str2 = "hElLo"; | |
boolean isEqual = str1.equalsIgnoreCase(str2); // 结果为true |
startsWith(String prefix) - 测试字符串是否以指定的前缀开始。
String str = "HelloWorld"; | |
boolean isStart = str.startsWith("Hello"); // 结果为true |
endsWith(String suffix) - 测试字符串是否以指定的后缀结束。
String str = "HelloWorld"; | |
boolean isEnd = str.endsWith("World"); // 结果为true |
indexOf(int ch) 和 indexOf(String str) - 返回指定字符或子字符串第一次出现的索引。
String str = "HelloWorld"; | |
int index = str.indexOf('W'); // 结果为5 | |
int indexStr = str.indexOf("World"); // 结果为5 |
lastIndexOf(int ch) 和 lastIndexOf(String str) - 返回指定字符或子字符串最后一次出现的索引。
String str = "HelloWorldHello"; | |
int lastIndex = str.lastIndexOf('l'); // 结果为11 | |
int lastIndexStr = str.lastIndexOf("Hello"); // 结果为10 |
substring(int beginIndex) 和 substring(int beginIndex, int endIndex) - 返回字符串的子字符串。
String str = "HelloWorld"; | |
String subStr1 = str.substring(6); // 结果为"World" | |
String subStr2 = str.substring(0, 5); // 结果为"Hello" |
trim() - 去除字符串两端的空白字符。
String str = " Hello "; | |
String trimmedStr = str.trim(); // 结果为"Hello" |
replace(char oldChar, char newChar) 和 replace(CharSequence target, CharSequence replacement) - 替换字符串中的字符或子字符串。
String str = "Hello World"; | |
String replacedStr1 = str.replace('o', 'a'); // 结果为"Hella Warld" | |
String replacedStr2 = str.replace("World", "Java"); // 结果为"Hello Java" |
toLowerCase() 和 toUpperCase() - 将字符串转换为小写或大写。
String str = "Hello"; | |
String lowerCaseStr = str.toLowerCase(); // 结果为"hello" | |
String upperCaseStr = str.toUpperCase(); // 结果为"HELLO" |
这些只是String
类提供的一些常用方法,实际上它还有更多的方法可以用于处理字符串,可以去Api文档查看。
三、StringBuilder
StringBuilder
是 Java 中一个可变的字符序列,用于构造字符串,尤其是在需要频繁修改字符串的场景下。StringBuilder
内部实现了一个可变的字符数组,通过数组索引来操作字符,因此比使用 String
连接字符串的效率要高得多。
特性
- 可变:
StringBuilder
可以在创建后修改其内容。 - 线程不安全:在多线程环境下,
StringBuilder
的操作可能不安全,因为多个线程可能同时修改其内部状态。如果需要在多线程环境下操作,可以使用StringBuffer
,它是StringBuilder
的线程安全版本。 - 效率高:相比于使用
String
进行字符串连接,StringBuilder
的效率更高,因为它减少了内存分配和垃圾收集的开销。
常用方法
-
构造方法
StringBuilder()
: 构造一个空的StringBuilder
。StringBuilder(int initialCapacity)
: 构造一个具有指定初始容量的StringBuilder
。StringBuilder(String str)
: 构造一个包含指定字符串内容的StringBuilder
。
-
添加和插入
append(String str)
: 追加指定字符串到此字符序列。append(char c)
: 追加指定字符到此字符序列。append(int i)
: 追加指定整数到此字符序列,将其转换为字符串。append(boolean b)
: 追加指定的布尔值到此字符序列,将其转换为字符串。append(char[] str, int offset, int len)
: 追加指定字符数组的子序列到此字符序列。insert(int offset, String str)
: 在此字符序列的指定位置插入指定的字符串。- ...(还有更多重载的
append
和insert
方法)
-
删除和替换
delete(int start, int end)
: 移除此序列的子字符串中的字符。deleteCharAt(int index)
: 移除此序列指定位置的字符。replace(int start, int end, String str)
: 使用给定字符串替换此序列的子字符串。
-
获取字符和子字符串
charAt(int index)
: 返回此序列中指定位置的字符。substring(int start, int end)
: 返回一个新的字符序列,它是此序列的一个子序列。
-
容量和长度
length()
: 返回此字符序列的长度。capacity()
: 返回当前容量。ensureCapacity(int minimumCapacity)
: 确保容量至少等于指定的最小值。
-
其他
reverse()
: 反转此字符序列。setCharAt(int index, char ch)
: 将此序列中指定位置的字符设置为指定的字符。toString()
: 返回此字符序列的字符串表示形式。
使用示例
public class StringBuilderExample { | |
public static void main(String[] args) { | |
StringBuilder sb = new StringBuilder(); | |
// 追加字符串 | |
sb.append("Hello"); | |
sb.append(" "); | |
sb.append("World"); | |
// 插入字符串 | |
sb.insert(5, "Beautiful"); | |
// 删除字符 | |
sb.deleteCharAt(6); | |
// 替换子字符串 | |
sb.replace(11, 16, "Java"); | |
// 获取子字符串 | |
String subStr = sb.substring(0, 5); | |
// 输出结果 | |
System.out.println(sb.toString()); // 输出: Hello Beautiful Java | |
System.out.println(subStr); // 输出: Hello | |
} | |
} |
在实际编程中,根据具体需求选择合适的 StringBuilder
方法,可以高效地构建和修改字符串。
四、StringJoiner
StringJoiner
是 Java 8 引入的一个实用类,它允许你以特定的分隔符、前缀和后缀来连接多个字符串。StringJoiner
特别适用于需要拼接多个字符串,并且希望这些字符串之间有特定的分隔符,或者在整体字符串的开始和结束处有特定前缀和后缀的情况。
特性
- 灵活性:你可以为
StringJoiner
指定分隔符、前缀和后缀。 - 线程安全:
StringJoiner
的实例是线程安全的,多个线程可以同时使用同一个StringJoiner
实例,而不需要额外的同步。 - 高效:
StringJoiner
内部使用StringBuilder
来存储和构建字符串,因此它在性能上表现良好。
常用方法
-
构造方法
StringJoiner(CharSequence delimiter)
: 创建一个新的StringJoiner
,使用指定的分隔符。StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
: 创建一个新的StringJoiner
,使用指定的分隔符、前缀和后缀。
-
添加字符串
add(CharSequence newElement)
: 添加一个新的字符串元素。merge(CharSequence delimiter, CharSequence newElement)
: 如果newElement
不为空,则将其添加到当前构建器中,前面添加指定的分隔符。这通常用于在连接多个字符串时,合并重复的分隔符。
-
获取结果字符串
toString()
: 返回由分隔符连接的字符串,包括前缀和后缀。
-
设置空值策略
setEmptyValue(CharSequence emptyValue)
: 设置当没有元素时返回的字符串。默认情况下,如果没有元素,toString()
方法将返回空字符串(包括前缀和后缀)。
使用示例
public class StringJoinerExample { | |
public static void main(String[] args) { | |
// 使用逗号作为分隔符 | |
StringJoiner joiner = new StringJoiner(","); | |
// 添加元素 | |
joiner.add("apple"); | |
joiner.add("banana"); | |
joiner.add("cherry"); | |
// 合并重复的分隔符 | |
joiner.merge(",", "banana"); // 这不会改变结果,因为 "banana" 已经存在 | |
// 获取结果字符串 | |
String result = joiner.toString(); // 输出: apple,banana,cherry | |
System.out.println(result); | |
// 使用前缀和后缀 | |
StringJoiner joinerWithPrefixSuffix = new StringJoiner(", ", "[", "]"); | |
joinerWithPrefixSuffix.add("one"); | |
joinerWithPrefixSuffix.add("two"); | |
String resultWithPrefixSuffix = joinerWithPrefixSuffix.toString(); // 输出: [one, two] | |
System.out.println(resultWithPrefixSuffix); | |
// 设置空值策略 | |
StringJoiner emptyJoiner = new StringJoiner(","); | |
emptyJoiner.setEmptyValue("no elements"); | |
String emptyResult = emptyJoiner.toString(); // 输出: no elements | |
System.out.println(emptyResult); | |
} | |
} |
在上面的示例中,我们展示了如何使用 StringJoiner
来连接字符串,并设置不同的分隔符、前缀、后缀以及空值策略。通过合理地使用 StringJoiner
,你可以简化字符串拼接的逻辑,并提高代码的可读性和维护性。
五、Pattern、Matcher
在Java中,Pattern
类是用来表示正则表达式的编译表示形式。正则表达式是一种强大的文本处理工具,可以用来匹配、查找和替换文本中的特定字符序列。Pattern
类的主要作用是将正则表达式(指定为字符串)编译为此类的实例,然后这个实例可以被用来创建一个Matcher
对象,该对象可以用于匹配任意字符序列针对正则表达式。
Pattern
类的主要方法包括:
- compile(String regex):该方法用于将给定的正则表达式编译并返回一个
Pattern
对象。这是使用正则表达式的第一步。 - matcher(CharSequence input):该方法用于创建一个匹配器,该匹配器将使用此模式的规则对输入序列进行匹配。
- pattern():返回此模式的字符串表示形式,也就是编译此模式的正则表达式。
- flags():返回此模式的匹配标志。
- toString():返回此模式的字符串表示形式,该字符串是正则表达式的字面值表示。
- quote(String s):返回指定字符串的字面值模式
String
。此方法产生一个字符串,可以安全地用于匹配字符串中的文字(而不是解释为正则表达式的其他元素)。
Pattern
类通常与Matcher
类一起使用,Matcher
类包含了一些用于执行匹配操作的方法,如find()
, matches()
, replaceFirst()
, replaceAll()
等。例如,你可以使用find()
方法在输入字符串中找到所有与正则表达式匹配的部分。
需要注意的是,虽然Pattern
类和Matcher
类提供了强大的文本处理能力,但在使用正则表达式时也需要谨慎,因为复杂的正则表达式可能会导致性能问题,或者在某些情况下可能无法正确匹配预期的结果。因此,在设计正则表达式时,应尽量保持其简单明了,并尽可能进行充分的测试以确保其正确性。
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
public class PatternMatcherExample { | |
public static void main(String[] args) { | |
// 定义一个包含电子邮件地址的字符串 | |
String emailText = "Contact us at info@example.com or support@example.org for assistance."; | |
// 编译一个用于匹配电子邮件地址的正则表达式 | |
Pattern pattern = Pattern.compile("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b"); | |
// 创建一个Matcher对象来应用模式 | |
Matcher matcher = pattern.matcher(emailText); | |
// 使用find()方法查找匹配项 | |
while (matcher.find()) { | |
// 输出匹配到的电子邮件地址 | |
System.out.println("Found email: " + matcher.group()); | |
} | |
} | |
} |
在这个例子中,我们定义了一个包含两个电子邮件地址的字符串emailText
。然后,我们使用Pattern.compile()
方法编译了一个正则表达式,该表达式用于匹配电子邮件地址的通用格式。
接下来,我们使用pattern.matcher(emailText)
创建了一个Matcher
对象,并将要搜索的文本传递给它。
最后,我们使用matcher.find()
方法在文本中查找与正则表达式匹配的子序列。当找到匹配项时,find()
方法返回true
,并且我们可以使用matcher.group()
方法获取匹配的文本。这个过程在while
循环中重复进行,直到没有更多的匹配项为止。
注意,正则表达式"\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b"
用于匹配大多数简单的电子邮件地址。这个正则表达式并不是完美的,因为它可能不会匹配所有有效的电子邮件地址格式,但它对于大多数常见的情况应该足够了。对于更复杂的电子邮件地址验证,可能需要使用更复杂的正则表达式或专门的电子邮件验证库。