文章目录
- 一、字符串常量池
- 二、String底层逻辑
-
- [2.1 private final byte coder](#2.1 private final byte coder)
- 三、String方法
-
- [3.1 求取字符串长度](#3.1 求取字符串长度)
- [3.2 判断字符串是否为空](#3.2 判断字符串是否为空)
- [3.3 获取指定索引位置的单个字符](#3.3 获取指定索引位置的单个字符)
- [3.4 比较两个字符串的内容是否相等](#3.4 比较两个字符串的内容是否相等)
- [3.5 查找字符第一次出现的下标](#3.5 查找字符第一次出现的下标)
- [3.6 其他类型转成字符串](#3.6 其他类型转成字符串)
- [3.7 字符串转成其他类型](#3.7 字符串转成其他类型)
- [3.8 大小写转换](#3.8 大小写转换)
- [3.9 字符串转数组](#3.9 字符串转数组)
- [3.10 替换字符串](#3.10 替换字符串)
- [3.11 字符串拆分](#3.11 字符串拆分)
- [3.12 去除空格](#3.12 去除空格)
- [3.13 字符串截取](#3.13 字符串截取)
一、字符串常量池
字符串常量池 = JAVA专门给字符串开的"缓存仓库",目的是少创建对象、省内存、跑的快。
- 它是一块专门存放字符串字面量的内存区域
- 所有用""双引号直接写的字符串,都会自动进池
- 同一个内容的字符串,池中只存一份
- 后面再用相同字符串,直接复用,不会再新建
总之:字符串常量池就是字符串对象的共享缓存区。
最直观的例子
java
String s1 = "hello";
String s2 = "hello";
执行过程:
- 执行s1 = "hello";
--->池中没有"hello"
--->创建--->放入字符串常量池中 - 执行s2 = "hello";
--->字符串常量池中已经有"hello"
--->直接把池中对象地址给s2
结果
java
System.out.println(s1 == s2); //true
==比较的是地址,此时s1和s2相等因为地址完全一样
那为什么new String不一样
new String String s = new String("abc");
- 先在堆中创建一个新 String 对象
- 再去常量池看 "abc" 是否存在
- 不存在则在字符串常量池中创建 "abc"
- 堆对象内部的 value 数组指向字符串常量池中的字符数组
java
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); //false
原因:
- new String(...)一定会在堆里创建新对象
- 但双引号"hello"依然会进常量池
- 所以s1、s2是堆上两个不同的对象
- 只是它们的底层都指向池里同一个char[]

下面为打开断点进行调试可发现:

字符串常量放在哪?
- Java 6及以前:在方法区(永久代PermGen)
- Java 7+:移到了堆内存(Heap)里
- Java 8+:永久代取消,用元空间MetaSpace,池仍在堆里
经典面试题:创建了几个对象
java
String s = new String("abc");
答案:2个
- 堆中:new String对象
- 常量池:"abc"字符串对象
编译器优化
java
String s = "a" + "b" + "c";
编译器会直接优化成:
java
String s = "abc";
二、String底层逻辑
在 Java 9+ 的 String 类源码中,coder 和 hash 是两个非常关键的私有成员变量,分别负责编码标识和哈希码缓存.
2.1 private final byte coder
作用:标记底层 byte[] value 数组使用的字符编码格式,决定每个字符占 1 字节还是 2 字节。
背景(为什么引入 coder?)
- JDK 8 及以前 :String 用 char[] 存储,固定 UTF-16 编码,每个字符占 2 字节 。
问题:英文、数字等单字节字符(Latin-1)也占 2 字节,内存浪费 50%。 - JDK 9+ 优化(Compact Strings) :改为 byte[] + coder,自动选编码 :
- 纯 Latin-1 → 1 字节 / 字符
- 含中文 / 特殊字符 → 2 字节 / 字符(UTF-16)
大幅节省内存(通常省 10%--15%)。
取值含义
java
// String 类源码(JDK 9+)
static final byte LATIN1 = 0;
static final byte UTF16 = 1;
private final byte value[]; // 真实存储
private final byte coder; // 编码标记
- coder = 0 (LATIN1)
字符串全是单字节字符 (a-z, 0-9, 英文符号)。
每个字符占 1 字节。
例:"Hello" → coder=0,value 长度 = 5。 - coder = 1 (UTF16)
含中文、 emoji、生僻字 等非 Latin-1 字符。
每个字符占 2 字节。
例:"Hello你好" → coder=1,value 长度 = 14。
三、String方法
3.1 求取字符串长度
length()
- 作用:获取字符串的字符个数(长度)
- 返回值:int(空字符串返回 0)
java
public static void main(String[] args) {
String str = "Hello Java";
System.out.println(str.length()); //输出:10(空格也算1个字符)
String empty = "";
System.out.println(empty.length()); //输出0
}
易错点:
- 数组是 .length 属性,字符串是 .length() 方法,不能漏写括号!
3.2 判断字符串是否为空
isEmpty()
- 作用:判断字符串是否为空字符串(length() == 0)
- 返回值:boolean
java
public static void main1(String[] args) {
String str1 = "";
String str2 = " "; //空格字符串
System.out.println(str1.isEmpty());
System.out.println(str2.isEmpty()); //false(空格不是空)
}
易错点:
- isEmpty() 只判断空字符串,不判断 null!
- null.isEmpty() 会报 NullPointerException(空指针异常)。
3.3 获取指定索引位置的单个字符
charAt(int index)
- 作用:获取指定索引位置的单个字符
- 参数:index(索引从 0 开始)
- 返回值:char
java
public static void main2(String[] args) {
String str = "abcdef";
char c = str.charAt(1);//找到对应下标的字符
System.out.println(c); //输出b
}
易错点:
- 索引越界:index < 0 或 index >= length() → StringIndexOutOfBoundsException
- 最后一个字符索引是 length()-1,不是 length()。
3.4 比较两个字符串的内容是否相等
- equals():严格区分大小写
java
public static void main(String[] args) {
String s1 = "abcd";
String s2 = new String("abcd");
// 正确:比较内容是否相同
System.out.println(s1 == s2); //false
// 错误:比较地址,结果 false
System.out.println(s1.equals(s2)); //true
}
2.equalsIgnoreCase():忽略大小写比较
java
public static void main(String[] args) {
String s1 = "abcd";
String s2 = new String("ABCD");
//不忽略大小写
System.out.println(s1.equals(s2));
//忽略大小写
System.out.println(s1.equalsIgnoreCase(s2)); //true
}
3.5 查找字符第一次出现的下标
java
public static void main(String[] args) {
String s1 = "abcda";
System.out.println(s1.indexOf('a'));
}
- 查找指定字符在字符串中的位置,找到返回下标,没有找到返回-1。
- 如果在字符串中同时存在多个指定字符,那么从左到右进行查找,只会返回第一个字符所对应字符串的下标
java
public static void main(String[] args) {
String s1 = "abcda";
System.out.println(s1.indexOf('a',2)); //4
}
如果多了一个参数,第二个参数则代表从哪个下标处进行查找,上述代码则是从s1字符串下标为2处进行查找字符a。所以返回4
java
public static void main(String[] args) {
String s1 = "abcdabcdabcd";
System.out.println(s1.indexOf("cda")); //2
}
除此之外,该方法还有重载方法可以查找字符串,如果找到该字符串,则返回第一个字符在字符串对应的下标,如果全部遍历完没找到则返回-1
3.6 其他类型转成字符串
- String.valueOf ( ) 最推荐、最安全
适用:int、long、float、double、boolean、char、对象都能转
优点:不会报空指针(null 会转成 "null" 字符串)
java
public static void main(String[] args) {
int a = 10;
Integer b = 20;
double c = 9.99;
boolean d = false;
String A = String.valueOf(a);
System.out.println(A);
String B = String.valueOf(b);
System.out.println(B);
String C = String.valueOf(c);
System.out.println(C);
String D = String.valueOf(d);
System.out.println(D);
}
- 包装类.toString ( )
适用:Integer、Long、Double、Boolean 等包装类型
java
Integer i = 456;
String s = i.toString(); // "456"
Double d = 3.14;
String s2 = Double.toString(d); // "3.14"
⚠️ 注意:如果对象是 null,会报空指针异常
- 变量 + "" 最简单、最常用
适用:任何类型,代码最短
java
int a = 789;
String s = a + ""; // "789"
double b = 1.23;
String s2 = b + ""; // "1.23"
优点:写起来最快
缺点:底层会创建多余对象,性能略差(一般场景无所谓)
3.7 字符串转成其他类型
一、基本类型转换(字符串 → int /double/long /boolean 等)
统一规律:全部用对应包装类的 parseXXX() 静态方法
格式:
java
包装类.parse基本类型(String 字符串)
1、String 转 int
java
String str = "123";
int num = Integer.parseInt(str);
2、String 转 double
java
double d = Double.parseDouble("3.14");
3、String 转 boolean
只有 true / false 能转,其他全部转 false
java
boolean flag = Boolean.parseBoolean("true");
二、String 转 包装类型(Integer、Double...)
java
// String 转 Integer
Integer i = Integer.valueOf("100");
// String 转 Double
Double d = Double.valueOf("3.14");
转换的字符串格式必须合法
比如 "123abc"、" 123"、空字符串 ""、null
调用 parseInt() 直接抛出 NumberFormatException 数字格式异常

3.8 大小写转换
- 全部转大写
字符串.toUpperCase()
java
String str = "HelloJava";
String upper = str.toUpperCase();
System.out.println(upper); // HELLOJAVA
- 全部转小写
字符串.toLowerCase()
java
String str = "HelloJava";
String lower = str.toLowerCase();
System.out.println(lower); // hellojava
注意点!!!
- String 是不可变的
toUpperCase()、toLowerCase() 不会修改原字符串,只会返回一个新字符串,必须用变量接收。
java
String s = "abc";
s.toUpperCase();
System.out.println(s); // 还是 abc!原字符串没变
- 只转换英文字母 a-z A-Z
数字、汉字、符号、空格全部不改变
java
String s = "Java123你好!@#";
System.out.println(s.toUpperCase()); // JAVA123你好!@#
3.9 字符串转数组
把字符串每一个字符,单独放进数组
方法:字符串.toCharArray()
java
public class StringTest {
public static void main(String[] args) {
// 1. 字符串转字符数组
String s1 = "abc123";
char[] charArr = s1.toCharArray();
System.out.println("字符数组:");
for (char c : charArr) {
System.out.print(c + " ");
}
System.out.println();
// 2. 字符串转字符串数组(分割)
String s2 = "苹果,香蕉,橘子";
String[] strArr = s2.split(",");
System.out.println("\n分割数组:");
for (String s : strArr) {
System.out.println(s);
}
// 3. 字符数组转回字符串
String newStr = new String(charArr);
System.out.println("\n字符数组转字符串:"+newStr);
}
}
3.10 替换字符串
- 全部替换(最常用)replace()
全部替换所有匹配的内容
格式:
java
字符串.replace(旧内容, 新内容)
java
public static void main(String[] args) {
String str = "javajava123";
String s = str.replace("java","C#");
System.out.println(s); //C#C#123
}
特点:
- 替换所有出现的旧字符串
- 支持字符、字符串替换
- 不会修改原字符串,返回新字符串
- 不是正则,普通文本替换,最安全
- 只替换第一个 replaceFirst()
只替换第一次出现的内容,后面的不动
java
public static void main(String[] args) {
String str = "javajava123";
String s = str.replaceFirst("java","C#");
System.out.println(s); //C#java123
}
- 替换所有(正则专用)replaceAll()
按照正则表达式替换所有内容
java
public static void main(String[] args) {
// 把所有数字替换成C#
String str = "javajava123";
String s = str.replaceAll("\\d","C#");
System.out.println(s); //javajavaC#C#C#
}
注意!
- 所有替换方法都满足:
String 不可变,原字符串不会被修改,必须用变量接收结果
java
String s = "abc";
s.replace("a","b");
System.out.println(s); // 依然是 abc
- 替换内容不存在时,不会报错,直接返回原字符串
完整代码运行:
java
public class StringReplace {
public static void main(String[] args) {
String str = "aabbaa123aa";
// 1. 全部替换
String s1 = str.replace("aa", "xx");
System.out.println("replace全部替换:"+s1);
// 2. 只替换第一个
String s2 = str.replaceFirst("aa", "xx");
System.out.println("replaceFirst第一个:"+s2);
// 3. 正则全部替换(替换所有数字)
String s3 = str.replaceAll("\\d","*");
System.out.println("replaceAll正则替换:"+s3);
}
}

3.11 字符串拆分
方法格式:
java
1. 字符串.split("分隔符")
2. 字符串.split("分隔符", 限制个数) //split 带两个参数(限制拆分个数)
作用:按照指定符号,把一个字符串切开,返回 String[] 字符串数组
java
String str = "Java,MySQL,HTML,Python";
// 以逗号 , 拆分
String[] arr = str.split(",");
String s = "1,2,3,4,5";
// 最多只拆分 2 次,分成3段
String[] arr = s.split(",", 3);
拆分后数组:
{"Java","MySQL","HTML","Python"}
{"1","2","3,4,5"}
注意!!!
split() 的参数是正则表达式!
下面这些符号本身是正则关键字,直接写会拆分失败、报错:
. * + ? \ | () []
想要正常拆分这些符号,必须加转义 \
格式:\特殊符号
- 点 . 拆分(最常考)
java
String s = "www.baidu.com";
// 错误写法:split(".") 完全拆分失败
// 正确写法
String[] arr = s.split("\\.");
- 竖线 | 拆分
java
String s = "a|b|c";
String[] arr = s.split("\\|");
- 反斜杠 \
Java 字符串里本身 \ 就要写 \\,所以一共要 4 个斜杠\\\\
java
String s = "a\\b\\c";
String[] arr = s.split("\\\\");
3.12 去除空格
trim()
- 作用
只去掉字符串 首尾 的空格
中间的空格完全不动
java
String s = " hello java ";
String res = s.trim();
System.out.println(res);
// 结果:hello java
缺点非常大:
- 只能清前后空格
- 中间空格删不掉
- 只能删普通半角空格,全角空格(中文空格)删不掉

用正则去除所有空格 replaceAll + 正则
- 去除全部空格(前后 + 中间所有空格)
java
String s = " hello java ";
String res = s.replaceAll("\\s+", "");
\s 匹配任意空白(普通空格、制表符)
- 匹配一个或多个,连续空格一次性删完
结果:hellojava
3.13 字符串截取
Java 字符串截取就两个主力方法:
- substring(int beginIndex)
- substring(int beginIndex, int endIndex)
还有辅助:charAt、split 分割截取。
重点规则:
Java 下标从 0 开始
区间规则:左包含,右不包含
start , end) 取 start,不取 end
第一种:substring (起始下标)
从指定下标,一直截取到字符串末尾
语法:
java
String 新字符串 = 原字符串.substring(开始下标);
示例:
java
String s = "abcdef";
// 下标:0:a 1:b 2:c 3:d 4:e 5:f
String res = s.substring(2);
System.out.println(res);
输出:cdef。从下标 2 开始,后面全部拿走。
第二种:substring (开始下标,结束下标)
截取 [start, end) 中间一段
左取右不取!
语法:
java
s.substring(start, end);
示例:
java
String s = "abcdef";
String res = s.substring(1,4);
System.out.println(res);
下标:1=b,2=c,3=d,4=e(不取)
输出:bcd 包头不包尾,左闭右开