杰瑞科技汇

Java String 用法有哪些关键点?

  1. String 的基本概念与不可变性
  2. 创建 String 对象的多种方式
  3. String 的常用方法详解
  4. StringStringBuilderStringBuffer 的区别与选择
  5. String 在内存中的分配(常量池)
  6. 格式化字符串
  7. 总结与最佳实践

String 的基本概念与不可变性

这是理解 String 的核心。

Java String 用法有哪些关键点?-图1
(图片来源网络,侵删)
  • 不可变性: String 对象一旦被创建,其内容(字符序列)就不能被改变,任何对 String 的修改操作(如 concat(), replace(), substring() 等)都不会修改原始对象,而是返回一个新的 String 对象

  • 为什么设计成不可变?

    1. 安全性: 在多线程环境下,不可变对象是线程安全的,因为它们不会被意外修改,将 String 用作 HashMap 的键时,可以确保其哈希值不会改变。
    2. 性能优化: 由于 String 不可变,JVM 可以对其进行优化,例如字符串常量池,这可以避免在内存中创建大量重复的字符串对象,节省内存空间。
    3. 哈希码缓存: String 类的 hashCode() 方法被重写了,并且在对象创建时计算并缓存了哈希码,因为字符串内容不变,所以它的哈希码也不变,这使得 String 非常适合作为哈希表的键。

创建 String 对象的多种方式

主要有两种方式:字面量赋值和 new 关键字构造。

字面量赋值

这是最常用、最推荐的方式。

Java String 用法有哪些关键点?-图2
(图片来源网络,侵删)
String str1 = "Hello, World!";
String str2 = "Hello, World!"; // str1 和 str2 指向内存中的同一个对象(在常量池中)

使用 new 关键字

这种方式会在堆内存中创建一个新的 String 对象,即使内容相同,也会创建新的对象。

String str3 = new String("Hello, World!");
String str4 = new String("Hello, World!"); // str3 和 str4 指向堆内存中两个不同的对象

混合使用的情况

String s1 = "hello";
String s2 = new String("hello");
String s3 = "hello";
System.out.println(s1 == s2); // false (s1在常量池,s2在堆)
System.out.println(s1 == s3); // true (s1和s3都指向常量池中的同一个对象)

String 的常用方法详解

1 长度与访问

  • length(): 返回字符串的长度(字符数)。

    String str = "Java";
    System.out.println(str.length()); // 输出 4
  • charAt(int index): 返回指定索引处的字符。

    String str = "Java";
    System.out.println(str.charAt(0)); // 输出 'J'
  • toCharArray(): 将字符串转换为字符数组。

    Java String 用法有哪些关键点?-图3
    (图片来源网络,侵删)
    String str = "Java";
    char[] chars = str.toCharArray(); // chars = ['J', 'a', 'v', 'a']

2 查找与判断

  • contains(CharSequence s): 判断字符串是否包含指定的字符序列。

    String str = "I love Java";
    System.out.println(str.contains("Java")); // true
  • startsWith(String prefix) / endsWith(String suffix): 判断字符串是否以指定前缀/后缀开始或结束。

    String str = "filename.txt";
    System.out.println(str.startsWith("file")); // true
    System.out.println(str.endsWith(".txt"));  // true
  • indexOf(String str) / lastIndexOf(String str): 返回指定子字符串第一次/最后一次出现的索引,如果找不到,返回 -1

    String str = "hello world, hello java";
    System.out.println(str.indexOf("hello"));    // 输出 0
    System.out.println(str.lastIndexOf("hello")); // 输出 13
  • isEmpty(): 判断字符串是否为空(长度为0)。

    System.out.println("".isEmpty()); // true
    System.out.println(" ".isEmpty()); // false

3 转换与修改(注意:都返回新对象)

  • substring(int beginIndex) / substring(int beginIndex, int endIndex): 截取子字符串。

    String str = "Hello, World!";
    System.out.println(str.substring(7));      // 输出 "World!"
    System.out.println(str.substring(7, 12));  // 输出 "World" (不包含endIndex)
  • concat(String str): 连接两个字符串。 运算符底层也调用此方法。

    String s1 = "Hello";
    String s2 = s1.concat(", World!");
    System.out.println(s2); // 输出 "Hello, World!"
  • replace(char oldChar, char newChar) / replace(CharSequence target, CharSequence replacement): 替换字符或字符序列。

    String str = "apple";
    System.out.println(str.replace('a', 'A')); // 输出 "Apple"
    System.out.println(str.replace("app", "or")); // 输出 "orange"
  • toLowerCase() / toUpperCase(): 转换为小写/大写。

    String str = "Java";
    System.out.println(str.toLowerCase()); // 输出 "java"
    System.out.println(str.toUpperCase()); // 输出 "JAVA"
  • trim(): 去除字符串首尾的空白字符(空格、制表符、换行等)。

    String str = "  Hello Java  ";
    System.out.println(str.trim()); // 输出 "Hello Java"

4 比较与分割

  • equals(Object anObject): 比较两个字符串的内容是否完全相同。

    String s1 = "hello";
    String s2 = "Hello";
    System.out.println(s1.equals(s2)); // false
  • equalsIgnoreCase(String anotherString): 忽略大小写比较内容。

    String s1 = "hello";
    String s2 = "HELLO";
    System.out.println(s1.equalsIgnoreCase(s2)); // true
  • compareTo(String anotherString): 字典序比较,返回一个整数值:

    • 0: 两个字符串相等。
    • < 0: 调用字符串小于参数字符串。
    • > 0: 调用字符串大于参数字符串。
      System.out.println("apple".compareTo("banana")); // 负数,因为 'a' < 'b'
      System.out.println("banana".compareTo("apple")); // 正数,因为 'b' > 'a'
  • split(String regex): 根据正则表达式分割字符串,返回一个字符串数组。

    String str = "apple,banana,orange";
    String[] fruits = str.split(",");
    // fruits = ["apple", "banana", "orange"]

StringStringBuilderStringBuffer 的区别与选择

特性 String StringBuilder StringBuffer
可变性 不可变 可变 可变
线程安全 线程安全 (不可变) 非线程安全 线程安全
性能 频繁修改时性能差 性能最好 性能比 StringBuilder 稍差
适用场景 存储不变的字符串常量 单线程环境下进行大量字符串拼接操作 多线程环境下进行大量字符串拼接操作

如何选择?

  1. 如果字符串内容不需要改变:始终使用 String

    String name = "John Doe";
  2. 如果在单线程环境下需要频繁修改字符串内容(如循环拼接):使用 StringBuilder,因为它最快。

    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 1000; i++) {
        sb.append(i);
    }
    String result = sb.toString();
  3. 如果在多线程环境下需要频繁修改字符串内容:使用 StringBuffer,它的方法是同步的(synchronized),可以保证线程安全。

    // 在多线程环境中,多个线程可能同时调用 append 方法
    StringBuffer sb = new StringBuffer();
    // ... (线程安全操作)

String 在内存中的分配(常量池)

这是 String 面试的高频考点。

  1. 字面量创建 ():

    • 当使用字面量创建 String 时,JVM 会首先在字符串常量池 中查找是否已经存在该值的字符串。
    • 如果存在,直接返回常量池中该对象的引用。
    • 如果不存在,则在常量池中创建一个新的字符串对象,并返回其引用。
    • 目的:复用对象,节省内存。
  2. new 关键字创建:

    • 使用 new 关键字创建 String 对象时,无论常量池中是否存在相同值的字符串,都一定会在堆内存中创建一个新的 String 对象
    • 如果常量池中不存在该字符串,JVM 会同时在常量池中创建一个对象,然后在堆内存中也创建一个对象,堆中的对象会指向常量池中的对象。
    • 目的:确保每次调用 new 都得到一个全新的对象。
// 示例分析
String s1 = "hello"; // 1. 在常量池中创建 "hello"
String s2 = "hello"; // 2. 从常量池中查找,直接引用 s1 的对象
System.out.println(s1 == s2); // true, 引用相同
String s3 = new String("hello"); // 3. 在堆中创建一个新的 "hello" 对象,常量池中已有
System.out.println(s1 == s3); // false, s1在常量池,s3在堆
String s4 = new String("hello") + " world"; // 4. 先在堆中创建 "hello",再拼接 " world",生成新对象 "hello world"
String s5 = "hello world"; // 5. 在常量池中创建 "hello world"
System.out.println(s4 == s5); // false, s4在堆,s5在常量池

格式化字符串

String.format() 方法可以像 printf 一样格式化字符串,非常强大。

String name = "Alice";
int age = 30;
double score = 95.5;
// 使用 format 方法
String formattedStr = String.format("Name: %s, Age: %d, Score: %.2f", name, age, score);
System.out.println(formattedStr); // 输出: Name: Alice, Age: 30, Score: 95.50
// 常用格式说明符:
// %s: 字符串
// %d: 整数
// %f: 浮点数
// %.2f: 保留两位小数的浮点数
// %c: 字符
// %b: 布尔值

总结与最佳实践

  1. 优先使用字面量:对于不会改变的字符串,使用 赋值,利用常量池优化。
  2. 避免不必要的拼接:在循环中频繁使用 拼接字符串,会创建大量临时对象,性能很差,应使用 StringBuilder
  3. 明确线程需求:在单线程中用 StringBuilder,在多线程中用 StringBuffer
  4. 善用 equalsequalsIgnoreCase时,使用 equals,而不是 。 比较的是对象的内存地址。
  5. 理解不可变性String 的修改操作都是返回新对象,这有助于理解代码行为和性能问题。

希望这份详细的总结能帮助你全面掌握 Java String 的用法!

分享:
扫描分享到社交APP
上一篇
下一篇