目录
String类方法示例:内容修改不操作内部数组,而是生成新字符串对象
String不可变的特性主要体现在以下几个方面:
内容无法修改
举一个很简单的栗子~
在 Java 中,concat是 String类的方法,用于拼接字符串。但是运行结果还是cat,因为String
对象一旦被创建,其字符序列就不能被改变!
String str1="cat";
str1.concat("mao");
System.out.println(str1);
//运行结果:cat
方法操作产生新对象
如果想把mao拼接在cat的后面,那是不是就得重新创建String对象用来输出拼接后的结果呢,
如下:
String str="cat";
String result=str.concat("mao");
System.out.println(result);
//运行结构:catmao
内存地址固定
由于String内容不可变,在内存中他会被分配一个固定的地址。如果多个变量引用同一个字符串常量,它们都会指向同一个内存地址。如下:
String str1="cat";
String str2="cat";
System.out.println(str1==str2);
//运行结果:true
哈希值固定
字符串内容不可变,所以其哈希值在创建后也固定不变。这使得String对象在作为HashMap的键或HashSet的元素时,能够高效地进行存储和查找。如下:
String作为HashMap的键
//创建HashMap
HashMap<String,Integer> map=new HashMap<>();
map.put("橘猫",3);
map.put("白猫",4);
map.put("奶牛猫",10);
//查找”奶牛猫“对应的值
int num=map.get("奶牛猫");
System.out.println(num);
//运行结果:10
在这个例子中,由于String的不可变性保证了其哈希值的固定,HashMap可以根据键的哈希值迅速定位到对应的值,实现高效查找
有可能大家根据上述案例不能直观的感受到String的不可变性——哈希值固定,利用String类的hashCode方法再给大家举一个简单的栗子~
String str1="矮脚曼基康";
String str2="矮脚曼基康";
int hash1=str1.hashCode();
int hash2=str2.hashCode();
System.out.println(hash1==hash2);
//运行结果:true
因为内容相同所以哈希值也相同,都存储在字符串常量池中,str1
和 str2
实际上指向同一个字符串对象,所以运行结果为true
线程安全
多个线程共享 String对象
由于 String 对象不可变,多个线程可以安全地共享同一个 String
对象,而无需担心数据被其他线程修改。举个栗子~
public class StringThreadSafety {
private static String sharedString = "shared data";
public static void main(String[] args) {
// 线程 1
Thread thread1 = new Thread(() -> {
System.out.println("Thread 1 reads: " + sharedString);
});
// 线程 2
Thread thread2 = new Thread(() -> {
System.out.println("Thread 2 reads: " + sharedString);
});
thread1.start();
thread2.start();
}
}
//运行结果:Thread 1 reads: shared data
// Thread 2 reads: shared data
String类的内部数据结构以及该数据结构的特点
内部数据结构
String
类的核心数据结构是一个char
类型的数组,定义如下:
private final char value[];
-
value
是一个char
数组,用于存储字符串中的每个字符。 -
final
关键字表示该数组引用不可变,即value
数组的引用不能被修改。
数据结构的特点
不可变性(Immutability)
-
String
对象是不可变的,一旦创建,其内容就不能被修改。 -
value
数组被声明为final
,意味着value
数组的引用不能被重新赋值。 -
任何对
String
的修改操作(如拼接、替换等)都会创建一个新的String
对象,而不是修改原有对象。
字符编码
-
value
数组存储的是Unicode字符,每个字符占用2个字节(16位),支持国际字符集。 -
Java使用UTF-16编码来表示字符。
高效性
-
由于
String
是不可变的,它可以被安全地共享和缓存,例如在字符串常量池中。 -
不可变性也使得
String
对象在多线程环境下是线程安全的,无需额外的同步机制。
内存优化
-
Java对字符串常量进行了优化,相同的字符串字面量会被存储在字符串常量池中,以减少内存开销。
-
例如,
String s1 = "hello";
和String s2 = "hello";
会指向常量池中的同一个对象。
性能考虑
-
由于
String
的不可变性,频繁的字符串拼接操作(如使用+
)可能会导致性能问题,因为每次拼接都会创建新的对象。 -
对于频繁修改字符串的场景,建议使用
StringBuilder
或StringBuffer
,它们是可变的字符序列,性能更高。
String类方法示例:内容修改不操作内部数组,而是生成新字符串对象
这段代码是 Java 中 String
类的 substring(int beginIndex)
方法的实现。它的作用是从当前字符串中截取从 beginIndex
开始到字符串末尾的子字符串。
public String substring(int beginIndex) {
//检查起始索引的合法性
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
//计算子字符串的长度
int subLen = value.length - beginIndex;
//检查子字符串的合法性
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
//返回子字符串
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
我们着重来看该代码最后一句new String(value, beginIndex, subLen)
-
创建一个新的字符串对象。
-
实现细节:
-
从原字符串的
value
数组中复制从beginIndex
开始、长度为subLen
的字符到新字符串对象中。 -
新字符串对象有自己的
char[]
数组,与原字符串的value
数组是独立的。
-
-
意义:
-
原字符串的内容没有被修改。
-
新字符串对象是独立存在的,与原字符串对象无关。
-
总结
-
不可变性:
String
是不可变的,任何对字符串内容的修改操作都会生成一个新的字符串对象。 -
方法行为:
substring
、concat
等方法不会修改原字符串的内部数组,而是通过复制字符数据生成新的字符串对象。 -
性能与优化:
-
不可变性带来了线程安全、内存优化等优点。
-
频繁的字符串修改操作可能会导致性能问题,建议使用
StringBuilder
或StringBuffer
。
-
肥嘟嘟左卫门就讲到这里啦,麻烦大家一键三连!!!🐽