详谈String、StringBuffer 和 StringBuilder

String的创建方式

1. 直接赋值

String msg1 = "nihao";

//在Java中,字符串可以用双引号括起来直接作为常量。

2. 使用new String() 构造方法

   String msg2 = new String("你好");

3. 使用字符数组创建字符串

   char[] chars = {'a','b','c','d'};
   String msg3 = new String(chars);  

4.使用字符数组和偏移量创建字符串

   char[] chars = {'a','b','c','d'};
   String msg4 = new String(chars, 1, 2);

5. 使用字节数组和偏移量创建字符串

   byte[] bytes = msg3.getBytes();
   String msg5 = new String(bytes, 2, 3);

  

String常见的方法

equals()  

String类重写了从 Object 类继承 的 equals() 方法。

  • Object 类的 equals() 方法默认实现是用于比较两个对象的引用是否相同(即内存地址是否相同)。
  • String类重写了 equals() 方法,改为比较字符串对象所包含的字符序列是否一致。
String msg1 = "hello";
String msg2 = "hello";
System.out.println(msg1 == msg2);//等号对于对象比较的是地址  true
System.out.println(msg3.equals(msg4));//true

String msg3 = new String("hello");
String msg4 = new String("hello");
System.out.println(msg3 == msg4);//false
System.out.println(msg3.equals(msg4));//true
//String重写了从object继承下来的equals方法(比较的是是不是同一个对象),比较的对象的内容是否相等

至于为什么直接创建的地址相同,而new出来的地址不同,可以参考[== 和 equals 的区别-CSDN博客](https://blog.csdn.net/weixin_64618449/article/details/140781550)这个比较详细,最后的面试题也有提及。

compareTo()

`compareTo()` 方法是用于比较两个字符串的大小关系的方法。它是 `Comparable<String>` 接口中定义的方法,用于比较当前字符串与另一个字符串的字典顺序。

  • 如果调用 `compareTo()` 方法的字符串按字典顺序在参数字符串之前,返回一个负整数。
  • 如果调用 `compareTo()` 方法的字符串与参数字符串相等,返回 0。
  • 如果调用 `compareTo()` 方法的字符串按字典顺序在参数字符串之后,返回一个正整数。
String str1 = "hello";
String str2 = "world";

int result = str1.compareTo(str2);

// 此时 result 的值将是一个负整数,因为 "hello" 在字典顺序上排在 "world" 之前

注意:`compareTo()` 方法对字符串进行基于Unicode值的比较,因此它区分大小写和字符的顺序。也可以通过判断结果是否为0,来代替equals()方法。

substring()

`substring()` 方法用于从一个字符串中提取子串。它有两种常见的重载形式:

1. substring(int beginIndex)

  • 接受一个整数参数 `beginIndex`,表示截取子串的起始位置(包含该位置的字符)。
  • 返回从 `beginIndex` 开始一直到字符串末尾的子串。

     String str = "Hello World";
     String substr = str.substring(6); // substr 变为 "World"

     从索引 6 开始(包括索引 6 处的字符 'W'),截取到字符串末尾的子串是 "World"。

2. substring(int beginIndex, int endIndex)

  •    接受两个整数参数:`beginIndex` 和 `endIndex`。
  •    `beginIndex` 表示截取子串的起始位置(包含该位置的字符)。
  •    `endIndex` 表示截取子串的结束位置(不包含该位置的字符)。
  •    返回从 `beginIndex` 开始,到 `endIndex` 之前的子串。

     
     String str = "Hello World";
     String substr = str.substring(0, 5); // substr 变为 "Hello" 

     从索引 0 开始(包括索引 0 处的字符 'H'),截取到索引 5 之前的子串是 "Hello"。

注意事项

  • 索引范围:Java 中的字符串索引从 0 开始。因此,索引 0 表示字符串的第一个字符,索引 1 表示第二个字符,依此类推。
  • 字符串长度:当使用 `substring(beginIndex)` 时,如果 `beginIndex` 大于等于字符串的长度,会返回一个空字符串。
  • 性能考虑:Java 中的 `substring()` 方法返回一个新的字符串对象,而不是修改原始字符串。因此,如果频繁使用 `substring()` 方法截取大量字符串,可能会产生大量的临时对象,影响性能。

indexOf()

`indexOf()` 方法用于返回指定字符或字符串在当前字符串中第一次出现的位置索引。


public int indexOf(String str)
 

`str`:要搜索的子字符串。如果找到子字符串,则返回第一次出现的索引位置(索引从0开始),如果未找到,则返回 `-1`。

String str = "Hello World";
int index = str.indexOf("o"); // 返回 4
int index2 = str.indexOf("lo"); // 返回 3
int index3 = str.indexOf("abc"); // 返回 -1,因为未找到

lastIndexOf()

`lastIndexOf()` 方法用于返回指定字符或字符串在当前字符串中最后一次出现的位置索引。


public int lastIndexOf(String str)
 

str:要搜索的子字符串。如果找到子字符串,则返回最后一次出现的索引位置(索引从0开始),如果未找到,则返回 `-1`。

String str = "Hello World";
int index = str.lastIndexOf("o"); // 返回 7
int index2 = str.lastIndexOf("lo"); // 返回 3
int index3 = str.lastIndexOf("abc"); // 返回 -1,因为未找到

注意事项

  • `indexOf()` 和 `lastIndexOf()` 方法都区分大小写。
  • 如果需要不区分大小写的搜索,可以使用 `toLowerCase()` 或 `toUpperCase()` 方法转换为统一大小写后再进行搜索。
  • 如果需要从指定位置开始搜索,可以使用重载的版本,如 `indexOf(String str, int fromIndex)` 和 `lastIndexOf(String str, int fromIndex)`。

charAt(int index)

`charAt()` 方法是用于获取字符串中特定索引位置处的字符。


public char charAt(int index)
 

index:要获取字符的索引位置,范围从 0 到字符串长度减一。

返回值:返回给定索引处的字符。

String str = "Hello";

// 获取索引为 0 的字符,即第一个字符
char firstChar = str.charAt(0);  // 'H'

// 获取索引为 4 的字符,即第五个字符
char fifthChar = str.charAt(4);  // 'o'

// 获取索引为 10 的字符,由于超出了字符串长度,将会抛出 StringIndexOutOfBoundsException 异常
char outOfBoundsChar = str.charAt(10);  
// Throws StringIndexOutOfBoundsException

注意事项

1.索引范围: 索引从 0 开始,即第一个字符的索引为 0,  最大索引是字符串长度减一。
2.异常:如果给定的索引超出了字符串的有效范围(即小于 0 或大于等于字符串长度),将会抛出 `StringIndexOutOfBoundsException` 异常。
3.返回值类型:  `charAt()` 方法返回一个 `char` 类型的值,即指定索引位置上的字符。

startsWith()

public boolean startsWith(String prefix)

prefix:要检查的前缀字符串。如果调用字符串以指定的 `prefix` 开头,则返回 `true`;否则返回 `false`。

String str = "Hello World";

boolean startsWithHello = str.startsWith("Hello");  // true
boolean startsWithHi = str.startsWith("Hi");        // false


endsWith()

public boolean endsWith(String suffix)
`suffix`:要检查的后缀字符串。如果调用字符串以指定的 `suffix` 结尾,则返回 `true`;否则返回 `false`。

String str = "Hello World";

boolean endsWithWorld = str.endsWith("World");  // true
boolean endsWithWorldLowerCase = str.endsWith("world");  // false,因为区分大小写


注意事项

1.大小写敏感: `startsWith()` 和 `endsWith()` 方法在比较前缀或后缀时是区分大小写的。
2.空字符串:  如果传入的前缀或后缀字符串是空字符串 `""`,则 `startsWith()` 和 `endsWith()` 方法会始终返回 `true`,因为空字符串是任何字符串的前缀和后缀。
3.返回值:  返回的是一个布尔值,用来指示调用的字符串是否以指定的前缀或后缀开始或结束。

split()

`split()` 方法用于根据指定的分隔符将字符串分割成多个子字符串,并将结果存储在一个字符串数组中。
public String[] split(String regex)
`regex`:用于分割字符串的正则表达式。返回一个字符串数组,包含分割后的子字符串。需要注意转义特殊字符或使用预定义的字符集合。

String str = "apple,banana,orange";
String[] fruits = str.split(",");
// fruits 数组中的内容为 {"apple", "banana", "orange"}


trim()

`trim()` 方法用于去除字符串的首尾空白字符(空格、制表符等)。
public String trim()
返回去除了首尾空白字符的新字符串。只能去除首尾的空白字符,不能去除字符串中间的空白。

String str = "   Hello World   ";
String trimmed = str.trim();
// trimmed 的值为 "Hello World"


toUpperCase()

`toUpperCase()` 方法用于将字符串中的所有字符转换为大写字母。
public String toUpperCase()
返回一个新字符串,其中所有字符都被转换为大写字母。

String str = "hello world";
String upperCaseStr = str.toUpperCase();
// upperCaseStr 的值为 "HELLO WORLD"


toLowerCase()

`toLowerCase()` 方法用于将字符串中的所有字符转换为小写字母。
public String toLowerCase()
返回一个新字符串,其中所有字符都被转换为小写字母。

String str = "Hello World";
String lowerCaseStr = str.toLowerCase();
// lowerCaseStr 的值为 "hello world"

注意:toUpperCase() 和toLowerCase() 方法不会修改非字母字符。

replace()

`replace()` 方法用于将字符串中所有匹配的字符序列或子字符串替换为新的字符序列或子字符串。这是一个全部替换的方法,即它会替换所有符合条件的部分,而不仅仅是第一个匹配项。


public String replace(CharSequence target, CharSequence replacement)
或者
 public String replace(char oldChar, char newChar)


参数:

   `target`:被替换的目标字符序列或子字符串。
   `replacement`:用于替换的新字符序列或子字符串。
   `oldChar`:被替换的旧字符。
   `newChar`:用于替换的新字符。

返回值:返回一个新的字符串,其中所有匹配的目标字符序列或子字符串都被替换为新的字符序列或子字符串。

示例:

  1. 使用 `CharSequence` 替换:    
     

String str = "Hello World";
String replacedStr = str.replace("o", "00");
// replacedStr 的值为 "Hell00 W00rld"

    

  2. 使用字符替换:
     

String str = "Hello World";
String replacedStr = str.replace('o', '0');
// replacedStr 的值为 "Hell0 W0rld"

注意事项

 `replace()` 方法返回一个新的字符串对象,原始字符串对象不会被修改。
 如果 `target` 是一个字符串,那么将会替换所有出现的子字符串。
 如果 `oldChar` 是一个字符,那么将会替换所有出现的该字符。
 替换区分大小写,例如 `"hello".replace("H", "X")` 将不会替换任何内容,因为 "H" 和 "h" 不是同一个字符。

intern()

在Java中,`intern()` 方法是一个比较特殊的方法,它用于在运行时常量池中(String Pool)将字符串对象加入池中,并返回池中对象的引用。这个方法可以用来节省内存,特别是在处理大量重复字符串的情况下。


 public String intern()


作用: 如果常量池中已经存在字符串对象的等效(内容相同)副本,则返回池中的对象引用。
 如果常量池中不存在字符串对象的等效副本,则将该字符串对象添加到常量池中,并返回该对象的引用。

示例:

String str1 = new String("hello");
String str2 = "hello";

// 使用 intern() 方法将字符串对象添加到常量池
String str1Interned = str1.intern();

// 比较两个字符串对象的引用
boolean sameReference = (str1Interned == str2); // true


使用场景:

 在处理大量字符串对象时,特别是存在许多重复内容的情况下,使用 `intern()` 方法可以减少内存消耗,因为它确保相同内容的字符串只在内存中保存一份。
 当需要确保字符串对象的全局唯一性时,比如在多线程环境下,确保不同类加载器加载的类共享字符串常量池。

注意事项

  •  虽然 `intern()` 方法可以帮助节省内存,但它也可能增加字符串常量池的使用量,潜在地影响性能。
  •  调用 `intern()` 方法时,如果常量池中已经存在相同内容的字符串,则直接返回池中的引用,而不会创建新的对象。
  •  建议在确实需要节省内存或者需要字符串全局唯一性的情况下使用 `intern()` 方法。通常情况下,Java 的字符串常量池会自动管理和优化字符串对象的存储。

StringBuffer 和 StringBuilder 

StringBuffer是 Java 中线程安全的可变字符序列类。它的方法大多数都是同步的,适合在多线程环境下使用,但同步化会带来一些性能开销。   它的方法都使用了 `synchronized` 关键字进行同步,因此是线程安全的。由于同步化,性能相对较低,特别是在单线程环境下不需要线程安全时,推荐使用 `StringBuilder`。

StringBuilder也是可变字符序列类,但不保证线程安全。它的所有方法都不是同步的,因此在单线程环境下能够提供更好的性能。 `StringBuilder` 的实现方式和 `StringBuffer` 类似,但没有同步化,因此更适合单线程环境下的字符串操作。

共同方法:`StringBuilder` 和 `StringBuffer` 都有类似的方法,用来进行字符串的增删改查操作,比如 `append()`, `delete()`, `insert()`, `length()` 等。  

StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("lbs");
stringBuffer.insert(0,"du");
System.out.println(stringBuffer);
String s = stringBuffer.toString();
System.out.println(s);
System.out.println(stringBuffer.charAt(1));
//删除的时候有synchronized 同步锁 保证每次只有一个线程进行操作 是安全的
System.out.println(stringBuffer.deleteCharAt(1));
//返回结果是操作后的StringBuffer
System.out.println(stringBuffer.delete(0,stringBuffer.length()));

使用场景:

  StringBuilder:推荐在单线程环境下进行字符串的动态拼接和修改,因为它的性能更高,不需要额外的同步开销。


  StringBuffer:适合在多线程环境下进行字符串操作,因为它保证了线程安全,虽然性能可能不如 `StringBuilder`。

为什么要有StringBuffer和StringBuilder呢?

`String` 的不可变性:
   在Java中,`String` 对象是不可变的,即一旦创建,它的值就不能被更改。每次对 `String` 对象进行拼接、修改等操作,都会生成一个新的 `String` 对象。这意味着如果你频繁地对字符串进行操作,会产生大量的临时对象,从而增加内存的使用和垃圾回收的开销。
`StringBuffer` 和 `StringBuilder` 的可变性:
   `StringBuffer` 和 `StringBuilder` 类允许字符串的内容修改,而不创建新的对象。它们通过可变的字符数组实现,在修改字符串时直接操作该数组,因此避免了频繁地创建新的对象。
线程安全性:
   `StringBuffer` 是线程安全的,所有对它的方法都是同步的(即带有`synchronized`关键字),这意味着多个线程可以安全地同时使用一个 `StringBuffer` 对象。
   `StringBuilder` 不是线程安全的,其方法不是同步的,因此只适合在单线程环境下使用。
性能比较:
   在单线程环境下,`StringBuilder` 的性能优于 `StringBuffer`,因为它不需要进行同步控制,更加高效。
   在多线程环境下,`StringBuffer` 虽然效率稍低(因为需要同步控制),但可以确保线程安全,避免数据不一致的问题。

内存开销:频繁使用 `String` 进行拼接会产生大量的临时对象,增加了堆内存的使用和垃圾回收的负担。

性能:使用 `StringBuffer` 或 `StringBuilder` 可以显著提高字符串拼接的性能,特别是在需要进行大量操作或者在循环中拼接字符串时。

  

面试题

//创建了几个对象
String msg6 = new String("qohdvshgo;");

1.字符串常量池中的对象: `"qohdvshgo;"` 这个字符串常量会被放入字符串常量池中,如果常量池中已经存在相同值的字符串,则会重用现有的对象。在这种情况下,常量池中会存储一个 `"qohdvshgo;"` 的对象。
2.堆中的对象: 使用 `new String("qohdvshgo;")` 显式地创建了一个新的 `String` 对象。这个对象存储在堆内存中,即使字符串常量池中已经存在 `"qohdvshgo;"` 对象,也会在堆中创建一个新的对象。

所以,如果常量池中不存在相同值的字符串对象,则会在常量池中创建一个新的对象,并将该对象返回,又在堆内存里面创建 了一个新的 `String` 对象,就创建了两个对象;如果常量池中已经存在相同值的字符串对象,则会直接返回已存在的对象引用,而不会创建新的对象,就只创建了一个对象。

  • 30
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无知、508

你的鼓励实我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值