字符串常量池
在Java程序中,类似于:1, 2, 3,3.14,“hello”等字面类型的常量经常频繁使用,为了使程序的运行速度更快、更节省内存,Java为8种基本数据类型和String类都提供了常量池。
三级常量池架构
为了节省存储空间以及程序的运行效率,Java中引入了:
1. Class文件常量池:每个.Java源文件编译后生成.Class文件中会保存当前类中的字面常量以及符号信息
-
存储内容:字面量(Literal)和符号引用(Symbolic References)
-
生成时机:编译阶段
-
特点:包含类、方法、字段等元数据信息
-
示例:
CONSTANT_Utf8_info
、CONSTANT_Integer_info
等结构
2. 运行时常量池:在.Class文件被加载时,.Class文件中的常量池被加载到内存中称为运行时常量池,运行时常量池每个类都有一份
-
存储位置:方法区(元空间)
-
加载过程:类加载时从Class文件常量池转换而来
-
特点:动态性(支持运行期间添加常量)
-
重要方法:
String#intern()
3. 字符串常量池:字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable(一种高效用来进行查找的数据结构),存储内容是所有字符串字面量和显式intern的字符串
不同JDK版本下字符串常量池的位置以及默认大小是不同的:
字符串常量池是一个特殊的内存区域,用来存储被多个对象共享的字符串。存储双引号引起来的字符串,存的是字符串的常量值。
- 看常量池中是否存在当前字符串
- 不存在,Java 会将其放入池中并返回引用。
- 存在,直接返回池中的引用。
String s1 = "hello";
String s2 = "hello";
System.out.println(s1==s2);//true
优点:提高内存效率,避免重复分配内存
String 对象创建
1. 直接使用字符串常量进行赋值
-
首次创建:
-
JVM检查字符串常量池是否存在"hello"
-
若不存在,在池中创建新字符串对象
-
返回常量池对象的引用
-
-
再次创建:
-
直接复用常量池中的现有对象
-
返回相同引用
-
s1
和 s2
引用的是同一个 String
对象,所以它们指向相同的内存地址。
2. 通过new创建String类对象
-
执行步骤:
-
强制创建堆对象:无论常量池是否存在,必定在堆中新建String对象
-
字符数组处理:
-
若常量池已有"hello":共享字符数组(
this.value = original.value
) -
若不存在:先在池创建,再复制到堆对象
-
-
哈希值继承:复制原字符串的哈希值(
this.hash = original.hash
)
-
由于 s3
和 s4
引用不同的String
对象,s3 == s4
返回 false
。
缺点:每次都需要为字符串分配新的内存。
this.value = original.value;
:将原始字符串的value
字段(字符数组)复制到新对象this.hash = original.hash;
:这是将original
字符串的hash
字段的值复制到新对象的hash
字段。hash
字段通常是字符串的哈希值,用来优化字符串的比较和查找。
-
虽然字符数组可能共享,但
new
操作必定创建新对象 -
不同String对象的
value
可能指向同一数组,但对象本身不同
这个构造函数的作用是创建一个新 String
对象,它与 original
对象拥有相同的字符数组和哈希值。因此,调用这个构造函数的结果是生成一个新的字符串对象,它的内容和 original
相同。
String s1 = "hello";
String s2 = "hello";
String s3=new String("hello");
String s4=new String("hello");
System.out.println(s1==s2);//true
System.out.println(s3==s4);//false
System.out.println(s1==s3);//false
3. 使用字符数组构造字符串
char[] ch={'h','e','l','l','o'};
String s1 = new String(ch);
//s1.intern();//调用之后,会将s1对象的引用放入到常量池中
String s2 = "hello";
System.out.println(s1==s2);//false
intern 是一个native方法(Native方法指:底层使用C++实现的,看不到其实现的源代码),该方法的作用是手动将创建的String对象添加到常量池中
-
original
:需要复制的原始char
数组。newLength
:新数组的长度,决定了复制后的数组大小。
-
char[] copy = new char[newLength];
创建一个新的char
数组,长度为newLength
。这个数组将存放源数组复制后的数据。 -
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
这行代码使用System.arraycopy
方法将original
数组的内容复制到copy
数组中。具体地:original
:源数组。0
:源数组复制的起始位置(从original
数组的第 0 个元素开始)。copy
:目标数组。0
:目标数组复制的起始位置(将从copy
数组的第 0 个元素开始)。Math.min(original.length, newLength)
:复制的元素数量是原数组长度和新数组长度中较小的一个。这是为了避免超出目标数组的边界。
System.arraycopy
方法会从原数组original
中复制数据到新数组copy
,最多复制Math.min(original.length, newLength)
个元素。 -
return copy;
最后,返回新复制的数组copy
。
总结:
该方法的作用是将原数组 original
的内容复制到一个长度为 newLength
的新数组中:
- 如果
newLength
大于或等于原数组的长度,则会将整个原数组的内容复制到新数组。 - 如果
newLength
小于原数组的长度,则只会复制原数组的前newLength
个元素。
字符串的不可变性
String s="hello";
s+=" World";
System.out.println(s);

//底层逻辑
String s="hello"; //创建s对象
StringBuilder stringBuilder = new StringBuilder(); //创建一个StringBuild对象,为stringbuilkd
stringBuilder.append(s);//将s对象append(追加)stringBuild之后
stringBuilder.append(" world"); //将" world" 字符串append(追加)stringBuild之后
s = stringBuilder.toString();//调用toString方法构造一个新的String对象并赋值给s
System.out.println(s);
String 类的构造方法
String 类的构造方法会使用传入的 String
参数来创建一个新的 String
对象。
在 IntelliJ IDEA 中,默认字符集通常是 UTF-8,
默认情况下,使用平台的默认字符集进行解码。
控制字符(0 到 31 和 127)通常不可显示,不能有效地转化为可见字符。
可打印字符(32 到 126)是可以有效转化为字符串中的可见字符
- 空格(32)
- 数字(48-57):
0-9
- 大写字母(65-90):
A-Z
- 小写字母(97-122):
a-z
- 标点符号和其他符号(33-47, 58-64, 91-96, 123-126)
bytes
:字节数组(byte[]
),包含了待解码的数据。字节数组的每个元素表示一个字节,通常用于表示字符串的字节编码。
charset
:Charset
对象,表示解码时使用的字符集。Charset
是 Java 中表示字符集的类,它可以通过如 StandardCharsets.UTF_8
、StandardCharsets.ISO_8859_1
等来指定
charsetName
:字符集的名称,指定解码时使用的字符集(例如 "UTF-8"
, "ISO-8859-1"
, "GBK"
等)。
String 类的构造方法 | 说明 |
String() | 创建一个空字符串 |
String(byte[] bytes) | 将字节数组转换为对应的字符串 |
String(byte[] bytes, Charset charset) | 将字节数组转换为对应的字符串,并且在转换过程中使用指定的字符集 Charset 进行解码 |
String(byte[] bytes, int offset, int length) | 在指定的字节数组 bytes 中,从 offset 开始,读取 length 个字节,并将这些字节解码为对应的字符串 |
String(byte[] bytes, int offset, int length, Charset charset) | 在指定的字节数组 bytes 中,从 offset 开始,读取 length 个字节,并将这些字节使用指定的字符集 Charset 解码为对应的字符串 |
String(byte[] bytes, String charsetName) | 将字节数组转换为对应的字符串,并且在转换过程中使用指定的字符集 charsetName 进行解码 |
String(byte[] bytes, int offset, int length, String charsetName) | 在指定的字节数组 bytes 中,从 offset 开始,读取 length 个字节,并将这些字节使用指定的字符集 charsetName 解码为对应的字符串 |
String(char[] value) | 将字符数组转换为字符串 |
String(char[] value, int offset, int count) | 在指定的字符数组 value 中,从 offset 开始,读取 count 个字符,并将这些字符转换为字符串 |
String(int[] codePoints, int offset, int count) | 在指定的Unicode 码点数组(int[] codePoints ) 中,从 offset 开始,读取 count 个Unicode 码点数量,并将这些Unicode 码点数量转换为字符串 |
String(String original) | 将original字符串复制一份到新的String 对象中 |
String(StringBuffer buffer) | 将 |
String(StringBuilder builder) | 将 StringBuilder 中的内容转换为一个不可变的 String 对象。即使之后修改 StringBuild 中的内容,string 对象中 的值也不会改变。 |