
字符串相关
⭐不可变字符序列------String
特点
-
java.lang.String类用于表示字符串。 在 Java 程序中,所有字符串字面量 (例如"hello")都被视为String类的实例。 -
🌠
String对象表示的是不可变的字符序列 。一旦一个String对象被创建,其值将无法被更改。任何对字符串内容的"修改"操作,实际上都会创建一个新的String对象。 -
String类被声明为final,因此:不能被继承、其行为在语言层面是固定的,不允许通过子类方式改变字符串的语义。这保证了String不可变性的安全性和一致性。 -
在 JDK 8 及之前,
String的字符内容存储在一个char[]数组中,该数组用于保存字符串的 UTF-16 编码字符:value被声明为private,外部无法直接访问,也没有get和set方法value被声明为final,其引用不可被修改,String类未提供任何方法用于修改数组中的单个字符
因此,字符串内容在逻辑和物理层面上都保持不可变。

java
public final class String
implements Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
...
- 从 JDK 9 开始,
String类的底层实现进行了优化,改为使用byte[]数组。
官方说明指出:
大多数字符串对象只包含 Latin-1(ISO-8859-1)字符,这些字符只需要一个字节存储,而使用
char[]会造成一半的空间浪费。
因此:如果字符串仅包含 Latin-1 字符,则使用 单字节编码;如果包含其他字符,则使用 UTF-16 编码(双字节);内部通过一个编码标志来区分使用的字符编码方式。该优化在不影响外部行为的前提下显著降低了内存占用。
java
public final class String
implements Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;
private final byte coder;
static final byte LATIN1 = 0;
static final byte UTF16 = 1;
private int hash; // Default to 0
...
- 使用
+运算符可进行字符串拼接;其他类型的对象在与字符串拼接时,会自动调用其toString()方法。
🌠内存结构
由于 String 对象被设计为不可变 ,相同内容的字符串在程序运行过程中可以被安全地共享。为了避免重复创建内容相同的字符串对象、降低内存开销并提升性能,JVM 引入了**字符串常量池(String Constant Pool)**这一机制,用于统一存储字符串字面量以及通过 String#intern() 方法维护的字符串实例。
在 JDK 6 及之前版本 中,字符串常量池属于 方法区(Method Area) 的一部分,方法区通常由 永久代(PermGen) 实现,其内存大小固定、垃圾回收能力有限,容易引发 OutOfMemoryError: PermGen space。
从 JDK 7 开始 ,JVM 对内存结构进行了调整,将 字符串常量池从方法区移至 Java 堆(Heap) 中。这样做的好处包括:
- 字符串常量池可以享受堆内存更灵活的扩展能力;
- 垃圾回收机制更加完善,减少内存溢出风险;
- 有利于提升整体内存管理的稳定性与可维护性。
需要注意的是,字符串常量池的位置变化并不影响 String 不可变性的语义,而是 JVM 内部实现层面的优化调整。
java
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true
// 内存中只有一个"hello"对象被创建,同时被s1和s2共享
s1 = "hi";

java
class Person {
String name;
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "Tom";
Person p2 = new Person();
p2.name = "Tom";
System.out.println(p1.name.equals(p2.name)); // 内容比较 true
System.out.println(p1.name == p2.name); // 引用地址比较 true
System.out.println(p1.name == "Tom"); // 引用地址比较 true
}
}
p1.name.equals(p2.name) → true
.equals()比较字符串内容是否一致。"Tom".equals("Tom")→ 内容相同 →true
p1.name == p2.name → true
==比较的是对象在内存中的引用地址。- Java 对字符串字面量(如
"Tom")会进行字符串常量池优化。 - 所以虽然是两次赋值,实际上
"Tom"指向的是同一个字符串对象 →true
p1.name == "Tom" → true
- 同上,
p1.name指向字符串常量"Tom"→ 同一个对象引用 →true
若改为:
java
p1.name = new String("Tom");
p2.name = new String("Tom");
结果:
java
true
false
false
.equals()依然比较内容 →true- 但两个
new String("Tom")创建了两个不同的对象 → 引用不同 →==为false

✨有关new
java
String s1 = "abc";
String s2 = new String("abc");
System.out.println(s1 == s2); // false
- 没有在堆中创建新对象;
s1指向的是字符串常量池中的String对象(如果"abc"不存在则在常量池创建String("abc")字符串对象;有则复用) s2指向在堆中创建的一个新的String对象,堆中String对象的value数组指向常量池中常量对象的value数组(同样会检查字符串常量池,无则创建,有则复用)

总结:"abc" 形式会直接使用字符串常量池中的对象;new String("abc") 会在堆中创建一个新的 String 对象,该对象的 value 数组指向常量池中已有字符串对象的 value 数组,因此内容相同但引用不同。
Tips:String s = new String("hello"); 可能会在内存中创建 2 个对象
java
String s1 = "a";
String s2 = "a";
String s3 = new String("a");
String s4 = new String("a");
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false

⭐有关intern()
- String s1 = "a";
说明:在字符串常量池中创建了一个字面量为"a"的字符串。
- s1 = s1 + "b";
说明:实际上原来的"a"字符串对象已经丢弃了,现在在堆空间中产生了一个字符串s1+"b"(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能。
最初 s1 指向 "a"(常量池)。拼接 "b" 后,s1 指向堆中新的 "ab" 对象。
- String s2 = "ab";
说明:直接在字符串常量池中创建一个字面量为"ab"的字符串。
- String s3 = "a" + "b";
说明:s3指向字符串常量池中已经创建的"ab"的字符串。
- String s4 = s1.intern();
说明:堆空间的s1对象在调用intern()之后,会将常量池中已经存在的"ab"字符串赋值给s4。
练习:
java
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "world";
String s3 = "hello" + "world"; // 编译期常量拼接
String s4 = s1 + "world"; // 运行期拼接
String s5 = s1 + s2; // 运行期拼接
String s6 = (s1 + s2).intern(); // intern() 返回常量池中的引用(如果有)
System.out.println(s3 == s4); // false
System.out.println(s3 == s5); // false
System.out.println(s4 == s5); // false
System.out.println(s3 == s6); // true
}
}
详解:
String s3 = "hello" + "world";
- 编译器在编译阶段 就会将它优化为
"helloworld",所以s3指向字符串常量池 中的"helloworld"。
String s4 = s1 + "world";
s1是一个变量,虽然值是常量"hello",但由于不是编译期常量,拼接发生在运行时 ,生成的新对象不在常量池中 →new String("helloworld")
String s5 = s1 + s2;
- 同样,
s1和s2都是变量,拼接在运行时发生 → 新的堆对象
String s6 = (s1 + s2).intern();
s1 + s2是运行时生成的"helloworld"(堆中对象)- 调用
.intern()会去常量池中查找 是否已有"helloworld":- 有的话返回常量池中的引用(即
s3) - 所以
s6 == s3→true
- 有的话返回常量池中的引用(即
😊结论:
(1)常量+常量:结果是常量池。且常量池中不会存在相同内容的常量。
(2)常量与变量 或 变量与变量:结果在堆中
(3)拼接后调用intern方法:返回值在常量池中
示例
java
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = s1 + "world";//s4字符串内容也是helloworld,s1是变量,"world"常量,变量 + 常量的结果在堆中
String s5 = s1 + s2;//s5字符串内容也helloworld,s1和s2都是变量,变量 + 变量的结果在堆中
String s6 = "hello" + "world";//常量+ 常量 结果在常量池中,因为编译期间就可以确定结果
System.out.println(s3 == s4);//false
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//true
}
}
final
java
public class Main {
public static void main(String[] args) {
final String s1 = "hello";
final String s2 = "world";
String s3 = "helloworld";
String s4 = s1 + "world";//s4字符串内容也helloworld,s1是常量,"world"常量,常量+常量结果在常量池中
String s5 = s1 + s2;//s5字符串内容也helloworld,s1和s2都是常量,常量+ 常量 结果在常量池中
String s6 = "hello" + "world";//常量+ 常量 结果在常量池中,因为编译期间就可以确定结果
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//true
System.out.println(s3 == s6);//true
}
}
intern()返回常量池中的引用(若有)
java
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = (s1 + "world").intern();//把拼接的结果放到常量池中
String s5 = (s1 + s2).intern();
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//true
}
}
concat方法拼接,哪怕是两个常量对象拼接,结果也是在堆。
java
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String sr2 = "world";
String s3 ="helloworld";
String s4 = "hello".concat("world");
String s5 = "hello" + "world";
System.out.println(s3 == s4);//false
System.out.println(s3 == s5);//true
}
}
java
public class StringTest {
String str = new String("good");
char[] ch = { 't', 'e', 's', 't' };
public void change(String str, char ch[]) {
str = "test ok";
ch[0] = 'b';
}
public static void main(String[] args) {
StringTest ex = new StringTest();
ex.change(ex.str, ex.ch);
System.out.println(ex.str + " and ");// good and
System.out.println(ex.ch); // best
}
}
常用API
问AI
⭐可变字符序列------StringBuffer、StringBuilder
在 Java 中之所以要引入 StringBuffer 和 StringBuilder ,是因为 String 类是不可变的。当对 String 进行拼接、修改等操作时,实际上都会创建新的 String 对象,原有对象不会被改变,这在频繁频繁修改或拼接字符串的场景下会产生大量临时对象,造成内存浪费和性能下降。为了解决这一问题,Java 提供了 StringBuffer 和 StringBuilder,它们是可变字符串类 ,可以在原对象上直接进行修改,从而提高效率。其中,StringBuffer 是线程安全的 ,适用于多线程环境;StringBuilder 是非线程安全的,但性能更高。
StringBuilder和StringBuffer均继承AbstractStringBuilder
java
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/** 用于存储字符的数组 */
char[] value; // 没有 final,value 可以指向新数组
/** 当前字符序列的长度 */
int count;
...
}
StringBuilder和StringBufferAPI 一致,与 String 大致相同
Java比较器
目的
引用数据类型是不能像基本数据类型直接使用比较运算符来比较大小的,而Java 比较器是一种定义对象之间大小关系(排序规则)的机制。
Java 提供了两套比较机制:
| 比较器 | 所在包 | 特点 |
|---|---|---|
| Comparable | java.lang | 自然排序(类自身定义) |
| Comparator | java.util | 外部排序(临时/多策略) |
Comparable------自然排序(内置排序规则)
java
public interface Comparable<T> {
int compareTo(T o);
}
- 实现 Comparable 接口的类必须实现
compareTo方法,两个对象即通过方法的返回值来比较大小。如果当前对象大于形参对象,则返回正整数,小于则返回负整数,等于则返回零。 - 实现 Comparable 接口的对象列表或数组可以通过 Collections.sort 或 Arrays.sort 等进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
- 如果对类 C 的任意两个对象 e1 和 e2 来说:e1.compareTo(e2) == 0 的结果,和 e1.equals(e2) 的结果永远相同(要么都为 true,要么都为 false),那么就称类 C 的自然排序与 equals 方法是一致的。Java 建议(但不强制)这样设计。
示例
java
public class Student implements Comparable<Student> {
private String name;
private int age;
@Override
public int compareTo(Student o) {
return this.age - o.age; // 按年龄升序
}
}
java
List<Student> list = new ArrayList<>();
Collections.sort(list); // 不需要额外比较器
缺点
- 必须修改类源码,不适合第三方类(如
String、Integer已写死) - 只能有一种排序规则
⭐Comparator------外部比较器(灵活排序)
- 适用于不方便修改类代码的场景
- 比较器独立于类,可以随时指定不同排序规则,也不会影响其他地方实现了Comparable接口的排序规则使用
java
public interface Comparator<T> {
int compare(T o1, T o2);
}
- 重写方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
- 可将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。
示例
java
Comparator<Student> ageComparator = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
};
Collections.sort(list, ageComparator);
Lambda
java
Collections.sort(list, (o1, o2) -> o1.getAge() - o2.getAge());
工具方法
java
list.sort(Comparator.comparingInt(Student::getAge));
👉 先按年龄,再按姓名
java
list.sort(
Comparator.comparing(Student::getAge)
.thenComparing(Student::getName)
);
总结
Comparable 用于定义类的"自然排序",Comparator 用于定义"灵活、多策略"的外部排序规则;实际开发中,Comparator 使用更频繁。
系统相关
java.lang.System
java.lang.System 是 Java 的核心类之一,位于 java.lang 包中,无需导包即可使用。
它主要用于:
- 标准输入 / 输出
- 获取系统时间
- 操作 JVM(退出、垃圾回收)
- 读取系统属性(如 OS、Java 版本等)
System 类中的成员几乎都是 static 的 ,通常通过 System.xxx 直接调用。
✨System 的 3 个成员变量
1️⃣ System.in ------ 标准输入流
java
public static final InputStream in;
作用:
- 表示标准输入流
- 默认对应键盘输入
- 常用于
Scanner、BufferedReader等
📌 Scanner 中的 System.in
java
Scanner sc = new Scanner(System.in);
示例:
java
import java.util.Scanner;
public class TestIn {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入一个整数:");
int num = sc.nextInt();
System.out.println("你输入的是:" + num);
}
}
2️⃣ System.out ------ 标准输出流
java
public static final PrintStream out;
作用:
- 表示标准输出流
- 默认输出到控制台
- 常用于打印信息
示例:
java
public class TestOut {
public static void main(String[] args) {
System.out.println("Hello Java");
System.out.print("不换行输出");
}
}
📌 System.out.println() 是最常用的调试和输出方式。
3️⃣ System.err ------ 标准错误输出流
java
public static final PrintStream err;
作用:
- 表示标准错误输出流
- 用于输出错误或警告信息
- 默认也是输出到控制台,但在日志或重定向时与
out可区分
示例:
java
public class TestErr {
public static void main(String[] args) {
System.out.println("普通信息");
System.err.println("错误信息");
}
}
📌 实际项目中常用于:
- 错误日志
- 异常提示
✨System 的 4 个成员方法
1️⃣ currentTimeMillis() ------ 获取当前时间戳
java
public static long currentTimeMillis()
作用:
- 返回当前时间与 1970-01-01 00:00:00 UTC 之间的毫秒数
- 常用于:
- 计算程序运行时间
- 生成时间戳
示例:
java
public class TestTime {
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
// 模拟耗时操作
}
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + " ms");
}
}
2️⃣ exit(int status) ------ 终止 JVM
java
public static void exit(int status)
作用:
- 立即终止 Java 虚拟机(JVM)
- 参数含义:
0:正常退出- 非
0:异常退出
示例:
java
public class TestExit {
public static void main(String[] args) {
System.out.println("程序开始");
System.exit(0);
System.out.println("这行不会执行");
}
}
📌 常用于:
- 程序出现严重错误
- 控制台工具程序
3️⃣ gc() ------ 建议 JVM 进行垃圾回收
java
public static void gc()
作用:
- 通知 JVM 进行垃圾回收
- 不保证立即执行(只是建议)
示例:
java
public class TestGC {
public static void main(String[] args) {
System.out.println("请求垃圾回收");
System.gc();
}
}
📌 实际开发中:
- 很少手动调用
- 垃圾回收主要由 JVM 自动管理
4️⃣ getProperty(String key) ------ 获取系统属性
方法签名:
java
public static String getProperty(String key)
作用:
- 获取 Java 运行环境或操作系统相关信息
- 常用于环境判断、路径获取
| 属性名 | 说明 |
|---|---|
java.version |
Java 版本 |
java.home |
Java 安装路径 |
os.name |
操作系统名称 |
os.version |
操作系统版本 |
user.name |
当前用户名 |
user.home |
用户主目录 |
user.dir |
当前工作目录 |
file.separator |
文件分隔符(/ 或 \) |
java
public class TestProperty {
public static void main(String[] args) {
System.out.println("Java版本:" + System.getProperty("java.version"));
System.out.println("操作系统:" + System.getProperty("os.name"));
System.out.println("用户目录:" + System.getProperty("user.home"));
System.out.println("当前路径:" + System.getProperty("user.dir"));
}
}
java.lang.Runtime
Runtime 类表示 Java 程序的运行环境,主要用于:
- 与 JVM 运行时环境 交互
- 获取 JVM 内存信息
- 主动触发垃圾回收
- 执行外部程序(如
.exe、脚本)
📌 特点:
Runtime是一个单例类- 不能通过
new Runtime()创建对象 - 只能通过
Runtime.getRuntime()获取实例
✨常用方法
1️⃣ getRuntime() ------ 获取 Runtime 实例
java
public static Runtime getRuntime()
作用:
- 获取当前 Java 程序唯一的
Runtime对象
示例:
java
public class TestRuntime {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
System.out.println(runtime);
}
}
2️⃣ exit(int status) ------ 终止 JVM
java
public void exit(int status)
作用:
- 终止 Java 虚拟机
- 与
System.exit(int)效果相同
示例:
java
public class TestExit {
public static void main(String[] args) {
Runtime.getRuntime().exit(0);
System.out.println("不会执行");
}
}
📌 通常更推荐使用 System.exit(),语义更直观。
3️⃣ gc() ------ 请求垃圾回收
java
public void gc()
作用:
- 请求 JVM 进行垃圾回收
- 不保证立即执行
示例:
java
public class TestGC {
public static void main(String[] args) {
Runtime.getRuntime().gc();
System.out.println("已请求GC");
}
}
4️⃣ freeMemory() ------ 获取空闲内存
java
public long freeMemory()
作用:
- 返回 JVM 中可用的空闲内存
- 单位:字节(byte)
5️⃣ totalMemory() ------ 获取 JVM 已分配内存
java
public long totalMemory()
作用:
- 返回 JVM 当前已向操作系统申请的内存总量
6️⃣ maxMemory() ------ 获取 JVM 最大内存
java
public long maxMemory()
作用:
- 返回 JVM 能使用的最大内存
- 与
-Xmx参数相关
示例:查看 JVM 内存情况
java
public class TestMemory {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
System.out.println("最大内存:" + runtime.maxMemory() / 1024 / 1024 + " MB");
System.out.println("已分配内存:" + runtime.totalMemory() / 1024 / 1024 + " MB");
System.out.println("空闲内存:" + runtime.freeMemory() / 1024 / 1024 + " MB");
}
}
📌 常用于:
- 性能调优
- 排查内存问题
7️⃣ exec(String command) ------ 执行外部程序(⚠️重点)
方法签名:
java
public Process exec(String command) throws IOException
作用:
- 在 Java 程序中执行 操作系统命令
- 返回
Process对象
示例 1:打开记事本(Windows)
java
public class TestExec {
public static void main(String[] args) throws Exception {
Runtime.getRuntime().exec("notepad");
}
}
示例 2:执行系统命令
java
public class TestExecCmd {
public static void main(String[] args) throws Exception {
Runtime.getRuntime().exec("cmd /c dir");
}
}
数学相关
java.lang.Math
数学工具类
java.math.BigInteger
超大整数运算,long 不够用(超过 ±9×10¹⁸),需要精确的大整数计算
java.math.BigDecimal
高精度小数运算,金融计算(金额、利率、税费)
java.util.Random
生成伪随机数