目录
substring(int beginIndex) - 从指定位置开始截取字符串:
substring(int beginIndex, int endIndex) - 截取字符串的子串:
contains(CharSequence s) - 判断字符串是否包含子串:
replace(char oldChar, char newChar) - 替换字符:
replace(CharSequence target, CharSequence replacement) - 替换字符串:
字符串的创建
方法一:直接赋值
public static void main(String[] args) {
String a = "123";
String b = "123";
System.out.println(a == b);
}
输出结果为:
解释:
- 由于 a 和 b 都是通过直接赋值的方式创建的,它们指向的是字符串常量池中的同一个对象。
- 字符串常量池的特点是:相同的字符串字面量会共享内存,以减少内存消耗。
方法二:使用 new 关键字
String c = new String("123");
public static void main(String[] args) {
String a = "123";
String b = "123";
String c = new String("123");
System.out.println(a == b);
System.out.println(a == c);
System.out.println(b == c);
}
此时的输出结果为:
new 关键字是在堆内存中开辟内存空间
String c = new String("123"); 的含义是为变量c在堆内存中开辟了一块内存空间,假设它的地址为0xc,但它的本质仍然是字符串,还依旧存储在0x1中,详细解释见下文:
- 字符串常量池:字符串常量 "123" 在编译期间会被放入字符串常量池中。
- 字符串对象:使用 new String("123") 创建了一个新的字符串对象,它不在字符串常量池中。
现在来分析代码输出:
- System.out.println(a == b);:由于 a 和 b 都是直接从字符串常量池中取出的相同字符串,因此它们引用的是同一个对象,所以结果为 true。
- System.out.println(a == c);:虽然 a 和 c 的字符内容相同,但是 c 是通过 new String("123") 创建的新对象,它在堆中,而不是字符串常量池中的引用。因此,这里比较的是两个不同的对象引用,结果为 false。
- System.out.println(b == c);:与上面类似,b 和 c 是不同的对象引用,即使它们的内容相同,结果也为 false
== 和equals的联系和区别
' =='
- 对基本数据类型来说,比较的是值是否相同,基本数据类型只能用==进行判断,基本数据类型调用不了方法;
- 对引用数据类型来说,比较的是地址是否相同;
'equals()'
对象的 equals() 方法用于比较两个对象的内容是否相等。默认情况下,Object 类中的 equals() 方法比较的是内存地址,但许多类(如String)会重写这个方法来比较实际的内容。
与String有关的方法
length() - 获取字符串的长度:
String a = "hello world";
int length = a.length();
System.out.println(length);
}
输出结果为:11
charAt(int index) - 根据索引获取字符:
public static void main(String[] args) {
String a = "hello world";
char ch = a.charAt(1);
System.out.println(ch);
}
输出结果为:e---->下标从0开始
想要获取某一个单独的字符,必须要用char进行接收。
substring(int beginIndex) - 从指定位置开始截取字符串:
substring(int beginIndex, int endIndex) - 截取字符串的子串:
public static void main(String[] args) {
String a = "hello world";
String b = a.substring(6);
System.out.println(b);
}
输出结果为:world--->从下标6开始截取
public static void main(String[] args) {
String a = "hello world";
String b = a.substring(1,4);
System.out.println(b);
}
输出结果为:ell--->从下标1截取到3(左闭右开区间)
contains(CharSequence s) - 判断字符串是否包含子串:
public static void main(String[] args) {
String a = "hello world";
String b = a.substring(1,4);
System.out.println(a.contains(b));
System.out.println(a.contains("nihao"));
}
输出结果为:true
false
replace(char oldChar, char newChar) - 替换字符:
public static void main(String[] args) {
String a = "hello world";
String newa = a.replace('h','z');
System.out.println(newa);
}
输出结果为:hezzo worzd--->h全都被替换成了z
replace(CharSequence target, CharSequence replacement) - 替换字符串:
public static void main(String[] args) {
String a = "hello world";
String newa = a.replace("hello","你好");
System.out.println(newa);
}
输出结果为:你好 world
split(String regex) - 字符串分割:
public static void main(String[] args) {
System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8));
String a = "apple,banana,orange";
String[] parts = a.split(",");
for(int i = 0;i<parts.length;i++){
System.out.println(parts[i]);
}
}
输出结果为:
apple
banana
orange
KMP算法
KMP 算法通过构建部分匹配表(前缀表)来优化字符串匹配过程,避免不必要的字符比较。
现要在aabaabaafa字符串中找到aabaaf
传统的解决方法:利用双层for循环
时间复杂度:O(n²)
public class SubstringSearch {
public static void main(String[] args) {
String text = "aabaabaafa";
String pattern = "aabaaf";
// 获取文本和模式的长度
int textLength = text.length();
int patternLength = pattern.length();
// 遍历文本字符串
for (int i = 0; i <= textLength - patternLength; i++) {
int j;
// 检查当前文本窗口是否与模式匹配
for (j = 0; j < patternLength; j++) {
if (text.charAt(i + j) != pattern.charAt(j)) {
break;
}
}
// 如果所有字符都匹配
if (j == patternLength) {
System.out.println("Pattern found at index: " + i);
return; // 如果只需要找到第一个匹配,可以在这里返回
}
}
System.out.println("Pattern not found");
}
}
输出结果为:
为了解决传统方法时间复杂度很低的问题,提出了KMP算法。
基本逻辑
- 两个游标从前向后进行遍历,并且比较值是否相等,如果相等则执行i++,j++;若不等,那么则和暴力解法不一致(此时,i游标到b,j游标到了f)
- 如果我们此时能够让i游标不懂,同时j游标指向b是最好的。至于为什么,稍后做出详细解释。
前缀
前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。
后缀
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续字符串。
KMP算法的核心是找到最长且相等前后缀的长度,利用前缀表来决定在匹配失败时,模式字符串应当跳过多少字符。
回到刚才的问题:为什么j游标直接到了f处呢?
我们可以看到,i,j游标处的字符不相等,而根据前缀表:j游标前一个字符的前缀表的数值为2,所以j游标向前移动2个位置,即到了b。
为什么要前一个字符的前缀表的数值呢?因为要找前面字符串的最长相同的前缀和后缀。
前缀表和next数组
很多KMP算法的实现都是使用next数组来做回退操作,那么next数组与前缀表有什么关系呢?
next数组就可以是前缀表,但是很多实现都是把前缀表统一减一(右移一位,初始位置为-1)之后作为next数组。这并不涉及到KMP的原理,而是具体实现。