Java基础——String类
前言:
在C语言中,处理字符串往往需要借助字符数组或指针,再配合标准库函数(strcpy,strlen)。这种方法将数据与操作方法分离开来,不太符合面向对象的思想。而字符串再日常开发中无处不在,因此java专门设计了String类,将字符串相关的数据和操作封装在一起。
本文将带你全面了解String的内部原理,常用方法,不可变性,常量池机制,以及它的“好兄弟”StringBuilder和StringBuffer。
目录
一.String的构造方法
String提供了多种构造方法,最常用的三种:
public class StringDemo {
public static void main(String[] args) {
// 1. 直接赋值(字符串常量)
String s1 = "hello bit";
System.out.println(s1);
// 2. 通过 new 关键字创建对象
String s2 = new String("hello bit");
System.out.println(s2);
// 3. 使用字符数组构造
char[] array = {'h', 'e', 'l', 'l', 'o'};
String s3 = new String(array);
System.out.println(s3);
// 4. 使用字节数组构造(可将 ASCII 码转为字符串)
byte[] bytes = {97, 98, 99, 100};
String s4 = new String(bytes);
System.out.println(s4); // 输出 "abcd"
}
}
注意:直接赋值与new创建的对象在内存布局上有很大的区别,下文会详细分析。
二.String的内存布局与常量池
2.1String内部结构(基于JDK17)
打开String源码,你会看到:
public final class String
implements Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value; // 存储字符串的字节数组
private final byte coder; // 编码标识:LATIN1 或 UTF16
private int hash; // 缓存哈希值,默认 0
// ...
}
value是一个final的字节数组,真正存储字符数据。
coder 表示编码方式,JDK9+引入了紧凑字符串特性,当字符串只包含Latin-1字符时用1字节,否则用2字节(UTF-16),有效节省内存
hash用于缓存哈希码,避免重复计算。
2.2字符串常量池
字符串常量池是JVM中特殊的存储区域,本质上是一个固定大小的哈希表。它的作用是避免重复创建相同内容的字符串对象,从而节省内存。
不同JDK版本中常量池的位置和大小有所差异:
| JDK版本 | 常量池位置 | 大小特性 |
| Java6 | 方法区(永久代) | 固定大小(1009) |
| Java7 | 堆中 | 可设置,默认(60013) |
| Java8 | 堆中 |
可设置,有范围(最小1009) |
2.3两种赋值方法的区别
示例1:直接赋值
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2); // true
“abc”首先被放入字符串常量池。
str2创建时发现常量池中已经存在“abc”,直接复用同一个引用。
因此str1和str2指向常量池中的同一个对象。
示例2:
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1 == str2); // false
执行new String(“abc”)时,如果常量池中没有”abc“,会先在常量池中创建一个。
然后再在堆上创建一个新的String对象,该对象的value数组指向常量池中的”abc“数据(实际上会复制一份)。
每次new都会产生一个独立的堆对象,所以str1和str2不相等。
三.字符串的比较
字符串比较是开发中的高频操作,java提供了多种比较方式。
3.1 ==:比较引用地址
基本类型:比较值是否相等。
引用类型:比较两个引用是否指向同一个对象。
int a = 10, b = 20, c = 10;
System.out.println(a == b); // false
System.out.println(a == c); // true
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false(不同对象)
3.2equals():比较内容(字典序)
String重写了Object的equals()方法,逐个比较字符:
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1.equals(s2)); // true
3.3compareTo():按字典序返回差值
若两字符串不同,返回第一个不相等字符的ASCII差值
若前k个字符都相等,则返回长度差。
String s1 = "abc";
String s2 = "ac";
System.out.println(s1.compareTo(s2)); // -1('b' - 'c' = -1)
String s3 = "abcdef";
System.out.println(s1.compareTo(s3)); // -3(长度差)
四.常用方法速览
| 方法 | 说明 |
| charAt(int index) | 获取指定位置字符 |
| indexOf(int ch/String str) | 返回第一次出现的位置 |
| lastIndexOf() | 从后往前查找 |
| toUpperCase()/toLowerCase() | 大小写转换 |
| toCharArray() | 转为字符数组 |
| replaceAll(String regex ,String replacement) | 全部替换 |
| split(String regex) | 按分隔符拆分 |
| substring(int begin,int end) | 截取子串(前闭后开) |
| trim() | 去除首尾空格 |
| intern() | 手动将字符串放入常量池 |
4.1拆分注意事项
特殊字符(如 . | +)需要转义,例如split("\\.")才能按点号拆分。
多个分割符可以用|组合,如split(",|;")。
4.2intern()方法示例
char[] ch = {'a', 'b', 'c'};
String s1 = new String(ch); // 堆中对象,常量池中无 "abc"
s1.intern(); // 将 s1 的引用放入常量池
String s2 = "abc"; // 直接复用常量池中的引用
System.out.println(s1 == s2); // true(未注释 intern 前为 false)
五.字符串的不可变性
5.1为什么String是不可变的?
value数组被private final修饰:虽然final保证了数组引用不可变,但数组内部元素其实是可变的,真正关键的是:
String类没有提供任何修改value数组的公共方法。所有看似修改的操作(replace ,toUpperCase)都会创建一个新的String对象,原对象不变。
源码片段:
public final class String {
private final byte[] value; // 不可变的关键
// 没有 setter 方法修改 value 内容
}
5.2不可变的好处
线程安全:不可变对象天然可以在多线程环境下共享。
常量池复用:因为内容不变,才能放心地让多个引用指向同一个字符串。
哈希值可缓存:hash字段只需计算一次,适合作为HashMap的键。
5.3误区提醒
下面这段代码并没有修改原字符串,而是创建了新的对象:
String s = "hello";
s = s + " world"; // 产生新的 String 对象
六.字符串拼接的性能陷阱
如果使用+在循环中拼接字符串,会产生大量临时对象,效率极低。
long start = System.currentTimeMillis();
String s = "";
for (int i = 0; i < 10000; i++) {
s += i; // 每次循环都会 new 一个 String 对象
}
long end = System.currentTimeMillis();
System.out.println("String 耗时:" + (end - start));
String方式可能耗时几十毫秒,而StringBilder几乎为0
七.StringBilder与StringBuffer
为了高效修改字符串,java提供了两个可变字符串类:
| 类 | 线程安全 | 性能 | 使用场景 |
| StringBuilder | 不安全 | 高 |
单线程下频繁 拼接/修改 |
| StringBuffer | 安全(synchronized) | 稍低 | 多线程下操作字符串 |
7.1常用方法
append():追加内容(类似+操作)
insert(int offset, String str):在指定位置插入
delete(int start ,int end):删除区间字符
reverse():反转字符串
toString():转换不可变的String。
StringBuilder sb = new StringBuilder("hello");
sb.append(" world");
sb.insert(5, ",");
System.out.println(sb); // hello, world
String result = sb.toString();
7.2String与StringBuilder互转
String->StringBuilder:通过构造器或append()。
StringBuilder->String:调用toString()。
八.总结
| 特性/类 | String | StringBuilder | StringBuffer |
| 是否可变 | 否 | 是 | 是 |
| 线程安全 | 是 | 否 | 是 |
| 性能(修改时) | 低 | 高 | 中 |
| 适用场景 | 不可变字符串,常量 | 单线程频繁修改 | 多线程频繁修改 |
更多推荐


所有评论(0)