杰瑞科技汇

Java中的String究竟是什么?

可以把 String 理解为 Java 中用来表示文本的数据类型,它不是一个简单的数据类型(intchar),而是一个对象

Java中的String究竟是什么?-图1
(图片来源网络,侵删)

下面我们从几个核心方面来深入理解 String


String 的核心特性

理解 String,首先要记住它的四个最关键特性:

a) 不可变性

这是 String 最重要的特性,一旦一个 String 对象被创建,它的内容(即它所包含的字符序列)就不能被改变

任何看似修改 String 的操作,实际上都是创建了一个新的 String 对象,而原来的对象保持不变。

Java中的String究竟是什么?-图2
(图片来源网络,侵删)

示例:

String s1 = "Hello";
String s2 = s1; // s2 和 s1 指向同一个对象
// 看似修改了 s1,但实际上:
// 1. JVM 在内存中创建了一个新的 String 对象 "World"。
// 2. 让 s1 这个引用变量指向这个新对象。
// 3. 原来那个 "Hello" 对象如果没有其他引用指向它,就会被垃圾回收器回收。
s1 = "World"; 
System.out.println(s1); // 输出: World
System.out.println(s2); // 输出: Hello (s2 仍然指向原来的 "Hello" 对象)

为什么 String 要设计成不可变?

  1. 线程安全不能被改变,所以多个线程可以同时访问一个 String 对象而无需担心数据被篡改,不需要同步操作,提高了效率。
  2. 安全:在处理如数据库用户名、密码、文件路径等敏感信息时,由于其不可变性,这些信息在内存中不会被意外修改,更安全。
  3. 性能优化(字符串常量池):这是 JVM 的一种优化机制,由于 String 不可变,JVM 可以将字面量(如 "abc")创建的字符串对象缓存起来,下次再使用 "abc" 时,会直接从常量池中引用,而不是创建新对象,从而节省内存。

b) 对象而非基本类型

String 是一个类,属于 java.lang 包。String 变量实际上是一个对象的引用,它指向存储在堆内存中的 String 对象。

String str = "abc"; 
// "abc" 是一个 String 对象,str 是指向这个对象的引用。

c) 字符串常量池

这是一个特殊的内存区域,位于方法区(在 JDK 7 及之后版本,它被移到了堆内存中),它专门用来存储字符串字面量。

Java中的String究竟是什么?-图3
(图片来源网络,侵删)

当使用双引号 创建字符串时,JVM 会首先检查常量池中是否已经存在该字符串。

  • 如果存在:直接返回池中对象的引用。
  • 如果不存在:创建一个新的字符串对象,并将其放入常量池,然后返回引用。

示例:

String s1 = "hello"; // JVM 在常量池中创建 "hello" 对象,s1 指向它
String s2 = "hello"; // JVM 发现常量池中已有 "hello",直接让 s2 指向同一个对象
System.out.println(s1 == s2); // 输出 true,因为 s1 和 s2 引用的是同一个对象

注意:使用 new 关键字创建字符串,不会检查常量池,而是直接在堆内存中创建一个新的对象。

String s3 = new String("hello"); // 在堆内存中创建一个新的 "hello" 对象
String s4 = new String("hello"); // 又在堆内存中创建另一个新的 "hello" 对象
System.out.println(s3 == s4); // 输出 false,因为 s3 和 s4 指向的是堆中两个不同的对象

d) final

String 类被声明为 final,这意味着:

  • 不能被继承:你不能创建一个 String 的子类,这保证了所有 String 对象的行为都是一致的,防止有人通过继承来破坏其不可变性。
  • 所有方法都是“固定”的String 类提供的方法集是稳定和不变的。

String 的常用方法

String 类提供了非常丰富的方法来操作字符串。

方法 描述 示例
length() 返回字符串的长度 "abc".length() 返回 3
charAt(int index) 返回指定索引处的字符 "abc".charAt(1) 返回 'b'
substring(int beginIndex) 返回从 beginIndex 开始到末尾的子串 "hello".substring(1) 返回 "ello"
substring(int beginIndex, int endIndex) 返回从 beginIndex 开始到 endIndex-1 的子串 "hello".substring(1, 3) 返回 "el"
concat(String str) 将指定的字符串连接到当前字符串的末尾(创建新对象 "a".concat("b") 返回 "ab"
toUpperCase() / toLowerCase() 将字符串转换为大写/小写(创建新对象 "aBc".toUpperCase() 返回 "ABC"
trim() 去除字符串首尾的空格(创建新对象 " a b c ".trim() 返回 "a b c"
equals(Object anObject) 比较两个字符串的是否相等(区分大小写) "abc".equals("ABC") 返回 false
equalsIgnoreCase(String anotherString) 比较两个字符串的内容是否相等(不区分大小写) "abc".equalsIgnoreCase("ABC") 返回 true
compareTo(String anotherString) 按字典顺序比较两个字符串,返回一个整数值 "abc".compareTo("abd") 返回 -1
contains(CharSequence s) 判断字符串是否包含指定的字符序列 "hello".contains("ell") 返回 true
indexOf(String str) 返回子字符串第一次出现的索引,未找到则返回 -1 "hello".indexOf("l") 返回 2
replace(char oldChar, char newChar) 替换所有出现的字符(创建新对象 "hello".replace('l', 'p') 返回 "heppo"

StringStringBuilderStringBuffer 的区别

在 Java 中,当你需要频繁修改字符串内容时,直接使用 String 会因为频繁创建新对象而导致性能问题,这时就需要 StringBuilderStringBuffer

特性 String StringBuilder StringBuffer
可变性 不可变 可变 可变
线程安全 线程安全(不可变天生安全) 非线程安全 线程安全
性能 频繁修改时性能差 性能最好(无同步开销) 性能比 StringBuilder 稍差(有同步开销)
使用场景 存储和操作不变的文本 单线程环境下,进行大量字符串拼接、修改操作 多线程环境下,进行大量字符串拼接、修改操作

何时使用?

  • 如果你的字符串内容不会改变,或者只改变一次,直接使用 String
  • 如果你在单线程环境中需要频繁地拼接、修改字符串,使用 StringBuilder(性能更高)。
  • 如果你在多线程环境中需要频繁地拼接、修改字符串,为了保证数据安全,使用 StringBuffer

示例:

// 使用 String (效率低)
String result = "";
for (int i = 0; i < 1000; i++) {
    result = result + "a"; // 每次循环都创建一个新的 String 对象
}
// 使用 StringBuilder (效率高)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append("a"); // 在原有的对象上进行修改,不创建新对象
}
String result = sb.toString();

  • String 是什么? 它是 Java 中用于表示文本的不可变对象
  • 核心特性不可变性、是对象、存在于字符串常量池、是final类
  • 如何创建? 可以通过字面量 或 new 关键字创建,两者在内存中的行为不同。
  • 如何操作? String 类提供了大量方法来获取子串、拼接、比较、查找等,但所有方法都会返回一个新的 String 对象。
  • 性能考量:对于频繁的字符串修改,应使用 StringBuilder(单线程)或 StringBuffer(多线程)来代替 String,以避免不必要的对象创建和垃圾回收开销。
分享:
扫描分享到社交APP
上一篇
下一篇